-module(gleeth@ethereum@abi@events). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/gleeth/ethereum/abi/events.gleam"). -export([event_topic/2, event_topic_hex/2, decode_log/2]). -export_type([decoded_log/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_log() :: {decoded_log, binary(), list({binary(), gleeth@ethereum@abi@types:abi_value()})}. -file("src/gleeth/ethereum/abi/events.gleam", 17). ?DOC(" Compute the event topic hash: keccak256(\"EventName(type1,type2,...)\")\n"). -spec event_topic(binary(), list(gleeth@ethereum@abi@types:abi_type())) -> bitstring(). event_topic(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>>, gleeth@crypto@keccak:keccak256_binary(gleam_stdlib:identity(Sig)). -file("src/gleeth/ethereum/abi/events.gleam", 27). ?DOC(" Compute the event topic hash as a hex string with 0x prefix.\n"). -spec event_topic_hex(binary(), list(gleeth@ethereum@abi@types:abi_type())) -> binary(). event_topic_hex(Name, Param_types) -> gleeth@utils@hex:encode(event_topic(Name, Param_types)). -file("src/gleeth/ethereum/abi/events.gleam", 57). -spec find_event_by_topic(binary(), list(gleeth@ethereum@abi@json:abi_entry())) -> {ok, gleeth@ethereum@abi@json:abi_entry()} | {error, gleeth@ethereum@abi@types:abi_error()}. find_event_by_topic(Topic0, Events) -> Topic0_clean = string:lowercase(gleeth@utils@hex:strip_prefix(Topic0)), Matching = gleam@list:find(Events, fun(Entry) -> case Entry of {event_entry, Name, Inputs} -> Param_types = gleam@list:map( Inputs, fun(P) -> erlang:element(3, P) end ), Computed = event_topic(Name, Param_types), Computed_hex = string:lowercase( gleam_stdlib:base16_encode(Computed) ), Computed_hex =:= Topic0_clean; _ -> false end end), case Matching of {ok, Entry@1} -> {ok, Entry@1}; {error, _} -> {error, {decode_error, <<"No matching event for topic: 0x"/utf8, Topic0_clean/binary>>}} end. -file("src/gleeth/ethereum/abi/events.gleam", 112). -spec decode_indexed_params( list(gleeth@ethereum@abi@json:event_param()), list(binary()) ) -> {ok, list({binary(), gleeth@ethereum@abi@types:abi_value()})} | {error, gleeth@ethereum@abi@types:abi_error()}. decode_indexed_params(Indexed_inputs, Topics) -> _pipe = gleam@list:zip(Indexed_inputs, Topics), gleam@list:try_map( _pipe, fun(Pair) -> {Input, Topic_hex} = Pair, case gleeth@ethereum@abi@types:is_dynamic(erlang:element(3, Input)) of true -> case gleeth@utils@hex:decode(Topic_hex) of {ok, Bytes} -> {ok, {erlang:element(2, Input), {fixed_bytes_value, Bytes}}}; {error, _} -> {error, {decode_error, <<"Invalid hex in topic: "/utf8, Topic_hex/binary>>}} end; false -> case gleeth@utils@hex:decode(Topic_hex) of {ok, Bytes@1} -> gleam@result:'try'( gleeth@ethereum@abi@decode:decode_single( erlang:element(3, Input), Bytes@1 ), fun(Value) -> {ok, {erlang:element(2, Input), Value}} end ); {error, _} -> {error, {decode_error, <<"Invalid hex in topic: "/utf8, Topic_hex/binary>>}} end end end ). -file("src/gleeth/ethereum/abi/events.gleam", 144). -spec decode_non_indexed_params( list(gleeth@ethereum@abi@json:event_param()), binary() ) -> {ok, list({binary(), gleeth@ethereum@abi@types:abi_value()})} | {error, gleeth@ethereum@abi@types:abi_error()}. decode_non_indexed_params(Non_indexed_inputs, Data_hex) -> Non_indexed_types = gleam@list:map( Non_indexed_inputs, fun(P) -> erlang:element(3, P) end ), Non_indexed_names = gleam@list:map( Non_indexed_inputs, fun(P@1) -> erlang:element(2, P@1) end ), case Data_hex of <<"0x"/utf8>> -> case Non_indexed_types of [] -> {ok, []}; _ -> {error, {decode_error, <<"Expected data for non-indexed params"/utf8>>}} end; <<""/utf8>> -> case Non_indexed_types of [] -> {ok, []}; _ -> {error, {decode_error, <<"Expected data for non-indexed params"/utf8>>}} end; _ -> case gleeth@utils@hex:decode(Data_hex) of {ok, Data_bytes} -> gleam@result:'try'( gleeth@ethereum@abi@decode:decode( Non_indexed_types, Data_bytes ), fun(Values) -> {ok, gleam@list:zip(Non_indexed_names, Values)} end ); {error, _} -> {error, {decode_error, <<"Invalid hex in log data"/utf8>>}} end end. -file("src/gleeth/ethereum/abi/events.gleam", 182). -spec merge_impl( list(gleeth@ethereum@abi@json:event_param()), list({binary(), gleeth@ethereum@abi@types:abi_value()}), list({binary(), gleeth@ethereum@abi@types:abi_value()}), list({binary(), gleeth@ethereum@abi@types:abi_value()}) ) -> list({binary(), gleeth@ethereum@abi@types:abi_value()}). merge_impl(Inputs, Indexed, Non_indexed, Acc) -> case Inputs of [] -> lists:reverse(Acc); [Input | Rest_inputs] -> case erlang:element(4, Input) of true -> case Indexed of [Param | Rest_indexed] -> merge_impl( Rest_inputs, Rest_indexed, Non_indexed, [Param | Acc] ); [] -> merge_impl(Rest_inputs, [], Non_indexed, Acc) end; false -> case Non_indexed of [Param@1 | Rest_non_indexed] -> merge_impl( Rest_inputs, Indexed, Rest_non_indexed, [Param@1 | Acc] ); [] -> merge_impl(Rest_inputs, Indexed, [], Acc) end end end. -file("src/gleeth/ethereum/abi/events.gleam", 174). ?DOC(" Merge indexed and non-indexed params back into the original order.\n"). -spec merge_params_in_order( list(gleeth@ethereum@abi@json:event_param()), list({binary(), gleeth@ethereum@abi@types:abi_value()}), list({binary(), gleeth@ethereum@abi@types:abi_value()}) ) -> list({binary(), gleeth@ethereum@abi@types:abi_value()}). merge_params_in_order(All_inputs, Indexed_params, Non_indexed_params) -> merge_impl(All_inputs, Indexed_params, Non_indexed_params, []). -file("src/gleeth/ethereum/abi/events.gleam", 83). -spec decode_event_params( binary(), list(gleeth@ethereum@abi@json:event_param()), list(binary()), binary() ) -> {ok, decoded_log()} | {error, gleeth@ethereum@abi@types:abi_error()}. decode_event_params(Event_name, Inputs, Indexed_topics, Data_hex) -> Indexed_inputs = gleam@list:filter( Inputs, fun(P) -> erlang:element(4, P) end ), Non_indexed_inputs = gleam@list:filter( Inputs, fun(P@1) -> not erlang:element(4, P@1) end ), gleam@result:'try'( decode_indexed_params(Indexed_inputs, Indexed_topics), fun(Indexed_params) -> gleam@result:'try'( decode_non_indexed_params(Non_indexed_inputs, Data_hex), fun(Non_indexed_params) -> All_params = merge_params_in_order( Inputs, Indexed_params, Non_indexed_params ), {ok, {decoded_log, Event_name, All_params}} end ) end ). -file("src/gleeth/ethereum/abi/events.gleam", 34). ?DOC( " Decode an event log using known ABI event definitions.\n" " Matches topics[0] against event signatures, then decodes indexed\n" " and non-indexed parameters.\n" ). -spec decode_log( gleeth@ethereum@types:log(), list(gleeth@ethereum@abi@json:abi_entry()) ) -> {ok, decoded_log()} | {error, gleeth@ethereum@abi@types:abi_error()}. decode_log(Log, Events) -> case erlang:element(3, Log) of [] -> {error, {decode_error, <<"Log has no topics (anonymous event)"/utf8>>}}; [Topic0 | Indexed_topics] -> gleam@result:'try'( find_event_by_topic(Topic0, Events), fun(Event) -> case Event of {event_entry, Name, Inputs} -> decode_event_params( Name, Inputs, Indexed_topics, erlang:element(4, Log) ); _ -> {error, {decode_error, <<"Matched entry is not an event"/utf8>>}} end end ) end.