%%%------------------------------------------------------------------- %%% @copyright (C) 2025, QPQ AG %%% @copyright (C) 2018, Aeternity Anstalt %%%------------------------------------------------------------------- -module(gmser_api_encoder_tests). -include_lib("eunit/include/eunit.hrl"). -define(TEST_MODULE, gmser_api_encoder). -define(TYPES, [ {key_block_hash , 32} , {micro_block_hash , 32} , {block_tx_hash , 32} , {block_state_hash , 32} , {channel , 32} , {contract_pubkey , 32} , {transaction , not_applicable} , {tx_hash , 32} , {account_pubkey , 32} , {signature , 64} , {name , not_applicable} , {native_token , 32} , {commitment , 32} , {peer_pubkey , 32} , {hash , 32} , {state , 32} , {poi , not_applicable}]). encode_decode_test_() -> encode_decode_test_(?TYPES). encode_decode_known_types_test_() -> KnownTypes = known_types(), SizedTypes = [{T, ?TEST_MODULE:byte_size_for_type(T)} || T <- KnownTypes], encode_decode_test_(SizedTypes). prefixes_are_known_types_test() -> MappedPfxs = mapped_prefixes(), KnownTypes = known_types(), lists:foreach( fun({Pfx, Type}) -> case lists:member(Type, KnownTypes) of true -> ok; false -> error({not_a_known_type, Pfx, Type}) end end, MappedPfxs), lists:foreach( fun(Type) -> case lists:keyfind(Type, 2, MappedPfxs) of {_, _} -> ok; false -> error({has_no_mapped_prefix, Type}) end end, KnownTypes). encode_decode_test_(Types) -> [{"Byte sizes are correct", fun() -> lists:foreach( fun({Type, ByteSize}) -> {_Type, _, ByteSize} = {Type, ByteSize, ?TEST_MODULE:byte_size_for_type(Type)} end, Types) end }, {"Serialize/deserialize known types", fun() -> lists:foreach( fun({Type, Size0}) -> ByteSize = case Size0 of not_applicable -> 42; _ when is_integer(Size0) -> Size0 end, Key = <<42:ByteSize/unit:8>>, EncodedKey = ?TEST_MODULE:encode(Type, Key), {Type, Key} = ?TEST_MODULE:decode(EncodedKey), {ok, Key} = ?TEST_MODULE:safe_decode(Type, EncodedKey) end, Types) end }, {"Key size check works", fun() -> lists:foreach( fun({_Type, not_applicable}) -> ok; ({Type, ByteSize}) -> CheckIllegalSize = fun(S) -> Key = <<42:S/unit:8>>, ?assertError(incorrect_size, ?TEST_MODULE:encode(Type, Key)), EncodedKey = ?TEST_MODULE:unsafe_encode(Type, Key), %% no size check {error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, EncodedKey) end, CheckIllegalSize(0), CheckIllegalSize(ByteSize - 1), CheckIllegalSize(ByteSize + 1) end, Types) end }, {"Missing prefix", fun() -> lists:foreach( fun({Type, Size0}) -> ByteSize = case Size0 of not_applicable -> 42; _ when is_integer(Size0) -> Size0 end, Key = <<42:ByteSize/unit:8>>, EncodedKey = ?TEST_MODULE:encode(Type, Key), <<_PartOfPrefix:1/unit:8, RestOfKey/binary>> = EncodedKey, {error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, RestOfKey), <<_PrefixWithoutDelimiter:2/unit:8, RestOfKey1/binary>> = EncodedKey, {error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, RestOfKey1), <<_WholePrefix:3/unit:8, RestOfKey2/binary>> = EncodedKey, {error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, RestOfKey2) end, Types) end }, {"Piece of encoded key", fun() -> lists:foreach( fun({Type, Size0}) -> ByteSize = case Size0 of not_applicable -> 42; _ when is_integer(Size0) -> Size0 end, Key = <<42:ByteSize/unit:8>>, EncodedKey = ?TEST_MODULE:encode(Type, Key), HalfKeySize = byte_size(EncodedKey) div 2, <> = EncodedKey, {error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, HalfKey), {error, invalid_encoding} = ?TEST_MODULE:safe_decode(Type, RestOfKey) end, Types) end }, {"Encode/decode binary with only zeros", fun() -> Bins = [<<0:Size/unit:8>> || Size <- lists:seq(1,64)], lists:foreach( fun(Bin) -> lists:foreach( fun({Type, S}) -> case S =:= byte_size(Bin) orelse S =:= not_applicable of true -> Encoded = ?TEST_MODULE:encode(Type, Bin), {ok, Decoded} = ?TEST_MODULE:safe_decode(Type, Encoded), ?assertEqual(Decoded, Bin); false -> ok end, Encoded1 = base58:binary_to_base58(Bin), Decoded1 = base58:base58_to_binary(Encoded1), ?assertEqual(Bin, Decoded1) end, Types) end, Bins) end}, {"Encode/decode keypairs", fun() -> KP1 = enacl:sign_keypair(), Enc1 = ?TEST_MODULE:encode_keypair(KP1), {ok, KP1} = ?TEST_MODULE:safe_decode_keypair(Enc1), KP2 = enacl:sign_keypair(), Enc2 = ?TEST_MODULE:encode_keypair(KP2), {ok, KP2} = ?TEST_MODULE:safe_decode_keypair(Enc2), BadEnc = Enc1#{~"priv" => maps:get(~"priv", Enc2)}, {error, illegal_encoding} = ?TEST_MODULE:safe_decode_keypair(BadEnc) end }, {"Encode AND decode both 32-byte and 64-byte account_seckey", fun() -> %% Originally, we could encode a 64-byte seckey, but decode would fail. #{public := Pub, secret := Sec} = enacl:sign_keypair(), <> = Sec, EncSeed = ?TEST_MODULE:encode(account_seckey, Seed), EncSec = ?TEST_MODULE:encode(account_seckey, Sec), {ok, Seed} = ?TEST_MODULE:safe_decode(account_seckey, EncSeed), {ok, Sec} = ?TEST_MODULE:safe_decode(account_seckey, EncSec) end } ]. known_types() -> Forms = get_forms(), [{type, _, union, Types}] = [Def || {attribute, _, type, {known_type, Def, []}} <- Forms], [Name || {atom,_, Name} <- Types]. mapped_prefixes() -> Forms = get_forms(), [Clauses] = [Cs || {function,_,pfx2type,1,Cs} <- Forms], Abst = [{B, A} || {clause,_,[B],[],[A]} <- Clauses], lists:map( fun({B, A}) -> {eval_expr(B), eval_expr(A)} end, Abst). get_forms() -> get_forms(code:which(?TEST_MODULE)). get_forms(Beam) -> {ok, {_, [{abstract_code, {raw_abstract_v1, Forms}}]}} = beam_lib:chunks(Beam, [abstract_code]), Forms. eval_expr(Expr) -> {value, Val, []} = erl_eval:expr(Expr, []), Val.