Compare commits

..

26 Commits

Author SHA1 Message Date
dda5cac7a9 Merge pull request 'Serialization got broken by previous PR' (#54) from uw-fix-serialization into master
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 49m58s
Reviewed-on: #54
2025-04-29 17:22:22 +09:00
Ulf Wiger
07d61722b4 Serialization got broken by previous PR
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 49m58s
2025-04-29 10:18:59 +02:00
4ac7531351 Merge pull request 'uw-switch-semantics' (#53) from uw-switch-semantics into master
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 49m56s
Reviewed-on: #53
2025-04-29 03:57:16 +09:00
Ulf Wiger
f996253e6b Add forgotten exports, expand(Types) function
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 49m55s
2025-04-28 12:12:43 +02:00
Ulf Wiger
b9a51acf55 Add gmser_dyn_types.erl
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 49m54s
2025-04-28 11:59:27 +02:00
Ulf Wiger
5df23c05c1 test case for 'switch'
Some checks failed
Gajumaru Serialization Tests / tests (push) Failing after 49m55s
2025-04-28 11:51:23 +02:00
Ulf Wiger
b358dfe914 Add switch semantics
Some checks failed
Gajumaru Serialization Tests / tests (push) Failing after 49m54s
2025-04-28 11:36:02 +02:00
0288719ae1 Merge pull request 'Save options, test cases for missing labels' (#52) from uw-save-options into master
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 49m42s
Reviewed-on: #52
2025-04-24 06:46:26 +09:00
Ulf Wiger
795c7f7860 Save options, test cases for missing labels
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 49m42s
2025-04-23 23:36:03 +02:00
0d77ca0388 Fix function_clause bug (#51)
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 49m16s
Co-authored-by: Ulf Wiger <ulf@wiger.net>
Reviewed-on: #51
2025-04-14 19:10:58 +09:00
ed204f8526 Merge pull request 'uw-dyn-options' (#50) from uw-dyn-options into master
Some checks failed
Gajumaru Serialization Tests / tests (push) Failing after 49m16s
Reviewed-on: #50
2025-04-14 18:59:38 +09:00
Ulf Wiger
a949d166f6 Add options for deserialization of missing labels
Some checks failed
Gajumaru Serialization Tests / tests (push) Failing after 49m16s
2025-04-14 11:54:48 +02:00
Ulf Wiger
ba771836fb Document static, make anyint standard
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 49m9s
2025-04-11 16:39:06 +02:00
c403fa89a1 Merge pull request 'Fix dynamic encoding type tag emission. Support 'negint', 'alt', 'items'' (#48) from uw-dynamic-encoding2 into master
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 49m5s
Reviewed-on: #48
2025-04-11 00:01:54 +09:00
Ulf Wiger
dd3e731480 Support 'negint', 'items' and 'alt'
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 49m5s
2025-04-10 16:58:03 +02:00
Ulf Wiger
6563ef9de7 WIP add 'items', fix some layout issues 2025-04-10 16:58:03 +02:00
bff07885fb Merge pull request 'Dynamic serialization using gmser_dyn' (#47) from uw-dynamic-encoding into master
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 48m57s
Reviewed-on: #47
2025-04-07 18:31:05 +09:00
Ulf Wiger
dd1c2455f0 Fix type-driven encode, more docs
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 48m53s
2025-04-05 21:44:36 +02:00
Ulf Wiger
3ede4f22e1 Register individual types, more docs 2025-04-05 13:20:30 +02:00
Ulf Wiger
4663a0f57e gmser_dyn.erl fairly complete
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 48m37s
2025-03-30 23:00:10 +02:00
ac64e01b0f Update runner paths
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in 47m16s
2025-02-04 15:58:21 +09:00
9d2ecc00d3 Update zx dep
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in -4m20s
2025-01-23 20:05:22 +09:00
356d86cd72 Fix dep reference
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in -4m21s
2025-01-23 20:01:15 +09:00
f3da241917 zomp (#46)
Some checks failed
Gajumaru Serialization Tests / tests (push) Failing after -4m25s
Reviewed-on: #46
Co-authored-by: Craig Everett <zxq9@zxq9.com>
Co-committed-by: Craig Everett <zxq9@zxq9.com>
2025-01-23 19:44:58 +09:00
2db9ea6134 Package for zx (#45)
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in -4m22s
This packages the library for deployment on zx (migrating from the old otpr-aeserialization-* line).

It also adds a native Erlang fallback to invokation of the Blake2 algorithm, picking enacl if it is present, but proceeding with eblake2 if not.

Reviewed-on: #45
Reviewed-by: dimitar.p.ivanov <dimitarivanov@qpq.swiss>
Reviewed-by: Ulf Wiger <ulfwiger@qpq.swiss>
Co-authored-by: Craig Everett <zxq9@zxq9.com>
Co-committed-by: Craig Everett <zxq9@zxq9.com>
2025-01-22 19:36:50 +09:00
4b5cfcb67a Remove old type references, update naming, add license notices (#43)
All checks were successful
Gajumaru Serialization Tests / tests (push) Successful in -4m21s
Removed the oracle type references and updated the dependency list to point to git.qpq.swiss.

Reviewed-on: #43
Co-authored-by: Craig Everett <zxq9@zxq9.com>
Co-committed-by: Craig Everett <zxq9@zxq9.com>
2025-01-22 13:22:03 +09:00
29 changed files with 1509 additions and 236 deletions

View File

@ -1,37 +0,0 @@
version: 2.1
executors:
aebuilder:
docker:
- image: aeternity/builder:bionic-otp23
user: builder
working_directory: ~/src
jobs:
build:
executor: aebuilder
steps:
- checkout
- restore_cache:
keys:
- dialyzer-cache-v1-{{ .Branch }}-{{ .Revision }}
- dialyzer-cache-v1-{{ .Branch }}-
- dialyzer-cache-v1-
- run:
name: Build
command: rebar3 compile
- run:
name: Static Analysis
command: rebar3 dialyzer
- run:
name: Eunit
command: rebar3 eunit
- run:
name: Common Tests
command: rebar3 ct
- save_cache:
key: dialyzer-cache-v1-{{ .Branch }}-{{ .Revision }}
paths:
- _build/default/rebar3_20.3.8_plt
- store_artifacts:
path: _build/test/logs

View File

@ -0,0 +1,14 @@
name: Gajumaru Serialization Tests
run-name: ${{ gitea.actor }} testing Gajumaru Serialization
on: [push, workflow_dispatch]
jobs:
tests:
runs-on: linux_amd64
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: eunit
run: |
. /home/act_runner/.erts/27.2.1/activate
./rebar3 eunit

2
.gitignore vendored
View File

@ -7,7 +7,7 @@ _*
*.swp *.swp
*.swo *.swo
.erlang.cookie .erlang.cookie
ebin ebin/*.beam
log log
erl_crash.dump erl_crash.dump
.rebar .rebar

1
Emakefile Normal file
View File

@ -0,0 +1 @@
{"src/*", [debug_info, {i, "include/"}, {outdir, "ebin/"}]}.

View File

@ -1,5 +1,6 @@
ISC License ISC License
Copyright (c) 2025, QPQ AG
Copyright (c) 2017, aeternity developers Copyright (c) 2017, aeternity developers
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any

163
README.md
View File

@ -1,9 +1,162 @@
aeserialization # GM Serialization
=====
Serialization helpers for Aeternity node. Serialization helpers for the Gajumaru.
Build For an overview of the static serializer, see [this document](doc/static.md).
-----
## Build
$ rebar3 compile $ rebar3 compile
## Test
$ rebar3 eunit
## Dynamic encoding
The module `gmser_dyn` offers dynamic encoding support, encoding most 'regular'
Erlang data types into an internal RLP representation.
Main API:
* `encode(term()) -> iolist()`
* `encode_typed(template(), term()) -> iolist()`
* `decode(iolist()) -> term()`
* `serialize(term()) -> binary()`
* `serialize_typed(template(), term()) -> binary()`
* `deserialize(binary()) -> term()`
In the examples below, we use the `decode` functions, to illustrate
how the type information is represented. The fully serialized form is
produced by the `serialize` functions.
The basic types supported by the encoder are:
* `integer()` (`anyint`, code: 246)
* `neg_integer()` (`negint`, code: 247)
* `non_neg_integer()` (`int` , code: 248)
* `binary()` (`binary`, code: 249)
* `boolean()` (`bool` , code: 250)
* `list()` (`list` , code: 251)
* `map()` (`map` , code: 252)
* `tuple()` (`tuple` , code: 253)
* `gmser_id:id()` (`id` , code: 254)
* `atom()` (`label` , code: 255)
(The range of codes is chosen because the `gmser_chain_objects` codes
range from 10 to 200, and also to stay within 1 byte.)
When encoding `map` types, the map elements are first sorted.
When specifying a map type for template-driven encoding, use
the `#{items => [{Key, Value}]}` construct.
## Labels
Labels correspond to (existing) atoms in Erlang.
Decoding of a label results in a call to `binary_to_existing_atom/2`, so will
fail if the corresponding atom does not already exist.
This behavior can be modified using the option `#{missing_labels => fail | create | convert}`,
where `fail` is the default, as described above, `convert` means that missing atoms are
converted to binaries, and `create` means that the atom is created dynamically.
The option can be passed e.g.:
```erlang
gmser_dyn:deserialize(Binary, set_opts(#{missing_labels => convert}))
```
or
```erlang
gmser_dyn:deserialize(Binary, set_opts(#{missing_labels => convert}, Types))
```
By calling `gmser_dyn:register_types/1`, after having added options to the type map,
the options can be made to take effect automatically.
It's possible to cache labels for more compact encoding.
Note that when caching labels, the same cache mapping needs to be used on the
decoder side.
Labels are encoded as `[<<255>>, << AtomToBinary/binary >>]`.
If a cached label is used, the encoding becomes `[<<255>, [Ix]]`, where
`Ix` is the integer-encoded index value of the cached label.
## Examples
Dynamically encoded objects have the basic structure `[<<0>>,V,Obj]`, where `V` is the
integer-coded version, and `Obj` is the top-level encoding on the form `[Tag,Data]`.
```erlang
E = fun(T) -> io:fwrite("~w~n", [gmser_dyn:encode(T)]) end.
E(17) -> [<<0>>,<<1>>,[<<248>>,<<17>>]]
E(<<"abc">>) -> [<<0>>,<<1>>,[<<249>>,<<97,98,99>>]]
E(true) -> [<<0>>,<<1>>,[<<250>>,<<1>>]]
E(false) -> [<<0>>,<<1>>,[<<250>>,<<0>>]]
E([1,2]) -> [<<0>>,<<1>>,[<<251>>,[[<<248>>,<<1>>],[<<248>>,<<2>>]]]]
E({1,2}) -> [<<0>>,<<1>>,[<<253>>,[[<<248>>,<<1>>],[<<248>>,<<2>>]]]]
E(#{a=>1, b=>2}) ->
[<<0>>,<<1>>,[<<252>>,[[[<<255>>,<<97>>],[<<248>>,<<1>>]],[[<<255>>,<<98>>],[<<248>>,<<2>>]]]]]
E(gmser_id:create(account,<<1:256>>)) ->
[<<0>>,<<1>>,[<<254>>,<<1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1>>]]
```
Note that tuples and list are encoded the same way, except for the initial type tag.
Maps are encoded as `[<Map>, [KV1, KV2, ...]]`, where `[KV1, KV2, ...]` is the sorted
list of key-value tuples from `map:to_list(Map)`, but with the `tuple` type tag omitted.
## Template-driven encoding
Templates can be provided to the encoder by either naming an already registered
type, or by passing a template directly. In both cases, the encoder will enforce
the type information in the template.
If the template has been registered, the encoder omits inner type tags (still
inserting the top-level tag), leading to some compression of the output.
This also means that the serialized term cannot be decoded without the same
schema information on the decoder side.
In the case of a directly provided template, all type information is inserted,
such that the serialized term can be decoded without any added type information.
The template types are still enforced during encoding.
```erlang
ET = fun(Type,Term) -> io:fwrite("~w~n", [gmser_dyn:encode_typed(Type,Term)]) end.
ET([{int,int}], [{1,2}]) -> [<<0>>,<<1>>,[<<251>>,[[[<<248>>,<<1>>],[<<248>>,<<2>>]]]]]
gmser_dyn:register_type(1000,lt2i,[{int,int}]).
ET(lt2i, [{1,2}]) -> [<<0>>,<<1>>,[<<3,232>>,[[<<1>>,<<2>>]]]]
```
### Alternative types
The dynamic encoder supports two additions to the `gmserialization` template
language: `any` and `#{alt => [AltTypes]}`.
The `any` type doesn't have an associated code, but enforces dynamic encoding.
The `#{alt => [Type]}` construct also enforces dynamic encoding, and will try
to encode as each type in the list, in the specified order, until one matches.
```erlang
gmser_dyn:encode_typed(#{alt => [negint,int]}, 5) -> [<<0>>,<<1>>,[<<247>>,<<5>>]]
gmser_dyn:encode_typed(#{alt => [negint,int]}, 5) -> [<<0>>,<<1>>,[<<248>>,<<5>>]]
gmser_dyn:encode_typed(anyint,-5) -> [<<0>>,<<1>>,[<<246>>,[<<247>>,<<5>>]]]
gmser_dyn:encode_typed(anyint,5) -> [<<0>>,<<1>>,[<<246>>,[<<248>>,<<5>>]]]
```
### Notes
Note that `anyint` is a standard type. The static serializer supports only
positive integers (`int`), as negative numbers are forbidden on-chain.
For dynamic encoding e.g. in messaging protocols, handling negative numbers can
be useful, so the `negint` type was added as a dynamic type. To encode a full-range
integer, the `alt` construct is needed.
(Floats are not supported, as they are non-deterministic. Rationals and fixed-point
numbers could easily be handled as high-level types, e.g. as `{int,int}`.)

83
doc/static.md Normal file
View File

@ -0,0 +1,83 @@
# Static Serialization
The `gmserialization` and `gmser_chain_objects` modules implement the
static serialization support used in the Gajumaru blockchain.
The purpose is to produce fully deterministic serialization, in order
to maintain predictable hashing.
Example:
```erlang
%% deterministic canonical serialization.
-spec serialize_to_binary(signed_tx()) -> binary_signed_tx().
serialize_to_binary(#signed_tx{tx = Tx, signatures = Sigs}) ->
gmser_chain_objects:serialize(
?SIG_TX_TYPE,
?SIG_TX_VSN,
serialization_template(?SIG_TX_VSN),
[ {signatures, lists:sort(Sigs)}
, {transaction, aetx:serialize_to_binary(Tx)}
]).
-spec deserialize_from_binary(binary()) -> signed_tx().
deserialize_from_binary(SignedTxBin) when is_binary(SignedTxBin) ->
[ {signatures, Sigs}
, {transaction, TxBin}
] = gmser_chain_objects:deserialize(
?SIG_TX_TYPE,
?SIG_TX_VSN,
serialization_template(?SIG_TX_VSN),
SignedTxBin),
assert_sigs_size(Sigs),
#signed_tx{ tx = aetx:deserialize_from_binary(TxBin)
, signatures = Sigs
}.
serialization_template(?SIG_TX_VSN) ->
[ {signatures, [binary]}
, {transaction, binary}
].
```
The terms that can be encoded using these templates are given by
this type in `gmserialization.erl`:
```erlang
-type encodable_term() :: non_neg_integer()
| binary()
| boolean()
| [encodable_term()] %% Of any length
| #{atom() => encodable_term()}
| tuple() %% Any arity, containing encodable_term().
| gmser_id:id().
```
The template 'language' is defined by these types:
```erlang
-type template() :: [{field_name(), type()}].
-type field_name() :: atom().
-type type() :: 'int'
| 'bool'
| 'binary'
| 'id' %% As defined in aec_id.erl
| [type()] %% Length one in the type. This means a list of any length.
| #{items := [{field_name(), type()}]} %% Record with named fields
%% represented as a map.
%% Encoded as a list in the given
%% order.
| tuple(). %% Any arity, containing type(). This means a static size array.
```
The `gmser_chain_objects.erl` module specifies a serialization code for each
object that can go on-chain. E.g.:
```erlang
tag(signed_tx) -> 11;
...
rev_tag(11) -> signed_tx;
```
The `tag` and `vsn` are laid out in the beginning of the serialized object.

11
ebin/gmserialization.app Normal file
View File

@ -0,0 +1,11 @@
{application,gmserialization,
[{description,"Serialization of data for the Gajumaru"},
{vsn,"0.1.2"},
{registered,[]},
{applications,[kernel,stdlib,crypto,base58]},
{env,[]},
{modules,[gmser_api_encoder,gmser_chain_objects,
gmser_contract_code,gmser_delegation,gmser_id,
gmser_rlp,gmserialization]},
{licenses,[]},
{links,[]}]}.

View File

@ -1,4 +1,10 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{deps, [ {base58, {git, "https://github.com/aeternity/erl-base58.git", {ref, "60a3356"}}} {deps,
, {enacl, {git, "https://gitlab.com/ioecs/enacl.git", {ref, "4eb7ec70"}}} [{base58,
]}. {git,
"https://git.qpq.swiss/QPQ-AG/erl-base58.git",
{ref, "e6aa62eeae3d4388311401f06e4b939bf4e94b9c"}}},
{enacl,
{git,
"https://git.qpq.swiss/QPQ-AG/enacl.git",
{ref, "4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}}}]}.

View File

@ -1,8 +1,8 @@
[{<<"base58">>, [{<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git", {git,"https://git.qpq.swiss/QPQ-AG/erl-base58.git",
{ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}}, {ref,"e6aa62eeae3d4388311401f06e4b939bf4e94b9c"}},
0}, 0},
{<<"enacl">>, {<<"enacl">>,
{git,"https://gitlab.com/ioecs/enacl.git", {git,"https://git.qpq.swiss/QPQ-AG/enacl.git",
{ref,"4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}}, {ref,"4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}},
0}]. 0}].

BIN
rebar3 Executable file

Binary file not shown.

View File

@ -1,10 +1,12 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2017, Aeternity Anstalt %%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc %%% @doc
%%% API encoding for the Aeternity node. %%% API encoding for the Gajumaru
%%% @end %%% @end
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeser_api_encoder). -module(gmser_api_encoder).
-vsn("0.1.2").
-export([encode/2, -export([encode/2,
decode/1, decode/1,
@ -27,10 +29,6 @@
| contract_source | contract_source
| transaction | transaction
| tx_hash | tx_hash
| oracle_pubkey
| oracle_query
| oracle_query_id
| oracle_response
| account_pubkey | account_pubkey
| account_seckey | account_seckey
| associate_chain | associate_chain
@ -56,9 +54,9 @@
-define(BASE58, 1). -define(BASE58, 1).
-define(BASE64, 2). -define(BASE64, 2).
-spec encode(known_type(), payload() | aeser_id:id()) -> encoded(). -spec encode(known_type(), payload() | gmser_id:id()) -> encoded().
encode(id_hash, Payload) -> encode(id_hash, Payload) ->
{IdType, Val} = aeser_id:specialize(Payload), {IdType, Val} = gmser_id:specialize(Payload),
encode(id2type(IdType), Val); encode(id2type(IdType), Val);
encode(Type, Payload) -> encode(Type, Payload) ->
Pfx = type2pfx(Type), Pfx = type2pfx(Type),
@ -94,14 +92,14 @@ type_size_check(Type, Bin) ->
end end
end. end.
-spec safe_decode(extended_type(), encoded()) -> {'ok', payload() | aeser_id:id()} -spec safe_decode(extended_type(), encoded()) -> {'ok', payload() | gmser_id:id()}
| {'error', atom()}. | {'error', atom()}.
safe_decode({id_hash, AllowedTypes}, Enc) -> safe_decode({id_hash, AllowedTypes}, Enc) ->
try decode(Enc) of try decode(Enc) of
{ActualType, Dec} -> {ActualType, Dec} ->
case lists:member(ActualType, AllowedTypes) of case lists:member(ActualType, AllowedTypes) of
true -> true ->
try {ok, aeser_id:create(type2id(ActualType), Dec)} try {ok, gmser_id:create(type2id(ActualType), Dec)}
catch error:_ -> {error, invalid_prefix} catch error:_ -> {error, invalid_prefix}
end; end;
false -> false ->
@ -175,8 +173,7 @@ id2type(contract) -> contract_pubkey;
id2type(contract_source) -> contract_source; id2type(contract_source) -> contract_source;
id2type(name) -> name; id2type(name) -> name;
id2type(native_token) -> native_token; id2type(native_token) -> native_token;
id2type(entry) -> entry; id2type(entry) -> entry.
id2type(oracle) -> oracle_pubkey.
type2id(account_pubkey) -> account; type2id(account_pubkey) -> account;
type2id(associate_chain) -> associate_chain; type2id(associate_chain) -> associate_chain;
@ -186,8 +183,7 @@ type2id(contract_pubkey) -> contract;
type2id(contract_source) -> contract_source; type2id(contract_source) -> contract_source;
type2id(name) -> name; type2id(name) -> name;
type2id(native_token) -> native_token; type2id(native_token) -> native_token;
type2id(entry) -> entry; type2id(entry) -> entry.
type2id(oracle_pubkey) -> oracle.
type2enc(key_block_hash) -> ?BASE58; type2enc(key_block_hash) -> ?BASE58;
type2enc(micro_block_hash) -> ?BASE58; type2enc(micro_block_hash) -> ?BASE58;
@ -203,10 +199,6 @@ type2enc(contract_store_value) -> ?BASE64;
type2enc(contract_source) -> ?BASE64; type2enc(contract_source) -> ?BASE64;
type2enc(transaction) -> ?BASE64; type2enc(transaction) -> ?BASE64;
type2enc(tx_hash) -> ?BASE58; type2enc(tx_hash) -> ?BASE58;
type2enc(oracle_pubkey) -> ?BASE58;
type2enc(oracle_query) -> ?BASE64;
type2enc(oracle_query_id) -> ?BASE58;
type2enc(oracle_response) -> ?BASE64;
type2enc(account_pubkey) -> ?BASE58; type2enc(account_pubkey) -> ?BASE58;
type2enc(account_seckey) -> ?BASE58; type2enc(account_seckey) -> ?BASE58;
type2enc(associate_chain) -> ?BASE58; type2enc(associate_chain) -> ?BASE58;
@ -239,10 +231,6 @@ type2pfx(contract_store_value) -> <<"cv">>;
type2pfx(contract_source) -> <<"cx">>; type2pfx(contract_source) -> <<"cx">>;
type2pfx(transaction) -> <<"tx">>; type2pfx(transaction) -> <<"tx">>;
type2pfx(tx_hash) -> <<"th">>; type2pfx(tx_hash) -> <<"th">>;
type2pfx(oracle_pubkey) -> <<"ok">>;
type2pfx(oracle_query) -> <<"ov">>;
type2pfx(oracle_query_id) -> <<"oq">>;
type2pfx(oracle_response) -> <<"or">>;
type2pfx(account_pubkey) -> <<"ak">>; type2pfx(account_pubkey) -> <<"ak">>;
type2pfx(account_seckey) -> <<"sk">>; type2pfx(account_seckey) -> <<"sk">>;
type2pfx(associate_chain) -> <<"ac">>; type2pfx(associate_chain) -> <<"ac">>;
@ -274,10 +262,6 @@ pfx2type(<<"ct">>) -> contract_pubkey;
pfx2type(<<"cx">>) -> contract_source; pfx2type(<<"cx">>) -> contract_source;
pfx2type(<<"tx">>) -> transaction; pfx2type(<<"tx">>) -> transaction;
pfx2type(<<"th">>) -> tx_hash; pfx2type(<<"th">>) -> tx_hash;
pfx2type(<<"ok">>) -> oracle_pubkey;
pfx2type(<<"ov">>) -> oracle_query;
pfx2type(<<"oq">>) -> oracle_query_id;
pfx2type(<<"or">>) -> oracle_response;
pfx2type(<<"ak">>) -> account_pubkey; pfx2type(<<"ak">>) -> account_pubkey;
pfx2type(<<"sk">>) -> account_seckey; pfx2type(<<"sk">>) -> account_seckey;
pfx2type(<<"ac">>) -> associate_chain; pfx2type(<<"ac">>) -> associate_chain;
@ -311,10 +295,6 @@ byte_size_for_type(contract_store_value) -> not_applicable;
byte_size_for_type(contract_source) -> not_applicable; byte_size_for_type(contract_source) -> not_applicable;
byte_size_for_type(transaction) -> not_applicable; byte_size_for_type(transaction) -> not_applicable;
byte_size_for_type(tx_hash) -> 32; byte_size_for_type(tx_hash) -> 32;
byte_size_for_type(oracle_pubkey) -> 32;
byte_size_for_type(oracle_query) -> not_applicable;
byte_size_for_type(oracle_query_id) -> 32;
byte_size_for_type(oracle_response) -> not_applicable;
byte_size_for_type(account_pubkey) -> 32; byte_size_for_type(account_pubkey) -> 32;
byte_size_for_type(account_seckey) -> 32; byte_size_for_type(account_seckey) -> 32;
byte_size_for_type(associate_chain) -> 32; byte_size_for_type(associate_chain) -> 32;

View File

@ -1,20 +1,22 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*- %%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2018, Aeternity Anstalt %%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc %%% @doc
%%% Functions for serializing chain objects to binary format. %%% Functions for serializing chain objects to binary format.
%%% @end %%% @end
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeser_chain_objects). -module(gmser_chain_objects).
-vsn("0.1.2").
-export([ serialize/4 -export([ serialize/4
, deserialize/4 , deserialize/4
, deserialize_type_and_vsn/1 , deserialize_type_and_vsn/1
]). ]).
-type template() :: aeserialization:template(). -type template() :: gmserialization:template().
-type fields() :: aeserialization:fields(). -type fields() :: gmserialization:fields().
%%%=================================================================== %%%===================================================================
%%% API %%% API
@ -22,15 +24,15 @@
-spec serialize(atom(), non_neg_integer(), template(), fields()) -> binary(). -spec serialize(atom(), non_neg_integer(), template(), fields()) -> binary().
serialize(Type, Vsn, Template, Fields) -> serialize(Type, Vsn, Template, Fields) ->
aeserialization:serialize(tag(Type), Vsn, Template, Fields). gmserialization:serialize(tag(Type), Vsn, Template, Fields).
deserialize_type_and_vsn(Binary) -> deserialize_type_and_vsn(Binary) ->
{Tag, Vsn, Fields} = aeserialization:deserialize_tag_and_vsn(Binary), {Tag, Vsn, Fields} = gmserialization:deserialize_tag_and_vsn(Binary),
{rev_tag(Tag), Vsn, Fields}. {rev_tag(Tag), Vsn, Fields}.
-spec deserialize(atom(), non_neg_integer(), template(), binary()) -> fields(). -spec deserialize(atom(), non_neg_integer(), template(), binary()) -> fields().
deserialize(Type, Vsn, Template, Binary) -> deserialize(Type, Vsn, Template, Binary) ->
aeserialization:deserialize(Type, tag(Type), Vsn, Template, Binary). gmserialization:deserialize(Type, tag(Type), Vsn, Template, Binary).
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
@ -40,12 +42,6 @@ tag(account) -> 10;
tag(signed_tx) -> 11; tag(signed_tx) -> 11;
tag(spend_tx) -> 12; tag(spend_tx) -> 12;
tag(data_extend_tx) -> 13; tag(data_extend_tx) -> 13;
tag(oracle) -> 20;
tag(oracle_query) -> 21;
tag(oracle_register_tx) -> 22;
tag(oracle_query_tx) -> 23;
tag(oracle_response_tx) -> 24;
tag(oracle_extend_tx) -> 25;
tag(name) -> 30; tag(name) -> 30;
tag(name_commitment) -> 31; tag(name_commitment) -> 31;
tag(name_claim_tx) -> 32; tag(name_claim_tx) -> 32;
@ -88,7 +84,6 @@ tag(contracts_mtree) -> 621;
tag(calls_mtree) -> 622; tag(calls_mtree) -> 622;
tag(channels_mtree) -> 623; tag(channels_mtree) -> 623;
tag(nameservice_mtree) -> 624; tag(nameservice_mtree) -> 624;
tag(oracles_mtree) -> 625;
tag(accounts_mtree) -> 626; tag(accounts_mtree) -> 626;
tag(acs_mtree) -> 627; tag(acs_mtree) -> 627;
tag(entries_mtree) -> 628; tag(entries_mtree) -> 628;
@ -126,12 +121,6 @@ rev_tag(10) -> account;
rev_tag(11) -> signed_tx; rev_tag(11) -> signed_tx;
rev_tag(12) -> spend_tx; rev_tag(12) -> spend_tx;
rev_tag(13) -> data_extend_tx; rev_tag(13) -> data_extend_tx;
rev_tag(20) -> oracle;
rev_tag(21) -> oracle_query;
rev_tag(22) -> oracle_register_tx;
rev_tag(23) -> oracle_query_tx;
rev_tag(24) -> oracle_response_tx;
rev_tag(25) -> oracle_extend_tx;
rev_tag(30) -> name; rev_tag(30) -> name;
rev_tag(31) -> name_commitment; rev_tag(31) -> name_commitment;
rev_tag(32) -> name_claim_tx; rev_tag(32) -> name_claim_tx;
@ -174,7 +163,6 @@ rev_tag(621) -> contracts_mtree;
rev_tag(622) -> calls_mtree; rev_tag(622) -> calls_mtree;
rev_tag(623) -> channels_mtree; rev_tag(623) -> channels_mtree;
rev_tag(624) -> nameservice_mtree; rev_tag(624) -> nameservice_mtree;
rev_tag(625) -> oracles_mtree;
rev_tag(626) -> accounts_mtree; rev_tag(626) -> accounts_mtree;
rev_tag(627) -> acs_mtree; rev_tag(627) -> acs_mtree;
rev_tag(628) -> entries_mtree; rev_tag(628) -> entries_mtree;

View File

@ -1,12 +1,14 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2019, Aeternity Anstalt %%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc %%% @doc
%%% Serialization of contract code %%% Serialization of contract code
%%% @end %%% @end
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeser_contract_code). -module(gmser_contract_code).
-vsn("0.1.2").
-include("aeser_contract_code.hrl"). -include("gmser_contract_code.hrl").
-export([ deserialize/1 -export([ deserialize/1
, serialize/1 , serialize/1
@ -16,14 +18,15 @@
serialize(CodeMap) -> serialize(CodeMap) ->
serialize(CodeMap, ?SOPHIA_CONTRACT_VSN_3). serialize(CodeMap, ?SOPHIA_CONTRACT_VSN_3).
-spec serialize(map(), non_neg_integer()) -> binary(). -spec serialize(map(), non_neg_integer()) -> binary().
serialize(CodeMap = #{ byte_code := ByteCode serialize(CodeMap = #{ byte_code := ByteCode
, type_info := TypeInfo }, SophiaContractVersion) -> , type_info := TypeInfo }, SophiaContractVersion) ->
%% Source hash %% Source hash
SourceHash = case CodeMap of SourceHash =
case CodeMap of
#{ source_hash := SHash } -> SHash; #{ source_hash := SHash } -> SHash;
#{ contract_source := SrcStr } -> #{ contract_source := SrcStr } -> blake2(32, list_to_binary(SrcStr))
enacl:generichash(32, list_to_binary(SrcStr))
end, end,
%% Compiler version %% Compiler version
@ -42,20 +45,35 @@ serialize(CodeMap = #{ byte_code := ByteCode
|| SophiaContractVersion > ?SOPHIA_CONTRACT_VSN_1 ] ++ || SophiaContractVersion > ?SOPHIA_CONTRACT_VSN_1 ] ++
[ {payable, Payable} [ {payable, Payable}
|| SophiaContractVersion > ?SOPHIA_CONTRACT_VSN_2 ], || SophiaContractVersion > ?SOPHIA_CONTRACT_VSN_2 ],
aeser_chain_objects:serialize(compiler_sophia, gmser_chain_objects:serialize(compiler_sophia,
SophiaContractVersion, SophiaContractVersion,
serialization_template(SophiaContractVersion), serialization_template(SophiaContractVersion),
Fields). Fields).
% NOTE:
% This form significantly favors the presence of enacl and slows fallback
% invokation of eblake2. `try' is really fast; the error throwing machinery
% is comparatively slow. The assumption here is that in cases where you want
% eblake2 performance isn't the problem you're solving (you're probably not
% syncing a new node, for example).
blake2(Size, Bin) ->
try
enacl:generichash(Size, Bin)
catch error:undef ->
{ok, Hash} = eblake2:blake2b(Size, Bin),
Hash
end.
-spec deserialize(binary()) -> map(). -spec deserialize(binary()) -> map().
deserialize(Binary) -> deserialize(Binary) ->
case aeser_chain_objects:deserialize_type_and_vsn(Binary) of case gmser_chain_objects:deserialize_type_and_vsn(Binary) of
{compiler_sophia = Type, ?SOPHIA_CONTRACT_VSN_1 = Vsn, _Rest} -> {compiler_sophia = Type, ?SOPHIA_CONTRACT_VSN_1 = Vsn, _Rest} ->
Template = serialization_template(Vsn), Template = serialization_template(Vsn),
[ {source_hash, Hash} [ {source_hash, Hash}
, {type_info, TypeInfo} , {type_info, TypeInfo}
, {byte_code, ByteCode} , {byte_code, ByteCode}
] = aeser_chain_objects:deserialize(Type, Vsn, Template, Binary), ] = gmser_chain_objects:deserialize(Type, Vsn, Template, Binary),
#{ source_hash => Hash #{ source_hash => Hash
, type_info => TypeInfo , type_info => TypeInfo
, byte_code => ByteCode , byte_code => ByteCode
@ -68,7 +86,7 @@ deserialize(Binary) ->
, {type_info, TypeInfo} , {type_info, TypeInfo}
, {byte_code, ByteCode} , {byte_code, ByteCode}
, {compiler_version, CompilerVersion} , {compiler_version, CompilerVersion}
] = aeser_chain_objects:deserialize(Type, Vsn, Template, Binary), ] = gmser_chain_objects:deserialize(Type, Vsn, Template, Binary),
#{ source_hash => Hash #{ source_hash => Hash
, type_info => TypeInfo , type_info => TypeInfo
, byte_code => ByteCode , byte_code => ByteCode
@ -83,7 +101,7 @@ deserialize(Binary) ->
, {byte_code, ByteCode} , {byte_code, ByteCode}
, {compiler_version, CompilerVersion} , {compiler_version, CompilerVersion}
, {payable, Payable} , {payable, Payable}
] = aeser_chain_objects:deserialize(Type, Vsn, Template, Binary), ] = gmser_chain_objects:deserialize(Type, Vsn, Template, Binary),
#{ source_hash => Hash #{ source_hash => Hash
, type_info => TypeInfo , type_info => TypeInfo
, byte_code => ByteCode , byte_code => ByteCode

View File

@ -1,17 +1,16 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2023, Aeternity Anstalt %%% @copyright (C) 2023, Aeternity Anstalt
%%% @doc %%% @doc
%%% Serialization of delegation signatures %%% Serialization of delegation signatures
%%% @end %%% @end
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeser_delegation). -module(gmser_delegation).
-vsn("0.1.2").
-export([ aens_preclaim_sig/3 -export([ aens_preclaim_sig/3
, aens_name_sig/4 , aens_name_sig/4
, aens_sig/3 , aens_sig/3
, oracle_sig/3
, oracle_response_sig/3
]). ]).
%% Delegation signatures are prefixed with a unique tag, ensuring not to %% Delegation signatures are prefixed with a unique tag, ensuring not to
@ -28,7 +27,7 @@
-type sig_data() :: binary(). -type sig_data() :: binary().
-spec aens_preclaim_sig(binary(), aeser_id:id(), aeser_id:id()) -> sig_data(). -spec aens_preclaim_sig(binary(), gmser_id:id(), gmser_id:id()) -> sig_data().
aens_preclaim_sig(NetworkId, Account, Contract) -> aens_preclaim_sig(NetworkId, Account, Contract) ->
assert_id(account, Account), assert_id(account, Account),
assert_id(contract, Contract), assert_id(contract, Contract),
@ -36,7 +35,7 @@ aens_preclaim_sig(NetworkId, Account, Contract) ->
Fields = [{account, Account}, {contract, Contract}], Fields = [{account, Account}, {contract, Contract}],
serialize(?TYPE_AENS_PRECLAIM, NetworkId, Template, Fields). serialize(?TYPE_AENS_PRECLAIM, NetworkId, Template, Fields).
-spec aens_name_sig(binary(), aeser_id:id(), aeser_id:id(), aeser_id:id()) -> sig_data(). -spec aens_name_sig(binary(), gmser_id:id(), gmser_id:id(), gmser_id:id()) -> sig_data().
aens_name_sig(NetworkId, Account, Name, Contract) -> aens_name_sig(NetworkId, Account, Name, Contract) ->
assert_id(account, Account), assert_id(account, Account),
assert_id(name, Name), assert_id(name, Name),
@ -45,7 +44,7 @@ aens_name_sig(NetworkId, Account, Name, Contract) ->
Fields = [{account, Account}, {name, Name}, {contract, Contract}], Fields = [{account, Account}, {name, Name}, {contract, Contract}],
serialize(?TYPE_AENS_NAME, NetworkId, Template, Fields). serialize(?TYPE_AENS_NAME, NetworkId, Template, Fields).
-spec aens_sig(binary(), aeser_id:id(), aeser_id:id()) -> sig_data(). -spec aens_sig(binary(), gmser_id:id(), gmser_id:id()) -> sig_data().
aens_sig(NetworkId, Account, Contract) -> aens_sig(NetworkId, Account, Contract) ->
assert_id(account, Account), assert_id(account, Account),
assert_id(contract, Contract), assert_id(contract, Contract),
@ -53,29 +52,14 @@ aens_sig(NetworkId, Account, Contract) ->
Fields = [{account, Account}, {contract, Contract}], Fields = [{account, Account}, {contract, Contract}],
serialize(?TYPE_AENS, NetworkId, Template, Fields). serialize(?TYPE_AENS, NetworkId, Template, Fields).
-spec oracle_sig(binary(), aeser_id:id(), aeser_id:id()) -> sig_data().
oracle_sig(NetworkId, Account, Contract) ->
assert_id(account, Account),
assert_id(contract, Contract),
Template = [{account, id}, {contract, id}],
Fields = [{account, Account}, {contract, Contract}],
serialize(?TYPE_ORACLE, NetworkId, Template, Fields).
-spec oracle_response_sig(binary(), aeser_id:id(), aeser_id:id()) -> sig_data().
oracle_response_sig(NetworkId, QueryId, Contract) ->
assert_id(oracle, QueryId),
assert_id(contract, Contract),
Template = [{query, id}, {contract, id}],
Fields = [{query, QueryId}, {contract, Contract}],
serialize(?TYPE_ORACLE_RESPONSE, NetworkId, Template, Fields).
%% ------------------------------------------------------------------------ %% ------------------------------------------------------------------------
%% -- Internal functions %% -- Internal functions
%% ------------------------------------------------------------------------ %% ------------------------------------------------------------------------
serialize(Type, NetworkId, Template, Fields) -> serialize(Type, NetworkId, Template, Fields) ->
Data = aeserialization:serialize(Type, ?VSN, Template, Fields), Data = gmserialization:serialize(Type, ?VSN, Template, Fields),
<<?DELEGATION_TAG:16, NetworkId/binary, Data/binary>>. <<?DELEGATION_TAG:16, NetworkId/binary, Data/binary>>.
assert_id(Type, AeserId) -> assert_id(Type, AeserId) ->
Type = aeser_id:specialize_type(AeserId). Type = gmser_id:specialize_type(AeserId).

996
src/gmser_dyn.erl Normal file
View File

@ -0,0 +1,996 @@
-module(gmser_dyn).
-export([ encode/1 %% (Term) -> rlp()
, encode/2 %% (Term, Types) -> rlp()
, encode/3 %% (Term, Vsn, Types) -> rlp()
, encode_typed/2 %% (Type, Term) -> rlp()
, encode_typed/3 %% (Type, Term, Types) -> rlp()
, encode_typed/4 %% (Type, Term, Vsn, Types) -> rlp()
, decode/1 %% (RLP) -> Term
, decode/2 %% (RLP, Types) -> Term
, decode/3 %% (RLP, Vsn, Types) -> Term
, decode_typed/2 %% (Type, RLP) -> Term
, decode_typed/3 %% (Type, RLP, Types) -> Term
, decode_typed/4 ]). %% (Type, RLP, Vsn, Types) -> Term
-export([ serialize/1 %% (Term) -> Bin
, serialize/2 %% (Term, Types) -> Bin
, serialize/3 %% (Term, Vsn, Types) -> Bin
, serialize_typed/2 %% (Type, Term) -> Bin
, serialize_typed/3 %% (Type, Term, Types) -> Bin
, serialize_typed/4 %% (Type, Term, Vsn, Types) -> Bin
, deserialize/1 %% (Bin) -> Term
, deserialize/2 %% (Bin, Types) -> Term
, deserialize/3 ]). %% (Bin, Vsn, Types) -> Term
%% register a type schema, inspect existing schema
-export([ register_types/1
, registered_types/0
, registered_types/1
, latest_vsn/0
, get_opts/1
, set_opts/1
, set_opts/2
, types_from_list/1
, revert_to_default_types/0
, dynamic_types/0 ]).
%% Register individual types, or cache labels
-export([ register_type/3
, cache_label/2
]).
-import(gmserialization, [ decode_field/2 ]).
-define(VSN, 1).
-include_lib("kernel/include/logger.hrl").
-ifdef(TEST).
-compile([export_all, nowarn_export_all]).
-include_lib("eunit/include/eunit.hrl").
-endif.
serialize(Term) ->
Vsn = latest_vsn(),
rlp_encode(encode(Term, Vsn, registered_types(Vsn))).
serialize(Term, Types0) ->
Types = proper_types(Types0),
Vsn = types_vsn(Types),
rlp_encode(encode(Term, Vsn, Types)).
serialize(Term, Vsn, Types) ->
rlp_encode(encode(Term, Vsn, proper_types(Types, Vsn))).
serialize_typed(Type, Term) ->
Vsn = latest_vsn(),
rlp_encode(encode_typed(Type, Term, Vsn, registered_types(Vsn))).
serialize_typed(Type, Term, Types0) ->
Types = proper_types(Types0),
Vsn = types_vsn(Types),
rlp_encode(encode_typed(Type, Term, Vsn, Types)).
serialize_typed(Type, Term, Vsn, Types) ->
rlp_encode(encode_typed(Type, Term, Vsn, proper_types(Types, Vsn))).
deserialize(Binary) ->
Fields0 = rlp_decode(Binary),
case decode_tag_and_vsn(Fields0) of
{0, Vsn, Fields} ->
decode_(Fields, Vsn, registered_types(Vsn));
Other ->
error({illegal_serialization, Other})
end.
deserialize(Binary, Types0) ->
Types = proper_types(Types0),
Vsn0 = types_vsn(Types),
Fields0 = rlp_decode(Binary),
case decode_tag_and_vsn(Fields0) of
{0, Vsn, Fields} when Vsn0 == undefined; Vsn0 == Vsn ->
decode_(Fields, Vsn, Types);
Other ->
error({illegal_serialization, Other})
end.
deserialize(Binary, Vsn, Types0) ->
Types = proper_types(Types0),
Fields0 = rlp_decode(Binary),
case decode_tag_and_vsn(Fields0) of
{0, Vsn, Fields} ->
decode_(Fields, Vsn, Types);
Other ->
error({illegal_serialization, Other})
end.
encode(Term) ->
Vsn = latest_vsn(),
encode(Term, Vsn, registered_types(Vsn)).
encode(Term, Types0) ->
Types = proper_types(Types0),
encode(Term, types_vsn(Types), Types).
encode(Term, Vsn, Types0) ->
Types = proper_types(Types0, Vsn),
[ encode_basic(int, 0)
, encode_basic(int, Vsn)
, encode_(Term, Vsn, Types) ].
encode_typed(Type, Term) ->
Vsn = latest_vsn(),
encode_typed(Type, Term, Vsn, registered_types(Vsn)).
encode_typed(Type, Term, Types0) ->
Types = proper_types(Types0),
encode_typed(Type, Term, types_vsn(Types), Types).
encode_typed(Type, Term, Vsn, Types0) ->
Types = proper_types(Types0, Vsn),
[ encode_basic(int, 0)
, encode_basic(int, Vsn)
, encode_typed_(Type, Term, Vsn, Types) ].
decode(Fields) ->
Vsn = latest_vsn(),
decode(Fields, Vsn, registered_types(Vsn)).
decode(Fields, Types0) ->
Types = proper_types(Types0),
decode(Fields, types_vsn(Types), Types).
decode(Fields0, Vsn, Types0) ->
Types = proper_types(Types0, Vsn),
case decode_tag_and_vsn(Fields0) of
{0, Vsn, Fields} ->
decode_(Fields, Vsn, Types);
Other ->
error({illegal_encoding, Other})
end.
decode_typed(Type, Fields) ->
Vsn = latest_vsn(),
decode_typed(Type, Fields, Vsn, registered_types(Vsn)).
decode_typed(Type, Fields, Types0) ->
Types = proper_types(Types0),
decode_typed(Type, Fields, types_vsn(Types), Types).
decode_typed(Type, Fields0, Vsn, Types0) ->
Types = proper_types(Types0, Vsn),
case decode_tag_and_vsn(Fields0) of
{0, Vsn, Fields} ->
decode_from_template(Type, Fields, Vsn, Types);
Other ->
error({illegal_encoding, Other})
end.
decode_tag_and_vsn([TagBin, VsnBin, Fields]) ->
{decode_basic(int, TagBin),
decode_basic(int, VsnBin),
Fields}.
proper_types(undefined) ->
registered_types(latest_vsn());
proper_types(#{} = Types) ->
Types.
proper_types(undefined, Vsn) ->
registered_types(Vsn);
proper_types(#{} = Types, Vsn) ->
assert_vsn(Vsn, Types).
types_vsn(#{vsn := Vsn}) -> Vsn;
types_vsn(_) -> latest_vsn().
assert_vsn(V, #{vsn := V} = Types) -> Types;
assert_vsn(V, #{vsn := Other} ) -> error({version_mismatch, V, Other});
assert_vsn(V, #{} = Types ) -> Types#{vsn => V}.
dynamic_types() ->
#{ vsn => ?VSN
, codes =>
#{ 246 => anyint
, 247 => negint
, 248 => int
, 249 => binary
, 250 => bool
, 251 => list
, 252 => map
, 253 => tuple
, 254 => id
, 255 => label}
, rev =>
#{ anyint => 246
, negint => 247
, int => 248
, binary => 249
, bool => 250
, list => 251
, map => 252
, tuple => 253
, id => 254
, label => 255}
, labels => #{}
, rev_labels => #{}
, templates =>
#{ anyint => #{alt => [negint, int]}
, negint => negint
, int => int
, binary => binary
, bool => bool
, list => list
, map => map
, tuple => tuple
, id => id
, label => label
}
, options => #{}
}.
registered_types() ->
registered_types(latest_vsn()).
registered_types(Vsn) ->
case persistent_term:get(pt_key(), undefined) of
undefined ->
dynamic_types();
#{latest_vsn := _, types := #{Vsn := Types}} ->
Types;
#{latest_vsn := _, types := _} ->
dynamic_types()
end.
template(TagOrCode, Vsn, Types) ->
{Tag, Template} = get_template(TagOrCode, Types),
{Tag, dyn_template_(Template, Vsn)}.
get_template(Code, #{codes := Codes, templates := Ts}) when is_integer(Code) ->
Tag = maps:get(Code, Codes),
{Tag, maps:get(Tag, Ts)};
get_template(Tag, #{templates := Ts}) when is_atom(Tag) ->
{Tag, maps:get(Tag, Ts)}.
dyn_template_(F, Vsn) ->
if is_function(F, 0) -> F();
is_function(F, 1) -> F(Vsn);
true -> F
end.
find_cached_label(Lbl, #{labels := Lbls}) ->
maps:find(Lbl, Lbls).
decode_([CodeBin, Flds], Vsn, Types) ->
Code = decode_basic(int, CodeBin),
{_Tag, Template} = template(Code, Vsn, Types),
decode_from_template(Template, Flds, Vsn, Types).
encode_(Term, Vsn, Types) ->
encode_(Term, dyn(emit()), Vsn, Types).
encode_(Term, E, Vsn, Types) ->
{_Tag, Template} = auto_template(Term),
encode_from_template(Template, Term, E, Vsn, Types).
%% To control when to emit type codes:
%% If the template is predefined, it's 'not dynamic' (nodyn(E)).
%% If we are encoding against a type that's part of a predefined template,
%% we typically don't emit the type code, except at the very top.
%% So: emit type codes if the 'emit' bit is set, or if the 'dyn' bit is set.
emit() -> 2#01.
dyn() -> 2#10.
emit(E) -> E bor 2#01.
noemit(E) -> E band 2#10.
dyn(E) -> E bor 2#10.
nodyn(E) -> E band 2#01.
encode_typed_(Type, Term, Vsn, #{codes := Codes, rev := Rev} = Types) ->
case (is_map_key(Type, Codes) orelse is_map_key(Type, Rev)) of
true ->
encode_typed_(Type, Term, nodyn(emit()), Vsn, Types);
false ->
encode_maybe_template(Type, Term, Vsn, Types)
end.
encode_typed_(any, Term, _, Vsn, Types) ->
encode_(Term, dyn(emit()), Vsn, Types);
encode_typed_(Code, Term, E, Vsn, #{codes := Codes} = Types) when is_map_key(Code, Codes) ->
{_Tag, Template} = template(Code, Vsn, Types),
[encode_basic(int,Code),
encode_from_template(Template, Term, noemit(nodyn(E)), Vsn, Types)];
encode_typed_(Tag, Term, E, Vsn, #{templates := Ts, rev := Rev} = Types)
when is_map_key(Tag, Ts) ->
Template = dyn_template_(maps:get(Tag, Ts), Vsn),
Code = maps:get(Tag, Rev),
[encode_basic(int,Code),
encode_from_template(Template, Term, noemit(nodyn(E)), Vsn, Types)];
encode_typed_(MaybeTemplate, Term, _, Vsn, Types) ->
encode_maybe_template(MaybeTemplate, Term, Vsn, Types).
encode_maybe_template(#{items := _} = Type, Term, Vsn, Types) ->
case is_map(Term) of
true ->
encode_from_template(Type, Term, emit(dyn()), Vsn, Types);
false ->
error({invalid, Type, Term})
end;
encode_maybe_template(#{alt := _} = Type, Term, Vsn, Types) ->
encode_from_template(Type, Term, Vsn, emit(dyn()), Types);
encode_maybe_template(#{switch := _} = Type, Term, Vsn, Types) ->
encode_from_template(Type, Term, Vsn, emit(dyn()), Types);
encode_maybe_template(Pat, Term, Vsn, Types) when is_list(Pat);
is_tuple(Pat) ->
encode_from_template(Pat, Term, emit(dyn()), Vsn, Types);
encode_maybe_template(Other, Term, _Vsn, _Types) ->
error({illegal_template, Other, Term}).
auto_template({id,Tag,V}) when Tag == account
; Tag == name
; Tag == commitment
; Tag == contract
; Tag == channel
; Tag == associate_chain
; Tag == entry ->
if is_binary(V) -> {id, id};
true ->
%% close, but no cigar
{tuple, tuple}
end;
auto_template(T) ->
if is_map(T) -> {map, map};
is_list(T) -> {list, list};
is_tuple(T) -> {tuple, tuple};
is_binary(T) -> {binary, binary};
is_boolean(T) -> {bool, bool};
is_atom(T) -> {label, label}; % binary_to_existing_atom()
is_integer(T),
T >= 0 -> {int, int};
is_integer(T),
T < 0 -> {negint, negint};
true ->
error({invalid_type, T})
end.
decode_from_template(any, Fld, Vsn, Types) ->
decode_(Fld, Vsn, Types);
decode_from_template(#{items := Items}, Fld, Vsn, Types) when is_list(Fld) ->
Zipped = lists:zipwith(
fun({{K, T}, V}) -> {K, T, V};
({{opt,K,T}, V}) -> {K, T, V}
end, Items, Fld),
lists:foldl(
fun({K, Type, V}, Map) ->
maps:is_key(K, Map) andalso error(badarg, duplicate_field),
Map#{K => decode_from_template({any,Type}, V, Vsn, Types)}
end, #{}, Zipped);
decode_from_template(#{alt := Alts} = T, Fld, Vsn, Types) when is_list(Alts) ->
decode_alt(Alts, Fld, T, Vsn, Types);
decode_from_template(#{switch := Alts} = T, Fld, Vsn, Types) when is_map(Alts) ->
decode_switch(Alts, Fld, T, Vsn, Types);
decode_from_template(list, Flds, Vsn, Types) ->
[decode_(F, Vsn, Types) || F <- Flds];
decode_from_template(map, Fld, Vsn, Types) ->
TupleFields = [F || F <- Fld],
Items = [decode_from_template({any,any}, T, Vsn, Types)
|| T <- TupleFields],
maps:from_list(Items);
decode_from_template(tuple, Fld, Vsn, Types) ->
Items = [decode_(F, Vsn, Types) || F <- Fld],
list_to_tuple(Items);
decode_from_template([Type], Fields, Vsn, Types) ->
[decode_from_template(Type, F, Vsn, Types)
|| F <- Fields];
decode_from_template(Type, V, Vsn, Types) when is_list(Type), is_list(V) ->
decode_fields(Type, V, Vsn, Types);
decode_from_template(Type, V, Vsn, Types) when is_tuple(Type), is_list(V) ->
Zipped = lists:zip(tuple_to_list(Type), V),
Items = [decode_from_template(T1, V1, Vsn, Types) || {T1, V1} <- Zipped],
list_to_tuple(Items);
decode_from_template(label, [C], _, #{rev_labels := RLbls}) ->
Code = decode_basic(int, C),
maps:get(Code, RLbls);
decode_from_template(Type, Fld, _, Types) when Type == int
; Type == negint
; Type == binary
; Type == bool
; Type == id
; Type == label ->
decode_basic(Type, Fld, Types).
encode_from_template(any, V, _E, Vsn, Types) ->
encode_(V, dyn(emit()), Vsn, Types);
encode_from_template(list, L, E, Vsn, Types) when is_list(L) ->
assert_type(is_list(L), list, L),
emit(E, list, Types,
[encode_(V, Vsn, Types) || V <- L]);
encode_from_template(#{items := Items}, M, E, Vsn, Types) ->
assert_type(is_map(M), map, M),
Emit = noemit(E),
Encode = fun(K, Type, V) ->
[encode_from_template(any, K, Emit, Vsn, Types),
encode_from_template(Type, V, Emit, Vsn, Types)]
end,
emit(E, map, Types,
lists:foldr(
fun({K, Type}, Acc) ->
V = maps:get(K, M),
[Encode(K, Type, V) | Acc];
({opt, K, Type}, Acc) ->
case maps:find(K, M) of
{ok, V} ->
[Encode(K, Type, V) | Acc];
error ->
Acc
end
end, [], Items));
encode_from_template(#{alt := Alts} = T, Term, E, Vsn, Types) when is_list(Alts) ->
encode_alt(Alts, Term, T, E, Vsn, Types);
encode_from_template(#{switch := Alts} = T, Term, E, Vsn, Types) when is_map(Alts),
is_map(Term) ->
encode_switch(Alts, Term, T, E, Vsn, Types);
encode_from_template(map, M, E, Vsn, Types) ->
assert_type(is_map(M), map, M),
Emit = emit(E),
emit(E, map, Types,
[[encode_from_template(any, K, Emit, Vsn, Types),
encode_from_template(any, V, Emit, Vsn, Types)]
|| {K, V} <- lists:sort(maps:to_list(M))]);
encode_from_template(tuple, T, E, Vsn, Types) ->
assert_type(is_tuple(T), tuple, T),
emit(E, tuple, Types,
[encode_(V, noemit(E), Vsn, Types) || V <- tuple_to_list(T)]);
encode_from_template(T, V, E, Vsn, Types) when is_tuple(T) ->
assert_type(is_tuple(V), T, V),
assert_type(tuple_size(T) =:= tuple_size(V), T, V),
Zipped = lists:zip(tuple_to_list(T), tuple_to_list(V)),
emit(E, tuple, Types,
[encode_from_template(T1, V1, noemit(E), Vsn, Types) || {T1, V1} <- Zipped]);
encode_from_template([Type] = T, List, E, Vsn, Types) ->
assert_type(is_list(List), T, List),
emit(E, list, Types,
[encode_from_template(Type, V, noemit(E), Vsn, Types) || V <- List]);
encode_from_template(Type, List, E, Vsn, Types) when is_list(Type), is_list(List) ->
encode_fields(Type, List, E, Vsn, Types);
encode_from_template(label, V, E, _, Types) ->
assert_type(is_atom(V), label, V),
case find_cached_label(V, Types) of
error ->
encode_basic(label, V, E, Types);
{ok, Code} when is_integer(Code) ->
emit(E, label, Types,
[encode_basic(int, Code)])
end;
encode_from_template(Type, V, E, _, Types) when Type == id
; Type == binary
; Type == bool
; Type == int
; Type == negint
; Type == label ->
encode_basic(Type, V, E, Types);
encode_from_template(Type, V, E, Vsn, Types) ->
encode_typed_(Type, V, E, Vsn, Types).
assert_type(true, _, _) -> ok;
assert_type(_, Type, V) -> error({illegal, Type, V}).
decode_alt([A|Alts], Fld, T, Vsn, Types) ->
try decode_from_template(A, Fld, Vsn, Types)
catch error:_ ->
decode_alt(Alts, Fld, T, Vsn, Types)
end;
decode_alt([], Fld, T, _, _) ->
error({illegal, T, Fld}).
encode_alt(Alts, Term, T, E, Vsn, Types) ->
%% Since we don't know which type may match, treat as dynamic.
encode_alt_(Alts, Term, T, dyn(E), Vsn, Types).
encode_alt_([A|Alts], Term, T, E, Vsn, Types) ->
try encode_from_template(A, Term, E, Vsn, Types)
catch error:_ ->
encode_alt_(Alts, Term, T, E, Vsn, Types)
end;
encode_alt_([], Term, T, _, _, _) ->
error({illegal, T, Term}).
decode_switch(Alts, Fld, T, Vsn, Types) ->
[KFld, VFld] = Fld,
Key = decode_(KFld, Vsn, Types),
case maps:find(Key, Alts) of
{ok, SubType} ->
SubTerm = decode_from_template(SubType, VFld, Vsn, Types),
#{Key => SubTerm};
error ->
error({illegal, T, Fld})
end.
encode_switch(Alts, Term, T, E, Vsn, Types) ->
assert_type(map_size(Term) == 1, singleton_map, Term),
[{Key, Subterm}] = maps:to_list(Term),
case maps:find(Key, Alts) of
{ok, SubType} ->
Enc = encode_from_template(SubType, Subterm, E, Vsn, Types),
emit(E, map, Types,
[[encode_from_template(any, Key, E, Vsn, Types),
Enc]]);
error ->
error({illegal, T, Term})
end.
%% Basically, dynamically encoding a statically defined object
encode_fields([{Field, Type}|TypesLeft],
[{Field, Val}|FieldsLeft], E, Vsn, Types) ->
[ encode_from_template(Type, Val, E, Vsn, Types)
| encode_fields(TypesLeft, FieldsLeft, E, Vsn, Types)];
encode_fields([{_Field, _Type} = FT|_TypesLeft],
[Val |_FieldsLeft], _E, _Vsn, _Types) ->
error({illegal_field, FT, Val});
encode_fields([Type|TypesLeft],
[Val |FieldsLeft], E, Vsn, Types) when is_atom(Type) ->
%% Not sure about this ...
[ encode_from_template(Type, Val, E, Vsn, Types)
| encode_fields(TypesLeft, FieldsLeft, E, Vsn, Types)];
encode_fields([], [], _, _, _) ->
[].
decode_fields([{Tag, Type}|TypesLeft],
[Field |FieldsLeft], Vsn, Types) ->
[ {Tag, decode_from_template(Type, Field, Vsn, Types)}
| decode_fields(TypesLeft, FieldsLeft, Vsn, Types)];
decode_fields([], [], _, _) ->
[].
emit(E, Tag, Types, Enc) when E > 0 ->
[emit_code(Tag, Types), Enc];
emit(0, _, _, Enc) ->
Enc.
emit_code(Tag, #{rev := Tags}) ->
encode_basic(int, maps:get(Tag, Tags)).
decode_basic(Type, [Tag,V], #{codes := Codes} = Types) ->
case decode_basic(int, Tag) of
Code when map_get(Code, Codes) == Type ->
decode_basic_(Type, V, Types);
_ ->
error(illegal)
end;
decode_basic(Type, V, Types) ->
decode_basic_(Type, V, Types).
decode_basic_(label, Fld, #{options := #{missing_labels := Opt}}) ->
Bin = decode_basic(binary, Fld),
case Opt of
create -> binary_to_atom(Bin, utf8);
fail -> binary_to_existing_atom(Bin, utf8);
convert ->
try binary_to_existing_atom(Bin, utf8)
catch
error:_ -> Bin
end
end;
decode_basic_(Type, Fld, _) ->
decode_basic(Type, Fld).
decode_basic(label, Fld) ->
binary_to_existing_atom(decode_basic(binary, Fld), utf8);
decode_basic(negint, Fld) ->
I = gmserialization:decode_field(int, Fld),
-I;
decode_basic(Type, Fld) ->
gmserialization:decode_field(Type, Fld).
encode_basic(negint, I, _, Types) when is_integer(I), I < 0 ->
[emit_code(negint, Types), gmserialization:encode_field(int, -I)];
encode_basic(Tag, V, E, Types) when E > 0 ->
[emit_code(Tag, Types), encode_basic(Tag, V)];
encode_basic(Tag, V, 0, _) ->
encode_basic(Tag, V).
encode_basic(label, A) when is_atom(A) ->
encode_basic(binary, atom_to_binary(A, utf8));
encode_basic(Type, Fld) ->
gmserialization:encode_field(Type, Fld).
rlp_decode(Bin) ->
gmser_rlp:decode(Bin).
rlp_encode(Fields) ->
gmser_rlp:encode(Fields).
%% ===========================================================================
%% Type registration and validation code
register_types(Types) when is_map(Types) ->
register_types(latest_vsn(), Types).
register_types(Vsn, Types) ->
Codes = maps:get(codes, Types, #{}),
Rev = rev_codes(Codes),
Templates = maps:get(templates, Types, #{}),
Labels = maps:get(labels, Types, #{}),
Options = maps:get(options, Types, #{}),
#{codes := Codes0, rev := Rev0, labels := Labels0,
templates := Templates0, options := Options0} =
dynamic_types(),
Merged = #{ codes => maps:merge(Codes0, Codes)
, rev => maps:merge(Rev0, Rev)
, templates => maps:merge(Templates0, Templates)
, options => maps:merge(Options0, Options)
, labels => maps:merge(Labels0, Labels) },
assert_sizes(Merged),
assert_mappings(Merged),
Merged1 = assert_label_cache(Merged),
put_types(Vsn, Merged1).
latest_vsn() ->
case persistent_term:get(pt_key(), undefined) of
undefined -> ?VSN;
#{latest_vsn := V} ->
V
end.
pt_key() -> {?MODULE, types}.
put_types(Types) ->
put_types(types_vsn(Types), Types).
put_types(V, Types) ->
K = pt_key(),
Old = case persistent_term:get(K, undefined) of
undefined -> default_types_pt();
Existing -> Existing
end,
put_types_(K, V, Types, Old).
put_types_(K, V, Types, #{latest_vsn := V0, types := Types0} = Old) ->
New = case V > V0 of
true ->
Old#{latest_vsn := V,
types := Types0#{V => Types#{vsn => V}}};
false ->
Old#{types := Types0#{V => Types#{vsn => V}}}
end,
persistent_term:put(K, New).
types_from_list(L) ->
types_from_list(L, registered_types()).
types_from_list(L, Types) ->
gmser_dyn_types:from_list(L, Types).
register_type(Code, Tag, Template) ->
register_type(latest_vsn(), Code, Tag, Template).
register_type(Vsn, Code, Tag, Template) when is_integer(Code), Code >= 0 ->
#{codes := Codes, rev := Rev, templates := Temps} = Types = registered_types(Vsn),
case {is_map_key(Code, Codes), is_map_key(Tag, Rev)} of
{false, false} ->
New = Types#{ codes := Codes#{Code => Tag}
, rev := Rev#{Tag => Code}
, templates := Temps#{Tag => Template} },
put_types(New),
New;
{true, _} -> error(code_exists);
{_, true} -> error(tag_exists)
end.
set_opts(Opts) ->
set_opts(Opts, registered_types()).
set_opts(Opts, Types) ->
Types#{options => Opts}.
get_opts(#{options := Opts}) ->
Opts.
cache_label(Code, Label) when is_integer(Code), Code >= 0, is_atom(Label) ->
#{labels := Lbls, rev_labels := RevLbls} = Types = registered_types(),
case {is_map_key(Label, Lbls), is_map_key(Code, RevLbls)} of
{false, false} ->
New = Types#{ labels := Lbls#{Label => Code}
, rev_labels := RevLbls#{Code => Label} },
put_types(New),
New;
{true,_} -> error(label_exists);
{_,true} -> error(code_exists)
end.
revert_to_default_types() ->
persistent_term:put(pt_key(), default_types_pt()).
default_types_pt() ->
#{latest_vsn => ?VSN, types => #{?VSN => dynamic_types()}}.
assert_sizes(#{codes := Codes, rev := Rev, templates := Ts} = Types) ->
assert_sizes(map_size(Codes), map_size(Rev), map_size(Ts), Types).
assert_sizes(Sz, Sz, Sz, _) ->
ok;
assert_sizes(Sz, RSz, Sz, Types) when RSz =/= Sz ->
%% Wrong size reverse mapping must mean duplicate mappings
%% We auto-generate the reverse-mappings, so we know there aren't
%% too many of them
?LOG_ERROR("Reverse mapping size doesn't match codes size", []),
Codes = maps:get(codes, Types),
CodeVals = maps:values(Codes),
Duplicates = CodeVals -- lists:usort(CodeVals),
error({duplicate_mappings, Duplicates, Types});
assert_sizes(Sz, _, TSz, Types) when Sz > TSz ->
?LOG_ERROR("More codes than templates", []),
Tags = maps:keys(maps:get(rev, Types)),
Templates = maps:get(templates, Types),
Missing = [T || T <- Tags,
not is_map_key(T, Templates)],
error({missing_mappings, Missing, Types});
assert_sizes(Sz, _, TSz, Types) when TSz > Sz ->
%% More mappings than codes. May not be horrible.
%% We check that all codes have mappings elsewhere.
?LOG_WARNING("More templates than codes in ~p", [Types]),
ok.
assert_mappings(#{rev := Rev, templates := Ts} = Types) ->
Tags = maps:keys(Rev),
case [T || T <- Tags,
not is_map_key(T, Ts)] of
[] ->
ok;
Missing ->
?LOG_ERROR("Missing templates for ~p", [Missing]),
error({missing_templates, Missing, Types})
end.
assert_label_cache(#{labels := Labels} = Types) ->
Ls = maps:keys(Labels),
case [L || L <- Ls, not is_atom(L)] of
[] -> ok;
_NonAtoms ->
error(non_atoms_in_label_cache)
end,
Rev = [{C,L} || {L,C} <- maps:to_list(Labels)],
case [C || {C,_} <- Rev, not is_integer(C)] of
[] -> ok;
_NonInts -> error(non_integer_label_cache_codes)
end,
RevLabels = maps:from_list(Rev),
case map_size(RevLabels) == map_size(Labels) of
true ->
Types#{rev_labels => RevLabels};
false ->
error(non_unique_label_cache_codes)
end.
rev_codes(Codes) ->
L = maps:to_list(Codes),
maps:from_list([{V, K} || {K, V} <- L]).
%% ===========================================================================
%% Unit tests
-ifdef(TEST).
trace() ->
dbg:tracer(),
dbg:tpl(?MODULE, x),
dbg:p(all, [c]).
notrace() ->
dbg:ctpl('_'),
dbg:stop().
round_trip_test_() ->
[?_test(t_round_trip(T)) ||
T <- t_sample_types()
].
ser_round_trip_test_() ->
[?_test(t_ser_round_trip(T)) ||
T <- t_sample_types()
].
t_sample_types() ->
[ 5
, -5
, <<"a">>
, [1,2,3]
, {<<"a">>,1}
, #{<<"a">> => 1}
, [#{1 => <<"c">>, [17] => true}]
, true
].
user_types_test_() ->
{foreach,
fun() ->
revert_to_default_types()
end,
fun(_) ->
revert_to_default_types()
end,
[ ?_test(t_reg_typed_tuple())
, ?_test(t_reg_chain_objects_array())
, ?_test(t_reg_template_fun())
, ?_test(t_reg_template_vsnd_fun())
, ?_test(t_reg_label_cache())
, ?_test(t_reg_label_cache2())
, ?_test(t_reg_options())
]}.
dynamic_types_test_() ->
[ ?_test(revert_to_default_types())
, ?_test(t_typed_map())
, ?_test(t_alts())
, ?_test(t_switch())
, ?_test(t_anyints())
, ?_test(t_missing_labels())
].
versioned_types_test_() ->
[ ?_test(t_new_version())
].
t_round_trip(T) ->
?debugVal(T),
?assertMatch({T, T}, {T, decode(encode(T))}).
t_ser_round_trip(T) ->
Data = serialize(T),
?debugFmt("Data (~p) = ~p~n", [T, Data]),
?assertMatch({T, T}, {T, deserialize(Data)}).
t_round_trip_typed(Type, T) ->
?debugVal(T),
?assertMatch({T, T}, {T, decode(encode_typed(Type, T))}).
t_reg_typed_tuple() ->
Type = {int, int, int},
MyTypes = #{ codes => #{ 1001 => int_tup3 }
, templates => #{ int_tup3 => Type }
},
register_types(MyTypes),
GoodTerm = {2,3,4},
?debugFmt("Type: ~p, GoodTerm = ~p", [Type, GoodTerm]),
Enc = encode_typed(int_tup3, GoodTerm),
GoodTerm = decode(Enc),
t_bad_typed_encode(int_tup3, {1,2,<<"a">>}, {illegal,int,<<"a">>}),
t_bad_typed_encode(int_tup3, {1,2,3,4}, {illegal, {int,int,int}, {1,2,3,4}}).
t_bad_typed_encode(Type, Term, Error) ->
try encode_typed(Type, Term),
error({expected_error, Error})
catch
error:Error ->
ok
end.
t_reg_chain_objects_array() ->
Template = [{foo, {int, binary}}, {bar, [{int, int}]}, {baz, {int}}],
?debugFmt("Template = ~p", [Template]),
MyTypes = #{ codes => #{ 1002 => coa }
, templates => #{ coa => Template } },
register_types(MyTypes),
Values = [{foo, {1, <<"foo">>}}, {bar, [{1, 2}, {3, 4}, {5, 6}]}, {baz, {1}}],
?debugFmt("Values = ~p", [Values]),
Enc = encode_typed(coa, Values),
Values = decode(Enc).
t_reg_template_fun() ->
Template = fun() -> {int,int} end,
New = register_type(1010, tup2f0, Template),
?debugFmt("New = ~p", [New]),
E = encode_typed(tup2f0, {3,4}),
{3,4} = decode(E),
ok.
t_reg_template_vsnd_fun() ->
Template = fun(1) -> {int,int} end,
New = register_type(1011, tup2f1, Template),
?debugFmt("New = ~p", [New]),
E = encode_typed(tup2f1, {3,4}),
{3,4} = decode(E),
ok.
t_reg_label_cache() ->
Enc0 = gmser_dyn:encode('1'),
?debugFmt("Enc0 (no cache): ~w", [Enc0]),
MyTypes1 = #{codes => #{1003 => lbl_tup2}, templates => #{ lbl_tup2 => {label,label} }},
register_types(MyTypes1),
Enc0a = gmser_dyn:encode_typed(lbl_tup2, {'1','1'}),
?debugFmt("Enc0a (no cache): ~w", [Enc0a]),
{'1','1'} = gmser_dyn:decode(Enc0a),
MyTypes2 = MyTypes1#{labels => #{'1' => 49}}, % atom_to_list('1') == [49]
register_types(MyTypes2),
Enc1 = gmser_dyn:encode('1'),
Enc1a = gmser_dyn:encode_typed(lbl_tup2, {'1','1'}),
?debugFmt("Enc1 (w/ cache): ~w", [Enc1]),
?debugFmt("Enc1a (w/ cache): ~w", [Enc1a]),
{'1','1'} = gmser_dyn:decode(Enc1a),
true = Enc0 =/= Enc1,
Enc2 = gmser_dyn:encode_typed(label, '1'),
?debugFmt("Enc2 (typed): ~w", [Enc2]),
?assertEqual(Enc2, Enc1),
?assertNotEqual(Enc0a, Enc1a).
t_reg_label_cache2() ->
TFromL = types_from_list(
[ {lbl_tup2, 1003, {label, label}}
, {labels,
[{'1', 49}]}
]),
?debugFmt("TFromL = ~w", [TFromL]),
register_types(TFromL),
Tup = {'1', '1'},
Enc = gmser_dyn:encode_typed(lbl_tup2, Tup),
[<<0>>,<<1>>,[<<3,235>>,[[<<49>>],[<<49>>]]]] = Enc,
_Tup = gmser_dyn:decode(Enc).
t_reg_options() ->
register_types(set_opts(#{missing_labels => convert})),
[Dyn,Vsn,[Am,<<"random">>]] = gmser_dyn:encode(random),
EncNewAm = [Dyn,Vsn,[Am,<<"foo12345">>]],
<<"foo12345">> = gmser_dyn:decode(EncNewAm),
ok.
t_typed_map() ->
Term = #{a => 13, {key,1} => [a]},
Items = [{a,int},{{key,1},[label]}],
OptItems = [{opt, b, int} | Items],
Enc = encode_typed(#{items => Items}, Term),
?assertEqual(Term, decode(Enc)),
?assertEqual(Enc, encode_typed(#{items => Items}, Term)),
?assertEqual(Enc, encode_typed(#{items => OptItems}, Term)),
Term1 = Term#{b => 4},
Enc1 = encode_typed(#{items => OptItems}, Term1),
?assertEqual(Term1, decode(Enc1)),
?assertEqual(Enc, encode_typed(#{items => Items}, Term1)).
t_alts() ->
t_round_trip_typed(#{alt => [negint, int]}, -4),
t_round_trip_typed(#{alt => [negint, int]}, 4),
ok.
t_switch() ->
T = #{switch => #{a => int, b => binary}},
t_round_trip_typed(T, #{a => 17}),
t_round_trip_typed(T, #{b => <<"foo">>}),
?assertError({illegal,int,<<"foo">>}, encode_typed(T, #{a => <<"foo">>})),
MMap = #{a => 17, b => <<"foo">>},
?assertError({illegal, singleton_map, MMap}, encode_typed(T, MMap)).
t_anyints() ->
t_round_trip_typed(anyint, -5),
t_round_trip_typed(anyint, 5),
ok.
t_missing_labels() ->
[Dyn,Vsn,[Am,<<"random">>]] = gmser_dyn:encode(random),
EncNewAm = [Dyn,Vsn,[Am,<<"flurbee">>]],
?assertError(badarg, gmser_dyn:decode(EncNewAm)),
?assertError(badarg, gmser_dyn:decode(EncNewAm, set_opts(#{missing_labels => fail}))),
<<"flurbee">> = gmser_dyn:decode(EncNewAm, set_opts(#{missing_labels => convert})),
true = is_atom(gmser_dyn:decode(EncNewAm, set_opts(#{missing_labels => create}))),
ok.
t_new_version() ->
V = latest_vsn(),
Types0 = registered_types(V),
V1 = V+1,
Types1 = types_from_list([{vsn, V1},
{msg1, 300, {int, int}}], Types0),
T2 = {3,5},
Enc21 = encode_typed(msg1, T2, Types1),
T2 = decode(Enc21, Types1),
V2 = V1+1,
Types2 = types_from_list([{vsn, V2},
{modify, {msg1, {int, int, int}}}], Types1),
Enc21 = encode_typed(msg1, T2, Types1),
?assertError({illegal,{int,int,int},T2}, encode_typed(msg1, T2, Types2)),
T3 = {3,5,7},
Enc32 = encode_typed(msg1, T3, Types2),
T3 = decode(Enc32, Types2).
-endif.

62
src/gmser_dyn_types.erl Normal file
View File

@ -0,0 +1,62 @@
-module(gmser_dyn_types).
-export([ add_type/4
, from_list/2
, expand/1 ]).
-export([ next_code/1 ]).
next_code(#{codes := Codes}) ->
lists:max(maps:keys(Codes)) + 1.
add_type(Tag, Code, Template, Types) ->
elem_to_type({Tag, Code, Template}, Types).
from_list(L, Types) ->
lists:foldl(fun elem_to_type/2, Types, L).
expand(#{vsn := V, templates := Templates0} = Types) ->
Templates =
maps:map(
fun(_, F) when is_function(F, 0) ->
F();
(_, F) when is_function(F, 1) ->
F(V);
(_, T) ->
T
end, Templates0),
Types#{templates := Templates}.
elem_to_type({Tag, Code, Template}, Acc) when is_atom(Tag), is_integer(Code) ->
#{codes := Codes, rev := Rev, templates := Temps} = Acc,
case {is_map_key(Tag, Rev), is_map_key(Code, Codes)} of
{false, false} ->
Acc#{ codes := Codes#{Code => Tag}
, rev := Rev#{Tag => Code}
, templates => Temps#{Tag => Template}
};
{true, _} -> error({duplicate_tag, Tag});
{_, true} -> error({duplicate_code, Code})
end;
elem_to_type({modify, {Tag, Template}}, Acc) ->
#{codes := _, rev := Rev, templates := Templates} = Acc,
_ = maps:get(Tag, Rev),
Templates1 = Templates#{Tag := Template},
Acc#{templates := Templates1};
elem_to_type({labels, Lbls}, Acc) ->
lists:foldl(fun add_label/2, Acc, Lbls);
elem_to_type({vsn, V}, Acc) ->
Acc#{vsn => V};
elem_to_type(Elem, _) ->
error({invalid_type, Elem}).
add_label({L, Code}, #{labels := Lbls, rev_labels := RevLbls} = Acc)
when is_atom(L), is_integer(Code), Code > 0 ->
case {is_map_key(L, Lbls), is_map_key(Code, RevLbls)} of
{false, false} ->
Acc#{labels := Lbls#{L => Code},
rev_labels := RevLbls#{Code => L}};
{true, _} -> error({duplicate_label, L});
{_, true} -> error({duplicate_label_code, Code})
end;
add_label(Elem, _) ->
error({invalid_label, Elem}).

View File

@ -1,12 +1,14 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*- %%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2018, Aeternity Anstalt %%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc %%% @doc
%%% ADT for identifiers %%% ADT for identifiers
%%% @end %%% @end
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeser_id). -module(gmser_id).
-vsn("0.1.2").
-export([ create/2 -export([ create/2
, specialize/1 , specialize/1
@ -24,7 +26,7 @@
, val , val
}). }).
-type tag() :: 'account' | 'oracle' | 'name' -type tag() :: 'account' | 'name'
| 'commitment' | 'contract' | 'channel' | 'commitment' | 'contract' | 'channel'
| 'associate_chain' | 'entry' . | 'associate_chain' | 'entry' .
-type val() :: <<_:256>>. -type val() :: <<_:256>>.
@ -40,7 +42,6 @@
-define(SERIALIZED_SIZE, 33). %% ?TAG_SIZE + ?PUB_SIZE -define(SERIALIZED_SIZE, 33). %% ?TAG_SIZE + ?PUB_SIZE
-define(IS_TAG(___TAG___), ___TAG___ =:= account; -define(IS_TAG(___TAG___), ___TAG___ =:= account;
___TAG___ =:= oracle;
___TAG___ =:= name; ___TAG___ =:= name;
___TAG___ =:= commitment; ___TAG___ =:= commitment;
___TAG___ =:= contract; ___TAG___ =:= contract;
@ -100,7 +101,6 @@ decode(<<Tag:?TAG_SIZE/unit:8, Val:?PUB_SIZE/binary>>) ->
encode_tag(account) -> 1; encode_tag(account) -> 1;
encode_tag(name) -> 2; encode_tag(name) -> 2;
encode_tag(commitment) -> 3; encode_tag(commitment) -> 3;
encode_tag(oracle) -> 4;
encode_tag(contract) -> 5; encode_tag(contract) -> 5;
encode_tag(channel) -> 6; encode_tag(channel) -> 6;
encode_tag(associate_chain) -> 7; encode_tag(associate_chain) -> 7;
@ -111,7 +111,6 @@ encode_tag(Other) -> error({illegal_id_tag_name, Other}).
decode_tag(1) -> account; decode_tag(1) -> account;
decode_tag(2) -> name; decode_tag(2) -> name;
decode_tag(3) -> commitment; decode_tag(3) -> commitment;
decode_tag(4) -> oracle;
decode_tag(5) -> contract; decode_tag(5) -> contract;
decode_tag(6) -> channel; decode_tag(6) -> channel;
decode_tag(7) -> associate_chain; decode_tag(7) -> associate_chain;

View File

@ -1,14 +1,18 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2017, Aeternity Anstalt %%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc %%% @doc
%%% Implementation of the Recursive Length Prefix. %%% Implementation of the Recursive Length Prefix.
%%% %%%
%%% https://zxq9.com/archives/2749
%%% https://github.com/ethereum/wiki/wiki/RLP %%% https://github.com/ethereum/wiki/wiki/RLP
%%% %%%
%%% @end %%% @end
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeser_rlp). -module(gmser_rlp).
-vsn("0.1.2").
-export([ decode/1 -export([ decode/1
, decode_one/1 , decode_one/1
, encode/1 , encode/1

View File

@ -1,5 +1,5 @@
{application, aeserialization, {application, gmserialization,
[{description, "Serialization of data for Aeternity"}, [{description, "Serialization of data for the Gajumaru"},
{vsn, "0.1.0"}, {vsn, "0.1.0"},
{registered, []}, {registered, []},
{applications, {applications,

View File

@ -1,16 +1,20 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2018, Aeternity Anstalt %%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc %%% @doc
%%% Functions for serializing generic objects to/from binary format. %%% Functions for serializing generic objects to/from binary format.
%%% @end %%% @end
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeserialization). -module(gmserialization).
-vsn("0.1.2").
-export([ decode_fields/2 -export([ decode_fields/2
, decode_field/2
, deserialize/5 , deserialize/5
, deserialize_tag_and_vsn/1 , deserialize_tag_and_vsn/1
, encode_fields/2 , encode_fields/2
, encode_field/2
, serialize/4 ]). , serialize/4 ]).
%%%=================================================================== %%%===================================================================
@ -21,6 +25,8 @@
, fields/0 , fields/0
]). ]).
-export_type([ encodable_term/0 ]).
-type template() :: [{field_name(), type()}]. -type template() :: [{field_name(), type()}].
-type field_name() :: atom(). -type field_name() :: atom().
-type type() :: 'int' -type type() :: 'int'
@ -37,7 +43,7 @@
| [encodable_term()] %% Of any length | [encodable_term()] %% Of any length
| #{atom() => encodable_term()} | #{atom() => encodable_term()}
| tuple() %% Any arity, containing encodable_term(). | tuple() %% Any arity, containing encodable_term().
| aeser_id:id(). | gmser_id:id().
-type fields() :: [{field_name(), encodable_term()}]. -type fields() :: [{field_name(), encodable_term()}].
@ -49,13 +55,13 @@
serialize(Tag, Vsn, Template, Fields) -> serialize(Tag, Vsn, Template, Fields) ->
List = encode_fields([{tag, int}, {vsn, int}|Template], List = encode_fields([{tag, int}, {vsn, int}|Template],
[{tag, Tag}, {vsn, Vsn}|Fields]), [{tag, Tag}, {vsn, Vsn}|Fields]),
aeser_rlp:encode(List). gmser_rlp:encode(List).
%% Type isn't strictly necessary, but will give a better error reason %% Type isn't strictly necessary, but will give a better error reason
-spec deserialize(atom(), non_neg_integer(), non_neg_integer(), -spec deserialize(atom(), non_neg_integer(), non_neg_integer(),
template(), binary()) -> fields(). template(), binary()) -> fields().
deserialize(Type, Tag, Vsn, Template0, Binary) -> deserialize(Type, Tag, Vsn, Template0, Binary) ->
Decoded = aeser_rlp:decode(Binary), Decoded = gmser_rlp:decode(Binary),
Template = [{tag, int}, {vsn, int}|Template0], Template = [{tag, int}, {vsn, int}|Template0],
case decode_fields(Template, Decoded) of case decode_fields(Template, Decoded) of
[{tag, Tag}, {vsn, Vsn}|Left] -> [{tag, Tag}, {vsn, Vsn}|Left] ->
@ -68,7 +74,7 @@ deserialize(Type, Tag, Vsn, Template0, Binary) ->
-spec deserialize_tag_and_vsn(binary()) -> -spec deserialize_tag_and_vsn(binary()) ->
{non_neg_integer(), non_neg_integer(), fields()}. {non_neg_integer(), non_neg_integer(), fields()}.
deserialize_tag_and_vsn(Binary) -> deserialize_tag_and_vsn(Binary) ->
[TagBin, VsnBin|Fields] = aeser_rlp:decode(Binary), [TagBin, VsnBin|Fields] = gmser_rlp:decode(Binary),
Template = [{tag, int}, {vsn, int}], Template = [{tag, int}, {vsn, int}],
[{tag, Tag}, {vsn, Vsn}] = decode_fields(Template, [TagBin, VsnBin]), [{tag, Tag}, {vsn, Vsn}] = decode_fields(Template, [TagBin, VsnBin]),
{Tag, Vsn, Fields}. {Tag, Vsn, Fields}.
@ -118,7 +124,7 @@ encode_field(binary, X) when is_binary(X) -> X;
encode_field(bool, true) -> <<1:8>>; encode_field(bool, true) -> <<1:8>>;
encode_field(bool, false) -> <<0:8>>; encode_field(bool, false) -> <<0:8>>;
encode_field(id, Val) -> encode_field(id, Val) ->
try aeser_id:encode(Val) try gmser_id:encode(Val)
catch _:_ -> error({illegal, id, Val}) catch _:_ -> error({illegal, id, Val})
end; end;
encode_field(Type, Val) -> error({illegal, Type, Val}). encode_field(Type, Val) -> error({illegal, Type, Val}).
@ -142,7 +148,7 @@ decode_field(binary, X) when is_binary(X) -> X;
decode_field(bool, <<1:8>>) -> true; decode_field(bool, <<1:8>>) -> true;
decode_field(bool, <<0:8>>) -> false; decode_field(bool, <<0:8>>) -> false;
decode_field(id, Val) -> decode_field(id, Val) ->
try aeser_id:decode(Val) try gmser_id:decode(Val)
catch _:_ -> error({illegal, id, Val}) catch _:_ -> error({illegal, id, Val})
end; end;
decode_field(Type, X) -> error({illegal, Type, X}). decode_field(Type, X) -> error({illegal, Type, X}).

View File

@ -1,67 +0,0 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2023, Aeternity Anstalt
%%%-------------------------------------------------------------------
-module(aeser_delegation_tests).
-include_lib("eunit/include/eunit.hrl").
-define(TEST_MODULE, aeser_delegation).
-define(ACCOUNT, aeser_id:create(account, <<1:256>>)).
-define(CONTRACT, aeser_id:create(contract, <<2:256>>)).
-define(NAME, aeser_id:create(name, <<3:256>>)).
-define(ORACLE, aeser_id:create(oracle, <<3:256>>)).
-define(NETWORK_ID, <<"my_fancy_network"/utf8>>).
encode_correct_test_() ->
[{"Encode preclaim sig",
fun() ->
aeser_delegation:aens_preclaim_sig(?NETWORK_ID, ?ACCOUNT, ?CONTRACT)
end},
{"Encode name sig",
fun() ->
aeser_delegation:aens_name_sig(?NETWORK_ID, ?ACCOUNT, ?NAME, ?CONTRACT)
end},
{"Encode aens wildcard sig",
fun() ->
aeser_delegation:aens_sig(?NETWORK_ID, ?ACCOUNT, ?CONTRACT)
end},
{"Encode oracle sig",
fun() ->
aeser_delegation:oracle_sig(?NETWORK_ID, ?ACCOUNT, ?CONTRACT)
end},
{"Encode oracle response sig",
fun() ->
aeser_delegation:oracle_response_sig(?NETWORK_ID, ?ORACLE, ?CONTRACT)
end}
].
encode_fail_test_() ->
[{"Bad encoding preclaim sig",
fun() ->
?assertError(_, aeser_delegation:aens_preclaim_sig(?NETWORK_ID, <<42:256>>, ?CONTRACT)),
?assertError(_, aeser_delegation:aens_preclaim_sig(?NETWORK_ID, ?CONTRACT, ?ACCOUNT))
end},
{"Bad encoding name sig",
fun() ->
?assertError(_, aeser_delegation:aens_name_sig(?NETWORK_ID, ?ACCOUNT, <<42:256>>, ?CONTRACT)),
?assertError(_, aeser_delegation:aens_name_sig(?NETWORK_ID, ?NAME, ?ACCOUNT, ?CONTRACT))
end},
{"Bad encoding aens wildcard sig",
fun() ->
?assertError(_, aeser_delegation:aens_sig(?NETWORK_ID, ?ACCOUNT, <<42:256>>)),
?assertError(_, aeser_delegation:aens_sig(?NETWORK_ID, ?CONTRACT, ?CONTRACT))
end},
{"Bad encoding oracle sig",
fun() ->
?assertError(_, aeser_delegation:oracle_sig(?NETWORK_ID, <<42:256>>, ?CONTRACT)),
?assertError(_, aeser_delegation:oracle_sig(?NETWORK_ID, ?ACCOUNT, ?ACCOUNT))
end},
{"Bad encoding oracle response sig",
fun() ->
?assertError(_, aeser_delegation:oracle_response_sig(?NETWORK_ID, <<42:256>>, ?CONTRACT)),
?assertError(_, aeser_delegation:oracle_response_sig(?NETWORK_ID, ?ORACLE, ?ORACLE))
end}
].

View File

@ -1,12 +1,13 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2018, Aeternity Anstalt %%% @copyright (C) 2018, Aeternity Anstalt
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeser_api_encoder_tests). -module(gmser_api_encoder_tests).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-define(TEST_MODULE, aeser_api_encoder). -define(TEST_MODULE, gmser_api_encoder).
-define(TYPES, [ {key_block_hash , 32} -define(TYPES, [ {key_block_hash , 32}
, {micro_block_hash , 32} , {micro_block_hash , 32}
, {block_tx_hash , 32} , {block_tx_hash , 32}
@ -15,8 +16,6 @@
, {contract_pubkey , 32} , {contract_pubkey , 32}
, {transaction , not_applicable} , {transaction , not_applicable}
, {tx_hash , 32} , {tx_hash , 32}
, {oracle_pubkey , 32}
, {oracle_query_id , 32}
, {account_pubkey , 32} , {account_pubkey , 32}
, {signature , 64} , {signature , 64}
, {name , not_applicable} , {name , not_applicable}

View File

@ -1,11 +1,12 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*- %%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2018, Aeternity Anstalt %%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc %%% @doc
%%% EUnit tests for aeser_chain_objects %%% EUnit tests for gmser_chain_objects
%%% @end %%% @end
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeser_chain_objects_tests). -module(gmser_chain_objects_tests).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
@ -78,10 +79,10 @@ deserialize(Template, Bin) ->
deserialize(Template, Bin, ?DEFAULT_TAG, ?DEFAULT_VERSION). deserialize(Template, Bin, ?DEFAULT_TAG, ?DEFAULT_VERSION).
deserialize(Template, Bin, Tag, Vsn) -> deserialize(Template, Bin, Tag, Vsn) ->
aeser_chain_objects:deserialize(Tag, Vsn, Template, Bin). gmser_chain_objects:deserialize(Tag, Vsn, Template, Bin).
serialize(Template, Bin) -> serialize(Template, Bin) ->
serialize(Template, Bin, ?DEFAULT_TAG, ?DEFAULT_VERSION). serialize(Template, Bin, ?DEFAULT_TAG, ?DEFAULT_VERSION).
serialize(Template, Bin, Tag, Vsn) -> serialize(Template, Bin, Tag, Vsn) ->
aeser_chain_objects:serialize(Tag, Vsn, Template, Bin). gmser_chain_objects:serialize(Tag, Vsn, Template, Bin).

View File

@ -1,7 +1,11 @@
-module(aeser_contract_code_tests). %%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%%-------------------------------------------------------------------
-module(gmser_contract_code_tests).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include("aeser_contract_code.hrl"). -include("gmser_contract_code.hrl").
-define(DUMMY_CODE_MAP_1, -define(DUMMY_CODE_MAP_1,
#{ byte_code => <<"DUMMY CODE">> #{ byte_code => <<"DUMMY CODE">>
@ -22,14 +26,14 @@
, payable => true} ). , payable => true} ).
vsn_1_test() -> vsn_1_test() ->
aeser_contract_code:deserialize( gmser_contract_code:deserialize(
aeser_contract_code:serialize(?DUMMY_CODE_MAP_1, ?SOPHIA_CONTRACT_VSN_1)). gmser_contract_code:serialize(?DUMMY_CODE_MAP_1, ?SOPHIA_CONTRACT_VSN_1)).
vsn_2_test() -> vsn_2_test() ->
aeser_contract_code:deserialize( gmser_contract_code:deserialize(
aeser_contract_code:serialize(?DUMMY_CODE_MAP_2, ?SOPHIA_CONTRACT_VSN_2)). gmser_contract_code:serialize(?DUMMY_CODE_MAP_2, ?SOPHIA_CONTRACT_VSN_2)).
vsn_3_test() -> vsn_3_test() ->
aeser_contract_code:deserialize( gmser_contract_code:deserialize(
aeser_contract_code:serialize(?DUMMY_CODE_MAP_3, ?SOPHIA_CONTRACT_VSN_3)). gmser_contract_code:serialize(?DUMMY_CODE_MAP_3, ?SOPHIA_CONTRACT_VSN_3)).

View File

@ -0,0 +1,49 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2023, Aeternity Anstalt
%%%-------------------------------------------------------------------
-module(gmser_delegation_tests).
-include_lib("eunit/include/eunit.hrl").
-define(TEST_MODULE, gmser_delegation).
-define(ACCOUNT, gmser_id:create(account, <<1:256>>)).
-define(CONTRACT, gmser_id:create(contract, <<2:256>>)).
-define(NAME, gmser_id:create(name, <<3:256>>)).
-define(NETWORK_ID, <<"my_fancy_network"/utf8>>).
encode_correct_test_() ->
[{"Encode preclaim sig",
fun() ->
gmser_delegation:aens_preclaim_sig(?NETWORK_ID, ?ACCOUNT, ?CONTRACT)
end},
{"Encode name sig",
fun() ->
gmser_delegation:aens_name_sig(?NETWORK_ID, ?ACCOUNT, ?NAME, ?CONTRACT)
end},
{"Encode aens wildcard sig",
fun() ->
gmser_delegation:aens_sig(?NETWORK_ID, ?ACCOUNT, ?CONTRACT)
end}
].
encode_fail_test_() ->
[{"Bad encoding preclaim sig",
fun() ->
?assertError(_, gmser_delegation:aens_preclaim_sig(?NETWORK_ID, <<42:256>>, ?CONTRACT)),
?assertError(_, gmser_delegation:aens_preclaim_sig(?NETWORK_ID, ?CONTRACT, ?ACCOUNT))
end},
{"Bad encoding name sig",
fun() ->
?assertError(_, gmser_delegation:aens_name_sig(?NETWORK_ID, ?ACCOUNT, <<42:256>>, ?CONTRACT)),
?assertError(_, gmser_delegation:aens_name_sig(?NETWORK_ID, ?NAME, ?ACCOUNT, ?CONTRACT))
end},
{"Bad encoding aens wildcard sig",
fun() ->
?assertError(_, gmser_delegation:aens_sig(?NETWORK_ID, ?ACCOUNT, <<42:256>>)),
?assertError(_, gmser_delegation:aens_sig(?NETWORK_ID, ?CONTRACT, ?CONTRACT))
end}
].

View File

@ -1,10 +1,11 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2017, Aeternity Anstalt %%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc Tests for Recursive Length Prefix %%% @doc Tests for Recursive Length Prefix
%%% @end %%% @end
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeser_rlp_tests). -module(gmser_rlp_tests).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
@ -13,7 +14,7 @@
-define(BYTE_ARRAY_OFFSET , 128). -define(BYTE_ARRAY_OFFSET , 128).
-define(LIST_OFFSET , 192). -define(LIST_OFFSET , 192).
-define(TEST_MODULE, aeser_rlp). -define(TEST_MODULE, gmser_rlp).
rlp_one_byte_test() -> rlp_one_byte_test() ->
B = <<42>>, B = <<42>>,

17
zomp.meta Normal file
View File

@ -0,0 +1,17 @@
{name,"Gajumaru Serialization"}.
{type,lib}.
{modules,[]}.
{prefix,none}.
{author,"Hans Svensson"}.
{desc,"Serialization helpers for the Gajumaru."}.
{package_id,{"otpr","gmserialization",{0,1,2}}}.
{deps,[{"otpr","eblake2",{1,0,1}},{"otpr","base58",{0,1,1}}]}.
{key_name,none}.
{a_email,[]}.
{c_email,[]}.
{copyright,"QPQ AG"}.
{file_exts,[]}.
{license,skip}.
{repo_url,"https://git.qpq.swiss/QPQ-AG/gmserialization"}.
{tags,["blockchain","crypto","gm","gajumaru"]}.
{ws_url,[]}.