Compare commits
No commits in common. "master" and "fortuna" have entirely different histories.
37
.circleci/config.yml
Normal file
37
.circleci/config.yml
Normal file
@ -0,0 +1,37 @@
|
||||
version: 2.1
|
||||
|
||||
executors:
|
||||
aebuilder:
|
||||
docker:
|
||||
- image: aeternity/builder
|
||||
user: builder
|
||||
working_directory: ~/aebytecode
|
||||
|
||||
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: make dialyzer
|
||||
- run:
|
||||
name: Eunit
|
||||
command: make eunit
|
||||
- run:
|
||||
name: Common Tests
|
||||
command: make test
|
||||
- save_cache:
|
||||
key: dialyzer-cache-v1-{{ .Branch }}-{{ .Revision }}
|
||||
paths:
|
||||
- _build/default/rebar3_20.3.8_plt
|
||||
- store_artifacts:
|
||||
path: _build/test/logs
|
@ -1,15 +0,0 @@
|
||||
name: Gajumaru Bytecode Tests
|
||||
run-name: ${{ gitea.actor }} testing Gajumaru Bytecode
|
||||
on: [push, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: linux_amd64
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
- name: test
|
||||
run: |
|
||||
. /home/act_runner/.erts/27.2.1/activate
|
||||
make dialyzer
|
||||
make eunit
|
25
.gitignore
vendored
25
.gitignore
vendored
@ -8,21 +8,12 @@ ebin/*.beam
|
||||
rel/example_project
|
||||
.concrete/DEV_MODE
|
||||
.rebar
|
||||
gmb_asm_scan.erl
|
||||
gmb_fate_asm_scan.erl
|
||||
gmb_fate_asm_scan.xrl
|
||||
aeb_asm_scan.erl
|
||||
aeb_fate_asm_scan.erl
|
||||
aeb_fate_asm_scan.xrl
|
||||
_build/
|
||||
gmfateasm
|
||||
include/gmb_fate_opcodes.hrl
|
||||
src/gmb_fate_opcodes.erl
|
||||
src/gmb_fate_ops.erl
|
||||
src/gmb_fate_pp.erl
|
||||
*.erl~
|
||||
*.hrl~
|
||||
*.aes~
|
||||
doc
|
||||
cover
|
||||
gmfate
|
||||
current_counterexample.eqc
|
||||
.rebar3
|
||||
ebin/*.beam
|
||||
aefateasm
|
||||
include/aeb_fate_opcodes.hrl
|
||||
src/aeb_fate_code.erl
|
||||
src/aeb_fate_opcodes.erl
|
||||
src/aeb_fate_pp.erl
|
||||
|
1
LICENSE
1
LICENSE
@ -1,6 +1,5 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2025, QPQ AG
|
||||
Copyright (c) 2017, aeternity developers
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
|
13
Makefile
13
Makefile
@ -1,6 +1,6 @@
|
||||
GENERATED_SRC = src/gmb_fate_opcodes.erl src/gmb_fate_ops.erl include/gmb_fate_opcodes.hrl src/gmb_fate_asm_scan.xrl src/gmb_fate_pp.erl
|
||||
GENERATOR_DEPS = ebin/gmb_fate_generate_ops.beam src/gmb_fate_asm_scan.template
|
||||
REBAR ?= ./rebar3
|
||||
GENERATED_SRC = src/aeb_fate_opcodes.erl src/aeb_fate_code.erl include/aeb_fate_opcodes.hrl src/aeb_fate_asm_scan.xrl src/aeb_fate_pp.erl
|
||||
GENERATOR_DEPS = ebin/aeb_fate_generate_ops.beam src/aeb_fate_asm_scan.template
|
||||
REBAR ?= rebar3
|
||||
|
||||
all: local
|
||||
|
||||
@ -15,8 +15,7 @@ console: local
|
||||
clean:
|
||||
@$(REBAR) clean
|
||||
rm -f $(GENERATED_SRC)
|
||||
rm -f ebin/*.beam
|
||||
rm -rf _build
|
||||
rm -f ebin/*
|
||||
|
||||
dialyzer: local
|
||||
@$(REBAR) as local dialyzer
|
||||
@ -31,7 +30,7 @@ test: local
|
||||
@$(REBAR) as local eunit
|
||||
|
||||
ebin/%.beam: src/%.erl
|
||||
erlc +debug_info -o $(dir $@) $<
|
||||
erlc -o $(dir $@) $<
|
||||
|
||||
$(GENERATED_SRC): $(GENERATOR_DEPS)
|
||||
erl -pa ebin/ -noshell -s gmb_fate_generate_ops gen_and_halt src/ include/
|
||||
erl -pa ebin/ -noshell -s aeb_fate_generate_ops gen_and_halt src/ include/
|
||||
|
43
README.md
43
README.md
@ -1,7 +1,7 @@
|
||||
# gmbytecode
|
||||
A library and stand alone assembler for Gajumaru bytecode.
|
||||
# aebytecode
|
||||
An library and stand alone assembler for aeternity bytecode.
|
||||
|
||||
This version supports AEVM bytecode and FATE bytecode.
|
||||
This version supports Aevm bytecode and Fate bytecode.
|
||||
|
||||
## Build
|
||||
|
||||
@ -19,7 +19,7 @@ Fate code exists in 3 formats:
|
||||
3. Internal. This is an Erlang representation of fate code
|
||||
Used by this particular engin implementation.
|
||||
|
||||
This library handles all three representations.
|
||||
This library handles all tree representations.
|
||||
The byte code format is described in a separate document.
|
||||
The internal format is described in a separate document.
|
||||
The text representation is described below.
|
||||
@ -49,7 +49,7 @@ or start with stack followed by an integer
|
||||
`stack1`
|
||||
`a`
|
||||
|
||||
Immediate values can be of 10 types:
|
||||
Immediate values can be of 9 types:
|
||||
|
||||
1. Integers as decimals: {Digits} or -{Digits}
|
||||
`42`
|
||||
@ -57,24 +57,8 @@ Immediate values can be of 10 types:
|
||||
And integers as Hexadecimals:: 0x{Hexdigits}
|
||||
`0x0deadbeef0`
|
||||
|
||||
2. Chain Objects. These are all addresses to different types of chain objects.
|
||||
Each address is a 256 bits number encoded in base58 with checksum
|
||||
with a prefix of "@" plus a type prefix followed by "_".
|
||||
|
||||
2a. Account Address: a base58c encoded number starting with @ak_ followed by a number of base58chars
|
||||
'@ak_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv`
|
||||
|
||||
2b. Contract address: @ct_{base58char}+
|
||||
`@ct_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv`
|
||||
|
||||
2c. Oracle address: @ok_{base58char}+
|
||||
`@ok_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv`
|
||||
|
||||
2d. Oracle query: @oq_{base58char}+
|
||||
`@oq_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv`
|
||||
|
||||
2e. Channel address: @ch_{base58char}+
|
||||
`@ch_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv`
|
||||
2. addresses, a base58 encoded string starting with # followed by a number of base58chars
|
||||
`#nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv`
|
||||
|
||||
3. Boolean true or false
|
||||
`true`
|
||||
@ -101,15 +85,8 @@ Immediate values can be of 10 types:
|
||||
`()`
|
||||
`(1, "foo")`
|
||||
|
||||
9. Variants: (| [Arities] | Tag | ( Elements ) |)
|
||||
`(| [1,3,5,2] | 3 | ( "foo", 12) |)`
|
||||
|
||||
10. Bytes: #{base64char}+
|
||||
`#AQIDCioLFQ==`
|
||||
|
||||
11. Contract bytearray (code of another smart contract)
|
||||
`@cb_+PJGA6A4Fz4T2LHV5knITCldR3rqO7HrXO2zhOAR9JWNbhf8Q8C4xbhx/gx8JckANwAXfQBVACAAAP4vhlvZADcABwECgv5E1kQfADcBBzcACwAWMBReAHMAFjBvJFMAFjBvggOoFAAUABQSggABAz/+tIwWhAA3AAdTAAD+1jB5kAQ3AAcLAAD+6MRetgA3AQc3ABoGggABAz+4TS8GEQx8JclFY2FsbGVyX2lzX2NyZWF0b3IRL4Zb2Q1nZXQRRNZEHxFpbml0EbSMFoQdYmFsYW5jZRHWMHmQFXZhbHVlEejEXrYNc2V0gi8AhTQuMy4wAUqQ8s4=`
|
||||
|
||||
9. Variants: (| Size | Tag | ( Elements ) |)
|
||||
`(| 42 | 12 | ( "foo", 12) |)`
|
||||
|
||||
Where
|
||||
|
||||
@ -119,8 +96,6 @@ Hexdigits: [0123456789abcdef]
|
||||
|
||||
base58char: [123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]
|
||||
|
||||
base64char: [ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxy0123456789+/=]
|
||||
|
||||
Characters: any printable ascii character 0..255 (except " no quoting yet)
|
||||
|
||||
Key: any value except for a map
|
||||
|
0
ebin/.gitkeep
Normal file
0
ebin/.gitkeep
Normal file
@ -1,16 +0,0 @@
|
||||
{application,gmbytecode,
|
||||
[{description,"Bytecode definitions, serialization and deserialization for the Gajumaru."},
|
||||
{vsn,"3.4.1"},
|
||||
{registered,[]},
|
||||
{applications,[kernel,stdlib,eblake2,gmserialization,getopt]},
|
||||
{env,[]},
|
||||
{modules,[gmb_aevm_abi,gmb_aevm_data,gmb_asm,gmb_asm_scan,
|
||||
gmb_disassemble,gmb_fate_abi,gmb_fate_asm,
|
||||
gmb_fate_asm_scan,gmb_fate_code,gmb_fate_data,
|
||||
gmb_fate_encoding,gmb_fate_generate_docs,
|
||||
gmb_fate_generate_ops,gmb_fate_maps,gmb_fate_opcodes,
|
||||
gmb_fate_ops,gmb_fate_pp,gmb_heap,gmb_memory,
|
||||
gmb_opcodes,gmb_primops,gmfateasm]},
|
||||
{maintainers,[]},
|
||||
{licenses,[]},
|
||||
{links,[]}]}.
|
55
include/aeb_fate_data.hrl
Normal file
55
include/aeb_fate_data.hrl
Normal file
@ -0,0 +1,55 @@
|
||||
-define(FATE_INTEGER_T, integer()).
|
||||
-define(FATE_BYTE_T, 0..255).
|
||||
-define(FATE_BOOLEAN_T, true | false).
|
||||
-define(FATE_NIL_T, []).
|
||||
-define(FATE_LIST_T, list()).
|
||||
-define(FATE_UNIT_T, {tuple, {}}).
|
||||
-define(FATE_MAP_T, #{ fate_type() => fate_type() }).
|
||||
-define(FATE_STRING_T, binary()).
|
||||
-define(FATE_ADDRESS_T, {address, <<_:256>>}).
|
||||
-define(FATE_VARIANT_T, {variant, ?FATE_BYTE_T, ?FATE_BYTE_T, tuple()}).
|
||||
-define(FATE_VOID_T, void).
|
||||
-define(FATE_TUPLE_T, {tuple, tuple()}).
|
||||
-define(FATE_BITS_T, {bits, integer()}).
|
||||
|
||||
-define(IS_FATE_INTEGER(X), is_integer(X)).
|
||||
-define(IS_FATE_LIST(X), (is_list(X))).
|
||||
-define(IS_FATE_STRING(X), (is_binary(X))).
|
||||
-define(IS_FATE_MAP(X), (is_map(X))).
|
||||
-define(IS_FATE_TUPLE(X), (is_tuple(X) andalso (tuple == element(1, X) andalso is_tuple(element(2, X))))).
|
||||
-define(IS_FATE_ADDRESS(X), (is_tuple(X) andalso (address == element(1, X) andalso is_binary(element(2, X))))).
|
||||
-define(IS_FATE_BITS(X), (is_tuple(X) andalso (bits == element(1, X) andalso is_integer(element(2, X))))).
|
||||
-define(IS_FATE_VARIANT(X), (is_tuple(X)
|
||||
andalso
|
||||
(variant == element(1, X)
|
||||
andalso is_integer(element(2, X))
|
||||
andalso is_integer(element(3, X))
|
||||
andalso is_tuple(element(4, X))
|
||||
))).
|
||||
-define(IS_FATE_BOOLEAN(X), is_boolean(X)).
|
||||
|
||||
-define(FATE_UNIT, {tuple, {}}).
|
||||
-define(FATE_TUPLE(T), {tuple, T}).
|
||||
-define(FATE_ADDRESS(A), {address, A}).
|
||||
-define(FATE_BITS(B), {bits, B}).
|
||||
|
||||
|
||||
-define(FATE_INTEGER_VALUE(X), (X)).
|
||||
-define(FATE_LIST_VALUE(X), (X)).
|
||||
-define(FATE_STRING_VALUE(X), (X)).
|
||||
-define(FATE_ADDRESS_VALUE(X), (element(2, X))).
|
||||
-define(FATE_MAP_VALUE(X), (X)).
|
||||
-define(FATE_MAP_SIZE(X), (map_size(X))).
|
||||
-define(FATE_STRING_SIZE(X), (byte_size(X))).
|
||||
-define(FATE_TRUE, true).
|
||||
-define(FATE_FALSE, false).
|
||||
-define(FATE_NIL, []).
|
||||
-define(FATE_VOID, void).
|
||||
-define(FATE_EMPTY_STRING, <<>>).
|
||||
-define(FATE_STRING(S), S).
|
||||
-define(FATE_VARIANT(Size, Tag,T), {variant, Size, Tag, T}).
|
||||
|
||||
-define(MAKE_FATE_INTEGER(X), X).
|
||||
-define(MAKE_FATE_LIST(X), X).
|
||||
-define(MAKE_FATE_MAP(X), X).
|
||||
-define(MAKE_FATE_STRING(X), X).
|
@ -30,7 +30,6 @@
|
||||
|
||||
-define( 'SHA3', 16#20).
|
||||
|
||||
-define( 'CREATOR', 16#2f).
|
||||
-define( 'ADDRESS', 16#30).
|
||||
-define( 'BALANCE', 16#31).
|
||||
-define( 'ORIGIN', 16#32).
|
||||
@ -166,8 +165,6 @@
|
||||
-define(PRIM_CALL_ORACLE_GET_ANSWER, 104).
|
||||
-define(PRIM_CALL_ORACLE_GET_QUESTION, 105).
|
||||
-define(PRIM_CALL_ORACLE_QUERY_FEE, 106).
|
||||
-define(PRIM_CALL_ORACLE_CHECK, 110).
|
||||
-define(PRIM_CALL_ORACLE_CHECK_QUERY, 111).
|
||||
|
||||
-define(PRIM_CALL_IN_AENS_RANGE(__TTYPE__), (((__TTYPE__) > 199) andalso ((__TTYPE__) < 300))).
|
||||
-define(PRIM_CALL_AENS_RESOLVE, 200).
|
||||
@ -186,20 +183,9 @@
|
||||
-define(PRIM_CALL_MAP_TOLIST, 305).
|
||||
|
||||
-define(PRIM_CALL_IN_CRYPTO_RANGE(__TTYPE__), (((__TTYPE__) > 399) andalso ((__TTYPE__) < 500))).
|
||||
-define(PRIM_CALL_CRYPTO_VERIFY_SIG, 400).
|
||||
-define(PRIM_CALL_CRYPTO_SHA3, 401).
|
||||
-define(PRIM_CALL_CRYPTO_SHA256, 402).
|
||||
-define(PRIM_CALL_CRYPTO_BLAKE2B, 403).
|
||||
-define(PRIM_CALL_CRYPTO_SHA256_STRING, 404).
|
||||
-define(PRIM_CALL_CRYPTO_BLAKE2B_STRING, 405).
|
||||
-define(PRIM_CALL_CRYPTO_VERIFY_SIG_SECP256K1, 410).
|
||||
-define(PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, 420).
|
||||
-define(PRIM_CALL_CRYPTO_ECRECOVER_SECP256K1, 421).
|
||||
|
||||
-define(PRIM_CALL_IN_AUTH_RANGE(__TTYPE__), (((__TTYPE__) > 499) andalso ((__TTYPE__) < 600))).
|
||||
-define(PRIM_CALL_AUTH_TX_HASH, 500).
|
||||
|
||||
-define(PRIM_CALL_IN_ADDRESS_RANGE(__TTYPE__), (((__TTYPE__) > 599) andalso ((__TTYPE__) < 700))).
|
||||
-define(PRIM_CALL_ADDR_IS_ORACLE, 600).
|
||||
-define(PRIM_CALL_ADDR_IS_CONTRACT, 601).
|
||||
-define(PRIM_CALL_ADDR_IS_PAYABLE, 610).
|
||||
-define(PRIM_CALL_CRYPTO_ECVERIFY, 400).
|
||||
-define(PRIM_CALL_CRYPTO_SHA3, 401).
|
||||
-define(PRIM_CALL_CRYPTO_SHA256, 402).
|
||||
-define(PRIM_CALL_CRYPTO_BLAKE2B, 403).
|
||||
-define(PRIM_CALL_CRYPTO_SHA256_STRING, 404).
|
||||
-define(PRIM_CALL_CRYPTO_BLAKE2B_STRING, 405).
|
@ -1,99 +0,0 @@
|
||||
-define(FATE_INTEGER_T, integer()).
|
||||
-define(FATE_BYTE_T, 0..255).
|
||||
-define(FATE_BOOLEAN_T, true | false).
|
||||
-define(FATE_NIL_T, []).
|
||||
-define(FATE_LIST_T, list()).
|
||||
-define(FATE_UNIT_T, {tuple, {}}).
|
||||
-define(FATE_MAP_T, #{ fate_type() => fate_type() }).
|
||||
-define(FATE_STORE_MAP_T, {store_map, #{ fate_type() => fate_type() | ?FATE_MAP_TOMBSTONE }, integer()}).
|
||||
-define(FATE_STRING_T, binary()).
|
||||
-define(FATE_ADDRESS_T, {address, <<_:256>>}).
|
||||
-define(FATE_BYTES_T(N), {bytes, binary()}).
|
||||
-define(FATE_CONTRACT_T, {contract, <<_:256>>}).
|
||||
-define(FATE_ORACLE_T, {oracle, <<_:256>>}).
|
||||
-define(FATE_ORACLE_Q_T, {oracle_query, <<_:256>>}).
|
||||
-define(FATE_CHANNEL_T, {channel, <<_:256>>}).
|
||||
-define(FATE_VARIANT_T, {variant, [byte()], ?FATE_BYTE_T, tuple()}).
|
||||
-define(FATE_VOID_T, void).
|
||||
-define(FATE_TUPLE_T, {tuple, tuple()}).
|
||||
-define(FATE_BITS_T, {bits, integer()}).
|
||||
-define(FATE_TYPEREP_T, {typerep, fate_type_type()}).
|
||||
-define(FATE_CONTRACT_BYTEARRAY_T, {contract_bytearray, binary()}).
|
||||
|
||||
-define(IS_FATE_INTEGER(X), (is_integer(X))).
|
||||
-define(IS_FATE_LIST(X), (is_list(X))).
|
||||
-define(IS_FATE_STRING(X), (is_binary(X))).
|
||||
-define(IS_FATE_STORE_MAP(X), (is_tuple(X) andalso tuple_size(X) == 3
|
||||
andalso store_map == element(1, X)
|
||||
andalso is_map(element(2, X))
|
||||
andalso is_integer(element(3, X)))).
|
||||
-define(IS_FATE_MAP(X), (is_map(X))).
|
||||
-define(IS_FATE_TUPLE(X), (is_tuple(X) andalso (tuple == element(1, X) andalso is_tuple(element(2, X))))).
|
||||
-define(IS_FATE_ADDRESS(X), (is_tuple(X) andalso (address == element(1, X) andalso is_binary(element(2, X))))).
|
||||
-define(IS_FATE_BYTES(X), (is_tuple(X) andalso (bytes == element(1, X) andalso is_binary(element(2, X))))).
|
||||
-define(IS_FATE_BYTES(N, X), (?IS_FATE_BYTES(X) andalso byte_size(element(2, X)) == (N))).
|
||||
-define(IS_FATE_CONTRACT(X), (is_tuple(X) andalso (contract == element(1, X) andalso is_binary(element(2, X))))).
|
||||
-define(IS_FATE_ORACLE(X), (is_tuple(X) andalso (oracle == element(1, X) andalso is_binary(element(2, X))))).
|
||||
-define(IS_FATE_ORACLE_Q(X), (is_tuple(X) andalso (oracle_query == element(1, X) andalso is_binary(element(2, X))))).
|
||||
-define(IS_FATE_CHANNEL(X), (is_tuple(X) andalso (channel == element(1, X) andalso is_binary(element(2, X))))).
|
||||
-define(IS_FATE_BITS(X), (is_tuple(X) andalso (bits == element(1, X) andalso is_integer(element(2, X))))).
|
||||
-define(IS_FATE_VARIANT(X), (is_tuple(X)
|
||||
andalso
|
||||
(variant == element(1, X)
|
||||
andalso is_list(element(2, X))
|
||||
andalso is_integer(element(3, X))
|
||||
andalso is_tuple(element(4, X))
|
||||
))).
|
||||
-define(IS_FATE_BOOLEAN(X), is_boolean(X)).
|
||||
-define(IS_FATE_TYPEREP(X), (is_tuple(X) andalso tuple_size(X) =:= 2 andalso element(1, X) =:= typerep)).
|
||||
-define(IS_FATE_CONTRACT_BYTEARRAY(X), (is_tuple(X) andalso tuple_size(X) =:= 2 andalso element(1, X) =:= contract_bytearray
|
||||
andalso is_binary(element(2, X)))).
|
||||
|
||||
-define(FATE_UNIT, {tuple, {}}).
|
||||
-define(FATE_TUPLE(T), {tuple, T}).
|
||||
-define(FATE_ADDRESS(A), {address, A}).
|
||||
-define(FATE_BYTES(X), {bytes, X}).
|
||||
-define(FATE_CONTRACT(X), {contract, X}).
|
||||
-define(FATE_ORACLE(X), {oracle, X}).
|
||||
-define(FATE_ORACLE_Q(X), {oracle_query, X}).
|
||||
-define(FATE_CHANNEL(X), {channel, X}).
|
||||
-define(FATE_BITS(B), {bits, B}).
|
||||
-define(FATE_TYPEREP(T), {typerep, T}).
|
||||
-define(FATE_STORE_MAP(Cache, Id), {store_map, Cache, Id}).
|
||||
-define(FATE_MAP_TOMBSTONE, '__DELETED__').
|
||||
|
||||
-define(FATE_INTEGER_VALUE(X), (X)).
|
||||
-define(FATE_BOOLEAN_VALUE(X), (X)).
|
||||
-define(FATE_LIST_VALUE(X), (X)).
|
||||
-define(FATE_TUPLE_ELEMENTS(X), (tuple_to_list(element(2, X)))).
|
||||
-define(FATE_STRING_VALUE(X), (X)).
|
||||
-define(FATE_ADDRESS_VALUE(X), (element(2, X))).
|
||||
-define(FATE_BYTES_VALUE(X), (element(2, X))).
|
||||
-define(FATE_CONTRACT_VALUE(X), (element(2, X))).
|
||||
-define(FATE_ORACLE_VALUE(X), (element(2, X))).
|
||||
-define(FATE_CHANNEL_VALUE(X), (element(2, X))).
|
||||
-define(FATE_BITS_VALUE(X), (element(2, X))).
|
||||
-define(FATE_MAP_VALUE(X), (X)).
|
||||
-define(FATE_STORE_MAP_CACHE(X), (element(2, X))).
|
||||
-define(FATE_STORE_MAP_ID(X), (element(3, X))).
|
||||
-define(FATE_MAP_SIZE(X), (map_size(X))).
|
||||
-define(FATE_STRING_SIZE(X), (byte_size(X))).
|
||||
-define(FATE_CONTRACT_BYTEARRAY_SIZE(X), (byte_size(X))).
|
||||
-define(FATE_TRUE, true).
|
||||
-define(FATE_FALSE, false).
|
||||
-define(FATE_NIL, []).
|
||||
-define(FATE_VOID, void).
|
||||
-define(FATE_EMPTY_STRING, <<>>).
|
||||
-define(FATE_STRING(S), S).
|
||||
-define(FATE_VARIANT(Arity, Tag,T), {variant, Arity, Tag, T}).
|
||||
-define(FATE_CONTRACT_BYTEARRAY(B), {contract_bytearray, B}).
|
||||
|
||||
% Result of gmb_fate_code:symbol_identifier(<<"init">>).
|
||||
% Stored here to avoid repeated calls to eblake2
|
||||
-define(FATE_INIT_ID, <<68,214,68,31>>).
|
||||
|
||||
-define(MAKE_FATE_INTEGER(X), X).
|
||||
-define(MAKE_FATE_LIST(X), X).
|
||||
-define(MAKE_FATE_MAP(X), X).
|
||||
-define(MAKE_FATE_STRING(X), X).
|
||||
-define(MAKE_FATE_CONTRACT_BYTEARRAY(X), {contract_bytearray, X}).
|
@ -1,15 +0,0 @@
|
||||
|
||||
-record(pmap, {key_t :: gmb_aevm_data:type(),
|
||||
val_t :: gmb_aevm_data:type(),
|
||||
parent :: none | non_neg_integer(),
|
||||
size = 0 :: non_neg_integer(),
|
||||
data :: #{gmb_heap:binary_value() => gmb_heap:binary_value() | tombstone}
|
||||
| stored}).
|
||||
|
||||
-record(maps, { maps = #{} :: #{ non_neg_integer() => #pmap{} }
|
||||
, next_id = 0 :: non_neg_integer() }).
|
||||
|
||||
-record(heap, { maps :: #maps{},
|
||||
offset :: gmb_heap:offset(),
|
||||
heap :: binary() | #{non_neg_integer() => non_neg_integer()} }).
|
||||
|
@ -1,12 +0,0 @@
|
||||
|
||||
-define(Type(), gmb_aevm_data:type()).
|
||||
|
||||
-define(TYPEREP_WORD_TAG, 0).
|
||||
-define(TYPEREP_STRING_TAG, 1).
|
||||
-define(TYPEREP_LIST_TAG, 2).
|
||||
-define(TYPEREP_TUPLE_TAG, 3).
|
||||
-define(TYPEREP_VARIANT_TAG, 4).
|
||||
-define(TYPEREP_TYPEREP_TAG, 5).
|
||||
-define(TYPEREP_MAP_TAG, 6).
|
||||
-define(TYPEREP_FUN_TAG, 7).
|
||||
-define(TYPEREP_CONTRACT_BYTEARRAY_TAG,8).
|
@ -1,27 +0,0 @@
|
||||
%%% @author Thomas Arts
|
||||
%%% @doc Allow to run QuickCheck tests as eunit tests
|
||||
%%% `rebar3 as eqc eunit --cover`
|
||||
%%% or `rebar3 as eqc eunit --module=gmb_fate_code`
|
||||
%%% Note that for obtainign cover file, one needs `rebar3 as eqc cover
|
||||
%%%
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 13 Dec 2018 by Thomas Arts <thomas@SpaceGrey.lan>
|
||||
|
||||
-module(gmb_fate_code_tests).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
-define(EQC_EUNIT(Module, PropName, Ms),
|
||||
{ atom_to_list(PropName),
|
||||
{timeout, (Ms * 10) div 1000, ?_assert(eqc:quickcheck(eqc:testing_time(Ms / 1000, Module:PropName())))}}).
|
||||
|
||||
quickcheck_test_() ->
|
||||
{setup, fun() -> eqc:start() end,
|
||||
[ ?EQC_EUNIT(gmfate_code_eqc, prop_opcodes, 200),
|
||||
?EQC_EUNIT(gmfate_code_eqc, prop_serializes, 3000),
|
||||
?EQC_EUNIT(gmfate_code_eqc, prop_fail_serializes, 3000),
|
||||
?EQC_EUNIT(gmfate_code_eqc, prop_fuzz, 3000)
|
||||
]}.
|
@ -1,27 +0,0 @@
|
||||
%%% @author Thomas Arts
|
||||
%%% @doc Allow to run QuickCheck tests as eunit tests
|
||||
%%% `rebar3 as eqc eunit --cover`
|
||||
%%% or `rebar3 as eqc eunit --module=gmb_fate_data`
|
||||
%%% Note that for obtainign cover file, one needs `rebar3 as eqc cover
|
||||
%%%
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 13 Dec 2018 by Thomas Arts <thomas@SpaceGrey.lan>
|
||||
|
||||
-module(gmb_fate_data_tests).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
-define(EQC_EUNIT(Module, PropName, Ms),
|
||||
{ atom_to_list(PropName),
|
||||
{timeout, (Ms * 3) / 1000, ?_assert(eqc:quickcheck(eqc:testing_time(Ms / 1000, Module:PropName())))}}).
|
||||
|
||||
quickcheck_test_() ->
|
||||
{setup, fun() -> eqc:start() end,
|
||||
[ ?EQC_EUNIT(gmfate_eqc, prop_roundtrip, 500),
|
||||
?EQC_EUNIT(gmfate_eqc, prop_format_scan, 2000),
|
||||
?EQC_EUNIT(gmfate_eqc, prop_order, 2000),
|
||||
?EQC_EUNIT(gmfate_eqc, prop_fuzz, 2000)
|
||||
]}.
|
@ -1,27 +0,0 @@
|
||||
%%% @author Thomas Arts
|
||||
%%% @doc Allow to run QuickCheck tests as eunit tests
|
||||
%%% `rebar3 as eqc eunit --cover`
|
||||
%%% or `rebar3 as eqc eunit --module=gmb_fate_encoding`
|
||||
%%% Note that for obtaining cover file, one needs `rebar3 as eqc cover
|
||||
%%%
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 13 Dec 2018 by Thomas Arts
|
||||
|
||||
-module(gmb_fate_encoding_tests).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
-define(EQC_EUNIT(Module, PropName, Ms),
|
||||
{ atom_to_list(PropName),
|
||||
{timeout, (Ms * 3) / 1000, ?_assert(eqc:quickcheck(eqc:testing_time(Ms / 1000, Module:PropName())))}}).
|
||||
|
||||
quickcheck_test_() ->
|
||||
{setup, fun() -> eqc:start() end,
|
||||
[ ?EQC_EUNIT(gmfate_type_eqc, prop_roundtrip, 1000),
|
||||
?EQC_EUNIT(gmfate_eqc, prop_serializes, 1000),
|
||||
?EQC_EUNIT(gmfate_eqc, prop_no_maps_in_keys, 1000),
|
||||
?EQC_EUNIT(gmfate_eqc, prop_idempotent, 1000)
|
||||
]}.
|
@ -1,167 +0,0 @@
|
||||
%%% @author Thomas Arts
|
||||
%%% @doc Use `rebar3 as eqc shell` to run properties in the shell
|
||||
%%%
|
||||
%%% We want to be sure that we can deserialize all FATE assembler that is accepted on chain.
|
||||
%%%
|
||||
%%% We test something slightly weaker here,
|
||||
%%% viz. All FATE assembler we serialize, we can deserialize
|
||||
%%%
|
||||
%%% Negative testing modelled:
|
||||
%%% Failure 1: function names differ from 4 bytes
|
||||
%%% Failure 2: pointer to empty code block
|
||||
%%% Failure 3: end_BB operation as not ending block or not at end of block
|
||||
%%% - empty code blocks
|
||||
%%% - blocks that are not of the form (not end_bb)* end_bb.
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 13 Dec 2018 by Thomas Arts <thomas@SpaceGrey.lan>
|
||||
|
||||
-module(gmfate_code_eqc).
|
||||
|
||||
-include_lib("eqc/include/eqc.hrl").
|
||||
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
%%-define(Failure(Failures, Number), case lists:member(Number, Failures) of true -> 1; false -> 0 end)
|
||||
|
||||
|
||||
prop_serializes() ->
|
||||
in_parallel(
|
||||
?FORALL(FateCode, fate_code(0),
|
||||
begin
|
||||
{T0, Binary} = timer:tc(fun() -> gmb_fate_code:serialize(FateCode) end),
|
||||
?WHENFAIL(eqc:format("serialized:\n ~120p~n", [Binary]),
|
||||
begin
|
||||
{T1, Decoded} = timer:tc(fun() -> gmb_fate_code:deserialize(Binary) end),
|
||||
measure(binary_size, size(Binary),
|
||||
measure(serialize, T0 / 1000,
|
||||
measure(deserialize, T1 / 1000,
|
||||
conjunction([{equal, equals(Decoded, FateCode)},
|
||||
{serialize_time, T0 / 1000 < 500},
|
||||
{deserialize_time, T1 / 1000 < 500}]))))
|
||||
end)
|
||||
end)).
|
||||
|
||||
prop_fail_serializes() ->
|
||||
conjunction([{Failure, eqc:counterexample(
|
||||
?FORALL(FateCode, fate_code(Failure),
|
||||
?FORALL(Binary, catch gmb_fate_code:serialize(FateCode),
|
||||
is_binary(Binary))))
|
||||
=/= true} || Failure <- [1, 2, 3, 4, 5] ]).
|
||||
|
||||
prop_fuzz() ->
|
||||
in_parallel(
|
||||
?FORALL(Binary, ?LET(FateCode, fate_code(0), gmb_fate_code:serialize(FateCode)),
|
||||
?FORALL(FuzzedBin, fuzz(Binary),
|
||||
try gmb_fate_code:deserialize(FuzzedBin) of
|
||||
Code ->
|
||||
?WHENFAIL(eqc:format("Code:\n ~p\n", [Code]),
|
||||
begin
|
||||
Bin1 = gmb_fate_code:serialize(Code),
|
||||
Code1 = gmb_fate_code:deserialize(Bin1),
|
||||
?WHENFAIL(eqc:format("Reserialized\n ~120p\n", [Bin1]),
|
||||
equals(Code, Code1))
|
||||
end)
|
||||
catch _:_ -> true
|
||||
end))).
|
||||
|
||||
prop_opcodes() ->
|
||||
?FORALL(Opcode, choose(0, 16#ff),
|
||||
try M = gmb_fate_opcodes:mnemonic(Opcode),
|
||||
?WHENFAIL(eqc:format("opcode ~p -> ~p", [Opcode, M]),
|
||||
conjunction([{valid, lists:member(Opcode, valid_opcodes())},
|
||||
{eq, equals(gmb_fate_opcodes:m_to_op(M), Opcode)}]))
|
||||
catch
|
||||
_:_ ->
|
||||
not lists:member(Opcode, valid_opcodes())
|
||||
end).
|
||||
|
||||
|
||||
valid_opcodes() ->
|
||||
[ Op || #{opcode := Op} <- gmb_fate_generate_ops:get_ops() ].
|
||||
|
||||
|
||||
fate_code(Failure) ->
|
||||
?SIZED(Size,
|
||||
?LET({FMap, SMap, AMap},
|
||||
{non_empty(map(if Failure == 1 -> binary(1);
|
||||
true -> binary(4) end,
|
||||
{sublist(lists:sort([private, payable])), %% deserialize sorts them
|
||||
{list(gmfate_type_eqc:fate_type(Size div 3)), gmfate_type_eqc:fate_type(Size div 3)}, bbs_code(Failure)})),
|
||||
small_map(small_fate_data_key(5), small_fate_data(4)),
|
||||
small_map(small_fate_data_key(5), small_fate_data(4))},
|
||||
gmb_fate_code:update_annotations(
|
||||
gmb_fate_code:update_symbols(
|
||||
gmb_fate_code:update_functions(
|
||||
gmb_fate_code:new(), FMap), SMap), AMap))).
|
||||
|
||||
short_list(Max, Gen) ->
|
||||
?LET(N, choose(0, Max), eqc_gen:list(N, Gen)).
|
||||
|
||||
small_map(KeyGen, ValGen) ->
|
||||
?LET(KeyVals, short_list(6, {KeyGen, ValGen}),
|
||||
return(maps:from_list(KeyVals))).
|
||||
|
||||
bbs_code(Failure) ->
|
||||
frequency([{if Failure == 2 -> 5; true -> 0 end, #{0 => []}},
|
||||
{10, ?LET(BBs, short_list(6, bb_code(Failure)),
|
||||
maps:from_list(
|
||||
lists:zip(lists:seq(0, length(BBs)-1), BBs)))}]).
|
||||
|
||||
bb_code(Failure) ->
|
||||
EndBB = [ Op || Op <- valid_opcodes(), gmb_fate_opcodes:end_bb(Op) ],
|
||||
NonEndBB = valid_opcodes() -- EndBB,
|
||||
frequency(
|
||||
[{if Failure == 3 -> 5; true -> 0 end, ?LET(Ops, non_empty(short_list(6, elements(NonEndBB))), bblock(Failure, Ops))},
|
||||
{if Failure == 4 -> 5; true -> 0 end, ?LET({Ops, Op}, {short_list(6, elements(valid_opcodes())), elements(EndBB)}, bblock(Failure, Ops ++ [Op]))},
|
||||
{10, ?LET({Ops, Op}, {short_list(6, elements(NonEndBB)), elements(EndBB)},
|
||||
bblock(Failure, Ops ++ [Op]))}]).
|
||||
|
||||
bblock(Failure, Ops) ->
|
||||
[ begin
|
||||
Mnemonic = gmb_fate_opcodes:mnemonic(Op),
|
||||
Arity = gmb_fate_opcodes:args(Op),
|
||||
case Arity of
|
||||
0 -> Mnemonic;
|
||||
_ -> list_to_tuple([Mnemonic |
|
||||
[ frequency([{if Failure == 5 -> 5; true -> 0 end, {stack, nat()}},
|
||||
{5, {stack, 0}},
|
||||
{5, {arg, nat()}},
|
||||
{5, {var, nat()}},
|
||||
{5, {immediate, small_fate_data(4)}}]) ||
|
||||
_ <- lists:seq(1, Arity) ]])
|
||||
end
|
||||
end || Op <- Ops ].
|
||||
|
||||
fuzz(Binary) ->
|
||||
?LET({N, Inj}, {choose(0, byte_size(Binary) - 1), choose(0, 255)},
|
||||
begin
|
||||
M = N * 8,
|
||||
<<X:M, _:8, Z/binary>> = Binary,
|
||||
<<X:M, Inj:8, Z/binary>>
|
||||
end).
|
||||
|
||||
prop_small() ->
|
||||
?FORALL(Value, small_fate_data(4),
|
||||
begin
|
||||
Bin = gmb_fate_encoding:serialize(Value),
|
||||
Size = byte_size(Bin),
|
||||
measure(size, Size,
|
||||
?WHENFAIL(eqc:format("Size: ~p\n", [Size]),
|
||||
Size < 1000))
|
||||
end).
|
||||
|
||||
prop_small_type() ->
|
||||
?FORALL(Type, ?SIZED(Size, gmfate_type_eqc:fate_type(Size div 3)),
|
||||
begin
|
||||
Bin = iolist_to_binary(gmb_fate_encoding:serialize_type(Type)),
|
||||
Size = byte_size(Bin),
|
||||
measure(size, Size,
|
||||
?WHENFAIL(eqc:format("Size: ~p\n", [Size]),
|
||||
Size < 1000))
|
||||
end).
|
||||
|
||||
small_fate_data(N) ->
|
||||
?SIZED(Size, resize(Size div N, gmfate_eqc:fate_data())).
|
||||
|
||||
small_fate_data_key(N) ->
|
||||
?SIZED(Size, ?LET(Data, gmfate_eqc:fate_data(Size div N, []), eqc_symbolic:eval(Data))).
|
@ -1,211 +0,0 @@
|
||||
%%% @author Thomas Arts
|
||||
%%% @doc Use `rebar3 as eqc shell` to run properties in the shell
|
||||
%%%
|
||||
%%% We need to be able to generate data that serializes with ?LONG_LIST, ?LONG_TUPLE etc.
|
||||
%%% In other words make some rather broad terms as well as some deep terms
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 13 Dec 2018 by Thomas Arts <thomas@SpaceGrey.lan>
|
||||
|
||||
-module(gmfate_eqc).
|
||||
|
||||
-include_lib("eqc/include/eqc.hrl").
|
||||
-include("../include/gmb_fate_data.hrl").
|
||||
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
prop_roundtrip() ->
|
||||
?FORALL(FateData, fate_data(),
|
||||
measure(bytes, size(term_to_binary(FateData)),
|
||||
begin
|
||||
Serialized = gmb_fate_encoding:serialize(FateData),
|
||||
?WHENFAIL(eqc:format("Serialized ~p to ~p~n", [FateData, Serialized]),
|
||||
equals(gmb_fate_encoding:deserialize(Serialized), FateData))
|
||||
end)).
|
||||
|
||||
prop_format_scan() ->
|
||||
?FORALL(FateData, fate_data([variant, map]),
|
||||
?WHENFAIL(eqc:format("Trying to format ~p failed~n", [FateData]),
|
||||
begin
|
||||
String = gmb_fate_data:format(FateData),
|
||||
{ok, _Scanned, _} = gmb_fate_asm_scan:scan(unicode:characters_to_list(String)),
|
||||
true
|
||||
end)).
|
||||
|
||||
prop_serializes() ->
|
||||
?FORALL({Data, Garbage}, {fate_data(), binary()},
|
||||
?WHENFAIL(eqc:format("Trying to serialize/deserialize ~p failed~n", [Data]),
|
||||
begin
|
||||
Binary = <<(gmb_fate_encoding:serialize(Data))/binary, Garbage/binary>>,
|
||||
{FateData, Rest} = gmb_fate_encoding:deserialize_one(Binary),
|
||||
measure(binary_size, size(Binary),
|
||||
conjunction([{equal, equals(Data, FateData)},
|
||||
{rest, equals(Garbage, Rest)},
|
||||
{size, size(Binary) < 500000}]))
|
||||
end)).
|
||||
|
||||
prop_no_maps_in_keys() ->
|
||||
?FORALL(FateData, fate_bad_map(), %% may contain a map in its keys
|
||||
begin
|
||||
HasMapInKeys = lists:any(fun(K) -> has_map(K) end, maps:keys(FateData)),
|
||||
try gmb_fate_encoding:serialize(FateData),
|
||||
?WHENFAIL(eqc:format("Should not serialize, contains a map in key\n", []),
|
||||
not HasMapInKeys)
|
||||
catch error:Reason ->
|
||||
?WHENFAIL(eqc:format("(~p) Should serialize\n", [Reason]), HasMapInKeys)
|
||||
end
|
||||
end).
|
||||
|
||||
prop_fuzz() ->
|
||||
in_parallel(
|
||||
?FORALL(Binary, ?LET(FateData, ?SIZED(Size, resize(Size div 4, fate_data())), gmb_fate_encoding:serialize(FateData)),
|
||||
?FORALL(InjectedBin, injection(Binary),
|
||||
try Org = gmb_fate_encoding:deserialize(InjectedBin),
|
||||
NewBin = gmb_fate_encoding:serialize(Org),
|
||||
NewOrg = gmb_fate_encoding:deserialize(NewBin),
|
||||
measure(success, 1,
|
||||
?WHENFAIL(eqc:format("Deserialize ~p gives\n~p\nSerializes to ~p\n", [InjectedBin, Org, NewOrg]),
|
||||
equals(NewBin, InjectedBin)))
|
||||
catch _:_ ->
|
||||
true
|
||||
end))).
|
||||
|
||||
|
||||
prop_order() ->
|
||||
?FORALL(Items, vector(3, fate_data([variant, map])),
|
||||
begin
|
||||
%% Use lt to take minimum
|
||||
Min = lt_min(Items),
|
||||
Max = lt_max(Items),
|
||||
conjunction([ {minimum, is_empty([ {Min, '>', I} || I<-Items, gmb_fate_data:lt(I, Min)])},
|
||||
{maximum, is_empty([ {Max, '<', I} || I<-Items, gmb_fate_data:lt(Max, I)])},
|
||||
{asym, gmb_fate_data:lt(Min, Max) orelse Min == Max}])
|
||||
end).
|
||||
|
||||
lt_min([X, Y | Rest]) ->
|
||||
case gmb_fate_data:lt(X, Y) of
|
||||
true -> lt_min([X | Rest]);
|
||||
false -> lt_min([Y| Rest])
|
||||
end;
|
||||
lt_min([X]) -> X.
|
||||
|
||||
lt_max([X, Y | Rest]) ->
|
||||
case gmb_fate_data:lt(X, Y) of
|
||||
true -> lt_max([Y | Rest]);
|
||||
false -> lt_max([X| Rest])
|
||||
end;
|
||||
lt_max([X]) -> X.
|
||||
|
||||
prop_idempotent() ->
|
||||
?FORALL(Items, list({fate_data_key(), fate_data()}),
|
||||
equals(gmb_fate_encoding:sort(Items),
|
||||
gmb_fate_encoding:sort(gmb_fate_encoding:sort(Items)))).
|
||||
|
||||
|
||||
|
||||
fate_data(Kind) ->
|
||||
?SIZED(Size, ?LET(Data, fate_data(Size, Kind), eqc_symbolic:eval(Data))).
|
||||
|
||||
fate_data() ->
|
||||
fate_data([map, variant, store_map]).
|
||||
|
||||
%% keys may contain variants but no maps
|
||||
fate_data_key() ->
|
||||
fate_data([variant]).
|
||||
|
||||
fate_data(0, Options) ->
|
||||
?LAZY(
|
||||
frequency(
|
||||
[{50, oneof([fate_integer(), fate_boolean(), fate_nil(), fate_unit()])},
|
||||
{10, oneof([fate_string(), fate_address(), fate_bytes(), fate_contract(),
|
||||
fate_oracle(), fate_oracle_q(), fate_bits(), fate_channel()])}] ++
|
||||
[{1, fate_store_map()} || lists:member(store_map, Options)]));
|
||||
fate_data(Size, Options) ->
|
||||
?LAZY(
|
||||
oneof([fate_data(0, Options),
|
||||
fate_list(Size, Options),
|
||||
fate_tuple(Size, Options)] ++
|
||||
[fate_variant(Size, Options)
|
||||
|| lists:member(variant, Options)] ++
|
||||
[fate_map(Size, Options)
|
||||
|| lists:member(map, Options)])).
|
||||
|
||||
|
||||
fate_integer() -> ?LET(X, oneof([int(), largeint()]), return(gmb_fate_data:make_integer(X))).
|
||||
fate_bits() -> ?LET(X, oneof([int(), largeint()]), return(gmb_fate_data:make_bits(X))).
|
||||
fate_boolean() -> ?LET(X, elements([true, false]), return(gmb_fate_data:make_boolean(X))).
|
||||
fate_nil() -> gmb_fate_data:make_list([]).
|
||||
fate_unit() -> gmb_fate_data:make_unit().
|
||||
fate_string() -> ?LET(X, frequency([{10, non_quote_string()}, {2, list(non_quote_string())},
|
||||
{1, ?LET(N, choose(64-3, 64+3), vector(N, $a))}]),
|
||||
return(gmb_fate_data:make_string(X))).
|
||||
fate_address() -> ?LET(X, binary(256 div 8), return(gmb_fate_data:make_address(X))).
|
||||
fate_bytes() -> ?LET(X, non_empty(binary()), return(gmb_fate_data:make_bytes(X))).
|
||||
fate_contract() -> ?LET(X, binary(256 div 8), return(gmb_fate_data:make_contract(X))).
|
||||
fate_oracle() -> ?LET(X, binary(256 div 8), return(gmb_fate_data:make_oracle(X))).
|
||||
fate_oracle_q() -> ?LET(X, binary(256 div 8), return(gmb_fate_data:make_oracle_query(X))).
|
||||
fate_channel() -> ?LET(X, binary(256 div 8), return(gmb_fate_data:make_channel(X))).
|
||||
|
||||
fate_values(Size, N, Options) ->
|
||||
eqc_gen:list(N, fate_data(Size div max(1, N), Options)).
|
||||
|
||||
%% May shrink to fate_unit
|
||||
fate_tuple(Size, Options) ->
|
||||
?LET(N, choose(0, 6),
|
||||
?LETSHRINK(Elements, fate_values(Size, N, Options),
|
||||
return(gmb_fate_data:make_tuple(list_to_tuple(Elements))))).
|
||||
|
||||
fate_variant(Size, Options) ->
|
||||
?LET({L1, L2, {tuple, Args}}, {list(choose(0, 255)), list(choose(0,255)), fate_tuple(Size, Options)},
|
||||
return(gmb_fate_data:make_variant(L1 ++ [tuple_size(Args)] ++ L2,
|
||||
length(L1), Args))).
|
||||
|
||||
fate_list(Size, Options) ->
|
||||
?LET(N, frequency([{20, choose(0, 6)}, {1, choose(64 - 3, 64 + 3)}]),
|
||||
?LETSHRINK(Vs, fate_values(Size, N, Options),
|
||||
return(gmb_fate_data:make_list(Vs)))).
|
||||
|
||||
fate_map(Size, Options) ->
|
||||
?LET(N, choose(0, 6),
|
||||
?LETSHRINK(Values, fate_values(Size, N, Options),
|
||||
?LET(Keys, vector(length(Values), fate_data(Size div max(1, N * 2), Options -- [map, store_map])),
|
||||
return(gmb_fate_data:make_map(maps:from_list(lists:zip(Keys, Values))))))).
|
||||
|
||||
fate_store_map() ->
|
||||
%% only #{} is allowed as cache in serialization
|
||||
?LET(X, oneof([int(), largeint()]),
|
||||
return(gmb_fate_data:make_store_map(abs(X)))).
|
||||
|
||||
fate_bad_map() ->
|
||||
?LET(N, choose(0, 6),
|
||||
?LET(Values, vector(N, ?SIZED(Size, resize(Size div 8, fate_data()))),
|
||||
?LET(Keys, vector(N, ?SIZED(Size, resize(Size div 4, fate_data()))),
|
||||
return(gmb_fate_data:make_map(maps:from_list(lists:zip(Keys, Values))))))).
|
||||
|
||||
non_quote_string() ->
|
||||
?SUCHTHAT(S, utf8(), [ quote || <<34>> <= S ] == []).
|
||||
|
||||
char() ->
|
||||
choose(1, 255).
|
||||
|
||||
injection(Binary) ->
|
||||
?LET({N, Inj}, {choose(0, byte_size(Binary) - 1), choose(0,255)},
|
||||
begin
|
||||
M = N * 8,
|
||||
<<X:M, _:8, Z/binary>> = Binary,
|
||||
<<X:M, Inj:8, Z/binary>>
|
||||
end).
|
||||
|
||||
is_empty(L) ->
|
||||
?WHENFAIL(eqc:format("~p\n", [L]), L == []).
|
||||
|
||||
has_map(L) when is_list(L) ->
|
||||
lists:any(fun(V) -> has_map(V) end, L);
|
||||
has_map(T) when is_tuple(T) ->
|
||||
has_map(tuple_to_list(T));
|
||||
has_map(M) when is_map(M) ->
|
||||
true;
|
||||
has_map(?FATE_STORE_MAP(_, _)) ->
|
||||
true;
|
||||
has_map(_) ->
|
||||
false.
|
@ -1,56 +0,0 @@
|
||||
%%% @author Thomas Arts
|
||||
%%% @doc Use `rebar3 as eqc shell` to run properties in the shell
|
||||
%%% Properties for testing Fate type representations
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 13 Dec 2018 by Thomas Arts <thomas@SpaceGrey.lan>
|
||||
|
||||
-module(gmfate_type_eqc).
|
||||
|
||||
-include_lib("eqc/include/eqc.hrl").
|
||||
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
kind(X) when is_atom(X) -> X;
|
||||
kind(T) when is_tuple(T) -> element(1, T).
|
||||
|
||||
prop_roundtrip() ->
|
||||
?FORALL(FateType, fate_type(),
|
||||
collect(kind(FateType),
|
||||
begin
|
||||
Serialized = gmb_fate_encoding:serialize_type(FateType),
|
||||
BinSerialized = list_to_binary(Serialized),
|
||||
?WHENFAIL(eqc:format("Serialized ~p to ~p (~p)~n", [FateType, Serialized, BinSerialized]),
|
||||
begin
|
||||
{Type, <<>>} = gmb_fate_encoding:deserialize_type(BinSerialized),
|
||||
equals(Type, FateType)
|
||||
end)
|
||||
end)).
|
||||
|
||||
|
||||
fate_type() ->
|
||||
?SIZED(Size, fate_type(Size)).
|
||||
|
||||
fate_type(0) ->
|
||||
oneof([integer,
|
||||
boolean,
|
||||
address,
|
||||
{bytes, nat()},
|
||||
contract,
|
||||
oracle,
|
||||
channel,
|
||||
bits,
|
||||
string]);
|
||||
fate_type(Size) ->
|
||||
?LAZY(
|
||||
oneof([fate_type(0),
|
||||
{list, fate_type(Size div 2)},
|
||||
?LETSHRINK(Ts, fate_types(Size), {tuple, Ts}),
|
||||
?LETSHRINK(Ts, fate_types(Size), {variant, Ts}),
|
||||
?LETSHRINK([T1, T2], vector(2, fate_type(Size div 2)),
|
||||
{map, T1, T2})])).
|
||||
|
||||
fate_types(Size) ->
|
||||
?LET(N, choose(0, 6),
|
||||
eqc_gen:list(N, fate_type(Size div max(2, N)))).
|
||||
|
26
rebar.config
26
rebar.config
@ -1,18 +1,16 @@
|
||||
%% -*- mode: erlang; indent-tabs-mode: nil -*-
|
||||
|
||||
{minimum_otp_vsn, "20.1"}.
|
||||
|
||||
{erl_opts, [debug_info]}.
|
||||
|
||||
{deps, [ {eblake2, "1.0.0"}
|
||||
, {gmserialization, {git, "https://git.qpq.swiss/QPQ-AG/gmserialization.git",
|
||||
{ref, "9d2ecc00d32ea295309563e54a81636ecb597e96"}}}
|
||||
, {aeserialization, {git, "https://github.com/aeternity/aeserialization.git",
|
||||
{ref, "b55c372"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
]}.
|
||||
|
||||
{escript_incl_apps, [gmbytecode, eblake2, gmserialization, getopt]}.
|
||||
{escript_main_app, gmbytecode}.
|
||||
{escript_name, gmfateasm}.
|
||||
{escript_incl_apps, [aebytecode, eblake2, aeserialization, getopt]}.
|
||||
{escript_main_app, aebytecode}.
|
||||
{escript_name, aefateasm}.
|
||||
{escript_emu_args, "%%!"}.
|
||||
|
||||
{pre_hooks,
|
||||
@ -29,8 +27,8 @@
|
||||
]}.
|
||||
|
||||
|
||||
{relx, [{release, {gmbytecode, "3.4.1"},
|
||||
[gmbytecode, eblake2, getopt]},
|
||||
{relx, [{release, {aebytecode, "2.0.1"},
|
||||
[aebytecode, eblake2, getopt]},
|
||||
|
||||
{dev_mode, true},
|
||||
{include_erts, false},
|
||||
@ -39,21 +37,21 @@
|
||||
|
||||
{profiles, [{binary, [
|
||||
{deps, [ {eblake2, "1.0.0"}
|
||||
, {gmserialization, {git, "https://git.qpq.swiss/QPQ-AG/gmserialization.git",
|
||||
{ref, "9d2ecc00d32ea295309563e54a81636ecb597e96"}}}
|
||||
, {aeserialization, {git, "https://github.com/aeternity/aeserialization.git",
|
||||
{ref, "b55c372"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
]},
|
||||
|
||||
{post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)",
|
||||
escriptize,
|
||||
"cp \"$REBAR_BUILD_DIR/bin/gmfateasm\" ./gmfateasm"},
|
||||
"cp \"$REBAR_BUILD_DIR/bin/aefateasm\" ./aefateasm"},
|
||||
{"win32",
|
||||
escriptize,
|
||||
"robocopy \"%REBAR_BUILD_DIR%/bin/\" ./ gmfateasm* "
|
||||
"robocopy \"%REBAR_BUILD_DIR%/bin/\" ./ aefateasm* "
|
||||
"/njs /njh /nfl /ndl & exit /b 0"} % silence things
|
||||
]}
|
||||
]},
|
||||
{eqc, [{erl_opts, [{parse_transform, eqc_cover}, {d, 'EQC'}]},
|
||||
{eqc, [{erl_opts, [{parse_transform, eqc_cover}]},
|
||||
{extra_src_dirs, ["quickcheck"]} %% May not be called eqc!
|
||||
]}
|
||||
]}.
|
||||
|
21
rebar.lock
21
rebar.lock
@ -1,23 +1,16 @@
|
||||
{"1.2.0",
|
||||
[{<<"gmserialization">>,
|
||||
{git,"https://git.qpq.swiss/QPQ-AG/gmserialization.git",
|
||||
{ref,"9d2ecc00d32ea295309563e54a81636ecb597e96"}},
|
||||
{"1.1.0",
|
||||
[{<<"aeserialization">>,
|
||||
{git,"https://github.com/aeternity/aeserialization.git",
|
||||
{ref,"b55c3726f4a21063721c68d6fa7fda39121edf11"}},
|
||||
0},
|
||||
{<<"base58">>,
|
||||
{git,"https://git.qpq.swiss/QPQ-AG/erl-base58.git",
|
||||
{ref,"e6aa62eeae3d4388311401f06e4b939bf4e94b9c"}},
|
||||
{git,"https://github.com/aeternity/erl-base58.git",
|
||||
{ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}},
|
||||
1},
|
||||
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
|
||||
{<<"enacl">>,
|
||||
{git,"https://git.qpq.swiss/QPQ-AG/enacl.git",
|
||||
{ref,"4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}},
|
||||
1},
|
||||
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}]}.
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
|
||||
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]},
|
||||
{pkg_hash_ext,[
|
||||
{<<"eblake2">>, <<"3C4D300A91845B25D501929A26AC2E6F7157480846FAB2347A4C11AE52E08A99">>},
|
||||
{<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>}]}
|
||||
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
|
||||
].
|
||||
|
@ -1,5 +1,4 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc Assembler for aevm machine code.
|
||||
%%%
|
||||
@ -26,19 +25,17 @@
|
||||
%%% 4. labels as descibed above.
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Updated : 22 Jan 2025
|
||||
%%% Created : 21 Dec 2017
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(gmb_asm).
|
||||
-vsn("3.4.1").
|
||||
-module(aeb_asm).
|
||||
|
||||
-export([ file/2
|
||||
, pp/1
|
||||
, to_hexstring/1
|
||||
]).
|
||||
|
||||
-include_lib("gmbytecode/include/gmb_opcodes.hrl").
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
|
||||
|
||||
pp(Asm) ->
|
||||
@ -50,10 +47,10 @@ format(Asm) -> format(Asm, 0).
|
||||
format([{comment, Comment} | Rest], Address) ->
|
||||
";; " ++ Comment ++ "\n" ++ format(Rest, Address);
|
||||
format([Mnemonic | Rest], Address) ->
|
||||
Op = gmb_opcodes:m_to_op(Mnemonic),
|
||||
Op = aeb_opcodes:m_to_op(Mnemonic),
|
||||
case (Op >= ?PUSH1) andalso (Op =< ?PUSH32) of
|
||||
true ->
|
||||
Arity = gmb_opcodes:op_size(Op) - 1,
|
||||
Arity = aeb_opcodes:op_size(Op) - 1,
|
||||
{Args, Code} = get_args(Arity, Rest),
|
||||
" " ++ atom_to_list(Mnemonic)
|
||||
++ " " ++ Args ++ "\n"
|
||||
@ -75,7 +72,7 @@ get_args(N, [Arg|Code]) ->
|
||||
|
||||
file(Filename, Options) ->
|
||||
{ok, File} = file:read_file(Filename),
|
||||
{ok, Tokens, _} = gmb_asm_scan:scan(binary_to_list(File)),
|
||||
{ok, Tokens, _} = aeb_asm_scan:scan(binary_to_list(File)),
|
||||
|
||||
case proplists:lookup(pp_tokens, Options) of
|
||||
{pp_tokens, true} ->
|
||||
@ -103,8 +100,8 @@ to_hexstring(ByteList) ->
|
||||
|
||||
|
||||
to_bytecode([{mnemonic,_line, Op}|Rest], Address, Env, Code, Opts) ->
|
||||
OpCode = gmb_opcodes:m_to_op(Op),
|
||||
OpSize = gmb_opcodes:op_size(OpCode),
|
||||
OpCode = aeb_opcodes:m_to_op(Op),
|
||||
OpSize = aeb_opcodes:op_size(OpCode),
|
||||
to_bytecode(Rest, Address + OpSize, Env, [OpCode|Code], Opts);
|
||||
to_bytecode([{int,_line, Int}|Rest], Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env, [Int|Code], Opts);
|
||||
@ -141,7 +138,7 @@ resolve_refs([Op | Rest], Env, Code) ->
|
||||
resolve_refs([],_Env, Code) -> Code.
|
||||
|
||||
expand_args([OP, Arg | Rest]) when OP >= ?PUSH1 andalso OP =< ?PUSH32 ->
|
||||
BitSize = (gmb_opcodes:op_size(OP) - 1) * 8,
|
||||
BitSize = (aeb_opcodes:op_size(OP) - 1) * 8,
|
||||
Bin = << << X:BitSize>> || X <- [Arg] >>,
|
||||
ArgByteList = binary_to_list(Bin),
|
||||
[OP | ArgByteList] ++ expand_args(Rest);
|
@ -1,6 +1,5 @@
|
||||
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc Assembler lexer.
|
||||
%%%
|
||||
@ -196,7 +195,7 @@ Erlang code.
|
||||
|
||||
-ignore_xref([format_error/1, string/2, token/2, token/3, tokens/2, tokens/3]).
|
||||
|
||||
-include_lib("gmbytecode/include/gmb_opcodes.hrl").
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
|
||||
|
||||
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
|
@ -1,22 +1,19 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Prettyprint aevm machine code
|
||||
%%% @end
|
||||
%%% Updated : 22 Jan 2025
|
||||
%%% Created : 02 Oct 2017
|
||||
%%% Created : 2 Oct 2017
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(gmb_disassemble).
|
||||
-vsn("3.4.1").
|
||||
-module(aeb_disassemble).
|
||||
|
||||
-export([ pp/1,
|
||||
format/2,
|
||||
format_address/1
|
||||
]).
|
||||
|
||||
-include_lib("gmbytecode/include/gmb_opcodes.hrl").
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
|
||||
|
||||
pp(Binary) ->
|
||||
@ -29,37 +26,37 @@ format(Binary, ErrFormatFun) ->
|
||||
pp(Address, [Op|Ops], Assembly, ErrFormatFun) ->
|
||||
case Op of
|
||||
X when (X >= ?STOP) andalso (X =< ?SIGNEXTEND) ->
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?LT) andalso (X =< ?BYTE) ->
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?SHA3) andalso (X =< ?SHA3) ->
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?ADDRESS) andalso (X =< ?EXTCODECOPY) ->
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?BLOCKHASH) andalso (X =< ?GASLIMIT) ->
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?POP) andalso (X =< ?JUMPDEST) ->
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?PUSH1) andalso (X =< ?PUSH32) ->
|
||||
Bytes = X-?PUSH1+1,
|
||||
{ArgList, NextOps} = lists:split(Bytes, Ops),
|
||||
Arg = arglist_to_arg(ArgList),
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), [{Arg,8*Bytes}]),
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), [{Arg,8*Bytes}]),
|
||||
next(Address+Bytes, NextOps, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?DUP1) andalso (X =< ?LOG4) ->
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?CREATE) andalso (X =< ?DELEGATECALL) ->
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?INVALID) andalso (X =< ?SUICIDE) ->
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
_ ->
|
||||
ErrFormatFun("unhandled op ~p at ~p",[Op, Address]),
|
1049
src/aeb_fate_asm.erl
Normal file
1049
src/aeb_fate_asm.erl
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,10 @@
|
||||
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||
%%% @copyright (C) 2019, aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Handling FATE code.
|
||||
%%% @end
|
||||
%%% ###REPLACEWITHNOTE###
|
||||
###REPLACEWITHNOTE###
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
Definitions.
|
||||
@ -14,13 +13,9 @@ HEXDIGIT = [0-9a-fA-F]
|
||||
LOWER = [a-z_]
|
||||
UPPER = [A-Z]
|
||||
BASE58 = [123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]
|
||||
BASE64 = [A-Za-z0-9+/=]
|
||||
INT = {DIGIT}+
|
||||
HEX = 0x{HEXDIGIT}+
|
||||
OBJ_PFX = (ak|ct|ok|oq|ch|sg)
|
||||
OBJECT = @{OBJ_PFX}_{BASE58}+
|
||||
CODE = @cb_{BASE64}+
|
||||
BYTES = #{BASE64}+
|
||||
HASH = #{BASE58}+
|
||||
WS = [\000-\s]
|
||||
ID = {LOWER}[a-zA-Z0-9_]*
|
||||
STRING = "[^"]*"
|
||||
@ -29,21 +24,16 @@ BITS = (\!)?\<[\s01]*\>
|
||||
Rules.
|
||||
arg{INT} : {token, {arg, TokenLine, parse_arg(TokenChars)}}.
|
||||
var{INT} : {token, {var, TokenLine, parse_var(TokenChars)}}.
|
||||
a : {token, {stack, TokenLine}}.
|
||||
a : {token, {stack, TokenLine, 0}}.
|
||||
a{INT} : {token, {stack, TokenLine, parse_acc(TokenChars)}}.
|
||||
|
||||
true : {token, {boolean, TokenLine, true}}.
|
||||
false : {token, {boolean, TokenLine, false}}.
|
||||
|
||||
%% ###REPLACEWITHOPTOKENS###
|
||||
###REPLACEWITHOPTOKENS###
|
||||
|
||||
FUNCTION : {token, {function, TokenLine, 'FUNCTION' }}.
|
||||
|
||||
{BYTES} :
|
||||
{token, {bytes, TokenLine, parse_hash(TokenChars)}}.
|
||||
{CODE} :
|
||||
{token, {contract_bytearray, TokenLine, parse_contract_bytearray(TokenChars)}}.
|
||||
{OBJECT} :
|
||||
{token, {object, TokenLine, parse_object(TokenChars)}}.
|
||||
{ID} :
|
||||
{token, {id, TokenLine, TokenChars}}.
|
||||
{HEX} :
|
||||
@ -52,11 +42,10 @@ FUNCTION : {token, {function, TokenLine, 'FUNCTION' }}.
|
||||
{token, {int, TokenLine, parse_int(TokenChars)}}.
|
||||
-{INT} :
|
||||
{token, {int, TokenLine, parse_int(TokenChars)}}.
|
||||
|
||||
%% Due to the definition of STRING the tokens start and end with a quote ".
|
||||
{HASH} :
|
||||
{token, {address, TokenLine, parse_hash(TokenChars)}}.
|
||||
{STRING} :
|
||||
{token, {string, TokenLine, unicode:characters_to_binary(
|
||||
lists:sublist(TokenChars, 2, length(TokenChars) - 2))}}.
|
||||
{token, {string, TokenLine, list_to_binary(TokenChars)}}.
|
||||
{BITS} :
|
||||
{token, {bits, TokenLine, bits(TokenChars)}}.
|
||||
|
||||
@ -77,7 +66,6 @@ FUNCTION : {token, {function, TokenLine, 'FUNCTION' }}.
|
||||
\{ : {token, {'{', TokenLine}}.
|
||||
\} : {token, {'}', TokenLine}}.
|
||||
\| : {token, {'|', TokenLine}}.
|
||||
\' : {token, {typerep, TokenLine}}.
|
||||
|
||||
;;.* :
|
||||
{token, {comment, TokenLine, drop_prefix($;, TokenChars)}}.
|
||||
@ -101,7 +89,7 @@ Erlang code.
|
||||
|
||||
-ignore_xref([format_error/1, string/2, token/2, token/3, tokens/2, tokens/3]).
|
||||
|
||||
-include_lib("gmbytecode/include/gmb_fate_opcodes.hrl").
|
||||
-include_lib("aebytecode/include/aeb_fate_opcodes.hrl").
|
||||
|
||||
|
||||
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
|
||||
@ -110,25 +98,11 @@ parse_int(Chars) -> list_to_integer(Chars).
|
||||
|
||||
parse_arg("arg" ++ N) -> list_to_integer(N).
|
||||
parse_var("var" ++ N) -> list_to_integer(N).
|
||||
parse_acc("a" ++ N) -> list_to_integer(N).
|
||||
|
||||
|
||||
parse_hash("#" ++ Chars) ->
|
||||
base64:decode(Chars).
|
||||
|
||||
parse_contract_bytearray("@" ++ Chars) ->
|
||||
case gmser_api_encoder:decode(unicode:characters_to_binary(Chars)) of
|
||||
{contract_bytearray, Bin} -> Bin
|
||||
end.
|
||||
|
||||
parse_object([_|Chars]) ->
|
||||
case gmser_api_encoder:decode(unicode:characters_to_binary(Chars)) of
|
||||
{account_pubkey, Bin} -> {address, Bin};
|
||||
{contract_pubkey, Bin} -> {contract, Bin};
|
||||
{oracle_pubkey, Bin} -> {oracle, Bin};
|
||||
{oracle_query_id, Bin} -> {oracle_query, Bin};
|
||||
{channel, Bin} -> {channel, Bin};
|
||||
{signature, Bin} -> {signature, Bin}
|
||||
end.
|
||||
base58_to_address(Chars).
|
||||
|
||||
scan(S) ->
|
||||
string(S).
|
||||
@ -146,3 +120,23 @@ bits([$> |_Rest], Acc) -> Acc;
|
||||
bits([$0 | Rest], Acc) -> bits(Rest, Acc bsl 1);
|
||||
bits([$1 | Rest], Acc) -> bits(Rest, (Acc bsl 1) bor 1);
|
||||
bits([$ | Rest], Acc) -> bits(Rest, Acc).
|
||||
|
||||
char_to_base58(C) ->
|
||||
binary:at(<<0,1,2,3,4,5,6,7,8,0,0,0,0,0,0,0,9,10,11,12,13,14,15,16,0,17,
|
||||
18,19,20,21,0,22,23,24,25,26,27,28,29,30,31,32,0,0,0,0,0,0,
|
||||
33,34,35,36,37,38,39,40,41,42,43,0,44,45,46,47,48,49,50,51,
|
||||
52,53,54,55,56,57>>, C-$1).
|
||||
|
||||
base58_to_integer(C, []) -> C;
|
||||
base58_to_integer(C, [X | Xs]) ->
|
||||
base58_to_integer(C * 58 + char_to_base58(X), Xs).
|
||||
|
||||
base58_to_integer([]) -> error;
|
||||
base58_to_integer([Char]) -> char_to_base58(Char);
|
||||
base58_to_integer([Char | Str]) ->
|
||||
base58_to_integer(char_to_base58(Char), Str).
|
||||
|
||||
base58_to_address(Base58) ->
|
||||
I = base58_to_integer(Base58),
|
||||
Bin = <<I:256>>,
|
||||
Bin.
|
208
src/aeb_fate_data.erl
Normal file
208
src/aeb_fate_data.erl
Normal file
@ -0,0 +1,208 @@
|
||||
%% FATE data representation.
|
||||
%%
|
||||
-include("aeb_fate_data.hrl").
|
||||
|
||||
-module(aeb_fate_data).
|
||||
|
||||
-type fate_integer() :: ?FATE_INTEGER_T.
|
||||
-type fate_boolean() :: ?FATE_BOOLEAN_T.
|
||||
-type fate_nil() :: ?FATE_NIL_T.
|
||||
-type fate_list() :: ?FATE_LIST_T.
|
||||
-type fate_unit() :: ?FATE_UNIT_T.
|
||||
-type fate_map() :: ?FATE_MAP_T.
|
||||
-type fate_string() :: ?FATE_STRING_T.
|
||||
-type fate_address() :: ?FATE_ADDRESS_T.
|
||||
|
||||
-type fate_variant() :: ?FATE_VARIANT_T.
|
||||
|
||||
-type fate_tuple() :: ?FATE_TUPLE_T.
|
||||
|
||||
-type fate_type_type() :: integer
|
||||
| boolean
|
||||
| {list, fate_type()}
|
||||
| {map, fate_type(), fate_type()}
|
||||
| {tuple, [fate_type()]}
|
||||
| address
|
||||
| bits
|
||||
| {variant, integer()}.
|
||||
|
||||
|
||||
-type fate_type() ::
|
||||
fate_boolean()
|
||||
| fate_integer()
|
||||
| fate_nil()
|
||||
| fate_list()
|
||||
| fate_unit()
|
||||
| fate_tuple()
|
||||
| fate_string()
|
||||
| fate_address()
|
||||
| fate_variant()
|
||||
| fate_map()
|
||||
| fate_type_type().
|
||||
|
||||
-export_type([fate_type/0
|
||||
, fate_boolean/0
|
||||
, fate_integer/0
|
||||
, fate_nil/0
|
||||
, fate_list/0
|
||||
, fate_unit/0
|
||||
, fate_tuple/0
|
||||
, fate_string/0
|
||||
, fate_address/0
|
||||
, fate_variant/0
|
||||
, fate_map/0
|
||||
, fate_type_type/0
|
||||
]).
|
||||
|
||||
-export([ make_integer/1
|
||||
, make_boolean/1
|
||||
, make_list/1
|
||||
, make_variant/3
|
||||
, make_tuple/1
|
||||
, make_string/1
|
||||
, make_map/1
|
||||
, make_address/1
|
||||
, make_bits/1
|
||||
, make_unit/0
|
||||
, tuple_to_list/1
|
||||
, decode/1
|
||||
, encode/1
|
||||
]).
|
||||
-export([format/1]).
|
||||
|
||||
|
||||
make_integer(I) when is_integer(I) -> ?MAKE_FATE_INTEGER(I).
|
||||
make_boolean(true) -> ?FATE_TRUE;
|
||||
make_boolean(false) -> ?FATE_FALSE.
|
||||
make_list([]) -> ?FATE_NIL;
|
||||
make_list(L) -> ?MAKE_FATE_LIST(L).
|
||||
make_string(S) when is_list(S) ->
|
||||
?FATE_STRING(list_to_binary(lists:flatten(S)));
|
||||
make_string(S) when is_binary(S) -> ?FATE_STRING(S).
|
||||
make_unit() -> ?FATE_UNIT.
|
||||
make_tuple(T) -> ?FATE_TUPLE(T).
|
||||
make_map(M) -> ?MAKE_FATE_MAP(M).
|
||||
make_address(A) -> ?FATE_ADDRESS(A).
|
||||
make_bits(I) when is_integer(I) -> ?FATE_BITS(I).
|
||||
|
||||
make_variant(Size, Tag, Values) when is_integer(Size), is_integer(Tag)
|
||||
, 0 =< Size
|
||||
, 0 =< Tag
|
||||
, Tag < Size
|
||||
, is_tuple(Values) ->
|
||||
?FATE_VARIANT(Size, Tag, Values).
|
||||
|
||||
tuple_to_list(?FATE_TUPLE(T)) -> erlang:tuple_to_list(T).
|
||||
|
||||
%% Encode is a convinience function for testing, encoding an Erlang term
|
||||
%% to a Fate term, but it can not distinguish between e.g. 32-byte strings
|
||||
%% and addresses. Therfore an extra tuple layer on the erlang side for
|
||||
%% addresses and bits.
|
||||
encode({bits, Term}) when is_integer(Term) -> make_bits(Term);
|
||||
%% TODO: check that each byte is in base58
|
||||
encode({address, B}) when is_binary(B) -> make_address(B);
|
||||
encode({address, I}) when is_integer(I) -> B = <<I:256>>, make_address(B);
|
||||
encode({address, S}) when is_list(S) -> make_address(base58_to_address(S));
|
||||
encode({variant, Size, Tag, Values}) -> make_variant(Size, Tag, Values);
|
||||
encode(Term) when is_integer(Term) -> make_integer(Term);
|
||||
encode(Term) when is_boolean(Term) -> make_boolean(Term);
|
||||
encode(Term) when is_list(Term) -> make_list([encode(E) || E <- Term]);
|
||||
encode(Term) when is_tuple(Term) ->
|
||||
make_tuple(list_to_tuple([encode(E) || E <- erlang:tuple_to_list(Term)]));
|
||||
encode(Term) when is_map(Term) ->
|
||||
make_map(maps:from_list([{encode(K), encode(V)} || {K,V} <- maps:to_list(Term)]));
|
||||
encode(Term) when is_binary(Term) -> make_string(Term).
|
||||
|
||||
|
||||
|
||||
decode(I) when ?IS_FATE_INTEGER(I) -> I;
|
||||
decode(?FATE_TRUE) -> true;
|
||||
decode(?FATE_FALSE) -> false;
|
||||
decode(L) when ?IS_FATE_LIST(L) -> [decode(E) || E <- L];
|
||||
decode(?FATE_ADDRESS(<<Address:256>>)) -> {address, Address};
|
||||
decode(?FATE_BITS(Bits)) -> {bits, Bits};
|
||||
decode(?FATE_TUPLE(T)) -> erlang:list_to_tuple([decode(E) || E <- T]);
|
||||
decode(?FATE_VARIANT(Size, Tag, Values)) -> {variant, Size, Tag, Values};
|
||||
decode(S) when ?IS_FATE_STRING(S) -> binary_to_list(S);
|
||||
decode(M) when ?IS_FATE_MAP(M) ->
|
||||
maps:from_list([{decode(K), decode(V)} || {K, V} <- maps:to_list(M)]).
|
||||
|
||||
-spec format(fate_type()) -> iolist().
|
||||
format(I) when ?IS_FATE_INTEGER(I) -> integer_to_list(?MAKE_FATE_INTEGER(I));
|
||||
format(?FATE_TRUE) -> "true";
|
||||
format(?FATE_FALSE) -> "false";
|
||||
format(?FATE_NIL) -> "[]";
|
||||
format(L) when ?IS_FATE_LIST(L) -> format_list(?FATE_LIST_VALUE(L));
|
||||
format(?FATE_UNIT) -> "()";
|
||||
format(?FATE_TUPLE(T)) ->
|
||||
["( ", lists:join(", ", [ format(E) || E <- erlang:tuple_to_list(T)]), " )"];
|
||||
format(S) when ?IS_FATE_STRING(S) -> [S];
|
||||
format(?FATE_BITS(B)) when B >= 0 ->
|
||||
["<", format_bits(B, "") , ">"];
|
||||
format(?FATE_BITS(B)) when B < 0 ->
|
||||
["!< ", format_nbits(-B-1, "") , " >"];
|
||||
format(?FATE_VARIANT(Size, Tag, T)) ->
|
||||
["(| ",
|
||||
lists:join("| ", [integer_to_list(Size), integer_to_list(Tag) |
|
||||
[format(make_tuple(T))]]),
|
||||
" |)"];
|
||||
format(M) when ?IS_FATE_MAP(M) ->
|
||||
["{ ", format_kvs(maps:to_list(?FATE_MAP_VALUE(M))), " }"];
|
||||
format(?FATE_ADDRESS(Address)) -> ["#", address_to_base58(Address)];
|
||||
format(V) -> exit({not_a_fate_type, V}).
|
||||
|
||||
format_bits(0, Acc) -> Acc;
|
||||
format_bits(N, Acc) ->
|
||||
Bit = $0 + (N band 1),
|
||||
format_bits(N bsr 1, [Bit|Acc]).
|
||||
|
||||
format_nbits(0, Acc) -> Acc;
|
||||
format_nbits(N, Acc) ->
|
||||
Bit = $1 - (N band 1),
|
||||
format_nbits(N bsr 1, [Bit|Acc]).
|
||||
|
||||
format_list(List) ->
|
||||
["[ ", lists:join(", ", [format(E) || E <- List]), " ]"].
|
||||
|
||||
format_kvs(List) ->
|
||||
lists:join(", ", [ [format(K), " => ", format(V)] || {K, V} <- List]).
|
||||
|
||||
|
||||
%% -- Local base 58 library
|
||||
|
||||
base58char(Char) ->
|
||||
binary:at(<<"123456789ABCDEFGHJKLMNPQRSTUVWXYZ"
|
||||
"abcdefghijkmnopqrstuvwxyz">>, Char).
|
||||
char_to_base58(C) ->
|
||||
binary:at(<<0,1,2,3,4,5,6,7,8,0,0,0,0,0,0,0,9,10,11,12,13,14,15,16,0,17,
|
||||
18,19,20,21,0,22,23,24,25,26,27,28,29,30,31,32,0,0,0,0,0,0,
|
||||
33,34,35,36,37,38,39,40,41,42,43,0,44,45,46,47,48,49,50,51,
|
||||
52,53,54,55,56,57>>, C-$1).
|
||||
|
||||
base58_to_integer(C, []) -> C;
|
||||
base58_to_integer(C, [X | Xs]) ->
|
||||
base58_to_integer(C * 58 + char_to_base58(X), Xs).
|
||||
|
||||
base58_to_integer([]) -> error;
|
||||
base58_to_integer([Char]) -> char_to_base58(Char);
|
||||
base58_to_integer([Char | Str]) ->
|
||||
base58_to_integer(char_to_base58(Char), Str).
|
||||
|
||||
base58_to_address(Base58) ->
|
||||
I = base58_to_integer(Base58),
|
||||
Bin = <<I:256>>,
|
||||
Bin.
|
||||
|
||||
address_to_base58(<<A:256>>) ->
|
||||
integer_to_base58(A).
|
||||
|
||||
integer_to_base58(0) -> <<"1">>;
|
||||
integer_to_base58(Integer) ->
|
||||
Base58String = integer_to_base58(Integer, []),
|
||||
list_to_binary(Base58String).
|
||||
|
||||
integer_to_base58(0, Acc) -> Acc;
|
||||
integer_to_base58(Integer, Acc) ->
|
||||
Quot = Integer div 58,
|
||||
Rem = Integer rem 58,
|
||||
integer_to_base58(Quot, [base58char(Rem)|Acc]).
|
260
src/aeb_fate_encoding.erl
Normal file
260
src/aeb_fate_encoding.erl
Normal file
@ -0,0 +1,260 @@
|
||||
%% Fate data (and instruction) serialization.
|
||||
%%
|
||||
%% The FATE serialization has to fullfill the following properties:
|
||||
%% * There has to be 1 and only 1 byte sequence
|
||||
%% representing each unique value in FATE.
|
||||
%% * A valid byte sequence has to be deserializable to a FATE value.
|
||||
%% * A valid byte sequence must not contain any trailing bytes.
|
||||
%% * A serialization is a sequence of 8-bit bytes.
|
||||
%%
|
||||
%% The serialization function should fullfill the following:
|
||||
%% * A valid FATE value should be serialized to a byte sequence.
|
||||
%% * Any other argument, not representing a valid FATE value should
|
||||
%% throw an exception
|
||||
%%
|
||||
%% The deserialization function should fullfill the following:
|
||||
%% * A valid byte sequence should be deserialized to a valid FATE value.
|
||||
%% * Any other argument, not representing a valid byte sequence should
|
||||
%% throw an exception
|
||||
%%
|
||||
%% History
|
||||
%% * First draft of FATE serialization encoding/decoding.
|
||||
%% Initial experiment with tags
|
||||
%% * Second draft
|
||||
%% * FATE data is now defined in aefa_data.erl
|
||||
%% * Third draft
|
||||
%% * Added Bit strings
|
||||
%%
|
||||
%% TODO:
|
||||
%% * Make the code production ready.
|
||||
%% (add tests, document exported functions).
|
||||
%% * Handle Variant types better.
|
||||
%% * Handle type representations.
|
||||
%% * Handle instructions.
|
||||
%%
|
||||
%% ------------------------------------------------------------------------
|
||||
-module(aeb_fate_encoding).
|
||||
|
||||
-export([ deserialize/1
|
||||
, deserialize_one/1
|
||||
, serialize/1
|
||||
]).
|
||||
|
||||
-include("aeb_fate_data.hrl").
|
||||
|
||||
%% Definition of tag scheme.
|
||||
%% This has to follow the protocol specification.
|
||||
|
||||
-define(SMALL_INT , 2#0). %% sxxxxxx 0 - 6 bit integer with sign bit
|
||||
%% 1 Set below
|
||||
-define(LONG_STRING , 2#00000001). %% 000000 01 - RLP encoded array, size >= 64
|
||||
-define(SHORT_STRING , 2#01). %% xxxxxx 01 - [bytes], 0 < xxxxxx:size < 64
|
||||
%% 11 Set below
|
||||
-define(SHORT_LIST , 2#0011). %% xxxx 0011 - [encoded elements], 0 < length < 16
|
||||
%% xxxx 0111 - FREE (For typedefs in future)
|
||||
-define(LONG_TUPLE , 2#00001011). %% 0000 1011 - RLP encoded (size - 16) + [encoded elements],
|
||||
-define(SHORT_TUPLE , 2#1011). %% xxxx 1011 - [encoded elements], 0 < size < 16
|
||||
%% 1111 Set below
|
||||
-define(LONG_LIST , 2#00011111). %% 0001 1111 - RLP encoded (length - 16) + [Elements]
|
||||
-define(MAP , 2#00101111). %% 0010 1111 - RLP encoded size + [encoded key, encoded value]
|
||||
-define(EMPTY_TUPLE , 2#00111111). %% 0011 1111
|
||||
-define(POS_BITS , 2#01001111). %% 0100 1111 - RLP encoded integer (to be interpreted as bitfield)
|
||||
-define(EMPTY_STRING , 2#01011111). %% 0101 1111
|
||||
-define(POS_BIG_INT , 2#01101111). %% 0110 1111 - RLP encoded (integer - 64)
|
||||
-define(FALSE , 2#01111111). %% 0111 1111
|
||||
%% %% 1000 1111 - FREE (Possibly for bytecode in the future.)
|
||||
-define(ADDRESS , 2#10011111). %% 1001 1111 - [32 bytes]
|
||||
-define(VARIANT , 2#10101111). %% 1010 1111 - encoded size + encoded tag + encoded values
|
||||
-define(NIL , 2#10111111). %% 1011 1111 - Empty list
|
||||
-define(NEG_BITS , 2#11001111). %% 1100 1111 - RLP encoded integer (infinite 1:s bitfield)
|
||||
-define(EMPTY_MAP , 2#11011111). %% 1101 1111
|
||||
-define(NEG_BIG_INT , 2#11101111). %% 1110 1111 - RLP encoded (integer - 64)
|
||||
-define(TRUE , 2#11111111). %% 1111 1111
|
||||
|
||||
-define(SHORT_TUPLE_SIZE, 16).
|
||||
-define(SHORT_LIST_SIZE , 16).
|
||||
-define(SMALL_INT_SIZE , 64).
|
||||
-define(SHORT_STRING_SIZE, 64).
|
||||
|
||||
-define(POS_SIGN, 0).
|
||||
-define(NEG_SIGN, 1).
|
||||
|
||||
|
||||
%% --------------------------------------------------
|
||||
%% Serialize
|
||||
%% Serialized a Fate data value into a sequence of bytes
|
||||
%% according to the Fate serialization specification.
|
||||
%% TODO: The type Fate Data is not final yet.
|
||||
-spec serialize(aeb_fate_data:fate_type()) -> binary().
|
||||
serialize(?FATE_TRUE) -> <<?TRUE>>;
|
||||
serialize(?FATE_FALSE) -> <<?FALSE>>;
|
||||
serialize(?FATE_NIL) -> <<?NIL>>; %% ! Untyped
|
||||
serialize(?FATE_UNIT) -> <<?EMPTY_TUPLE>>; %% ! Untyped
|
||||
serialize(M) when ?IS_FATE_MAP(M), ?FATE_MAP_SIZE(M) =:= 0 -> <<?EMPTY_MAP>>; %% ! Untyped
|
||||
serialize(?FATE_EMPTY_STRING) -> <<?EMPTY_STRING>>;
|
||||
serialize(I) when ?IS_FATE_INTEGER(I) -> serialize_integer(I);
|
||||
serialize(?FATE_BITS(Bits)) when is_integer(Bits) -> serialize_bits(Bits);
|
||||
serialize(String) when ?IS_FATE_STRING(String),
|
||||
?FATE_STRING_SIZE(String) > 0,
|
||||
?FATE_STRING_SIZE(String) < ?SHORT_STRING_SIZE ->
|
||||
Size = ?FATE_STRING_SIZE(String),
|
||||
Bytes = ?FATE_STRING_VALUE(String),
|
||||
<<Size:6, ?SHORT_STRING:2, Bytes/binary>>;
|
||||
serialize(String) when ?IS_FATE_STRING(String),
|
||||
?FATE_STRING_SIZE(String) > 0,
|
||||
?FATE_STRING_SIZE(String) >= ?SHORT_STRING_SIZE ->
|
||||
Bytes = ?FATE_STRING_VALUE(String),
|
||||
<<?LONG_STRING, (aeser_rlp:encode(Bytes))/binary>>;
|
||||
serialize(?FATE_ADDRESS(Address)) when is_binary(Address) ->
|
||||
<<?ADDRESS, (aeser_rlp:encode(Address))/binary>>;
|
||||
serialize(?FATE_TUPLE(T)) when size(T) > 0 ->
|
||||
S = size(T),
|
||||
L = tuple_to_list(T),
|
||||
Rest = << <<(serialize(E))/binary>> || E <- L >>,
|
||||
if S < ?SHORT_TUPLE_SIZE ->
|
||||
<<S:4, ?SHORT_TUPLE:4, Rest/binary>>;
|
||||
true ->
|
||||
Size = rlp_integer(S - ?SHORT_TUPLE_SIZE),
|
||||
<<?LONG_TUPLE:8, Size/binary, Rest/binary>>
|
||||
end;
|
||||
serialize(L) when ?IS_FATE_LIST(L) ->
|
||||
[_E|_] = List = ?FATE_LIST_VALUE(L),
|
||||
S = length(List),
|
||||
Rest = << <<(serialize(El))/binary>> || El <- List >>,
|
||||
if S < ?SHORT_LIST_SIZE ->
|
||||
<<S:4, ?SHORT_LIST:4, Rest/binary>>;
|
||||
true ->
|
||||
Val = rlp_integer(S - ?SHORT_LIST_SIZE),
|
||||
<<?LONG_LIST, Val/binary, Rest/binary>>
|
||||
end;
|
||||
serialize(Map) when ?IS_FATE_MAP(Map) ->
|
||||
L = [{_K,_V}|_] = lists:sort(maps:to_list(?FATE_MAP_VALUE(Map))),
|
||||
Size = length(L),
|
||||
%% TODO: check all K same type, and all V same type
|
||||
%% check K =/= map
|
||||
Elements = << <<(serialize(K1))/binary, (serialize(V1))/binary>> || {K1,V1} <- L >>,
|
||||
<<?MAP,
|
||||
(rlp_integer(Size))/binary,
|
||||
(Elements)/binary>>;
|
||||
serialize(?FATE_VARIANT(Size, Tag, Values)) when 0 < Size, Size < 256,
|
||||
0 =< Tag, Tag < Size ->
|
||||
<<?VARIANT, Size:8, Tag:8,
|
||||
(serialize(?FATE_TUPLE(Values)))/binary
|
||||
>>.
|
||||
|
||||
|
||||
%% -----------------------------------------------------
|
||||
|
||||
rlp_integer(S) when S >= 0 ->
|
||||
aeser_rlp:encode(binary:encode_unsigned(S)).
|
||||
|
||||
serialize_integer(I) when ?IS_FATE_INTEGER(I) ->
|
||||
V = ?FATE_INTEGER_VALUE(I),
|
||||
Abs = abs(V),
|
||||
Sign = case V < 0 of
|
||||
true -> ?NEG_SIGN;
|
||||
false -> ?POS_SIGN
|
||||
end,
|
||||
if Abs < ?SMALL_INT_SIZE -> <<Sign:1, Abs:6, ?SMALL_INT:1>>;
|
||||
Sign =:= ?NEG_SIGN -> <<?NEG_BIG_INT,
|
||||
(rlp_integer(Abs - ?SMALL_INT_SIZE))/binary>>;
|
||||
Sign =:= ?POS_SIGN -> <<?POS_BIG_INT,
|
||||
(rlp_integer(Abs - ?SMALL_INT_SIZE))/binary>>
|
||||
end.
|
||||
|
||||
serialize_bits(B) when is_integer(B) ->
|
||||
Abs = abs(B),
|
||||
Sign = case B < 0 of
|
||||
true -> ?NEG_SIGN;
|
||||
false -> ?POS_SIGN
|
||||
end,
|
||||
if
|
||||
Sign =:= ?NEG_SIGN -> <<?NEG_BITS, (rlp_integer(Abs))/binary>>;
|
||||
Sign =:= ?POS_SIGN -> <<?POS_BITS, (rlp_integer(Abs))/binary>>
|
||||
end.
|
||||
|
||||
-spec deserialize(binary()) -> aeb_fate_data:fate_type().
|
||||
deserialize(B) ->
|
||||
{T, <<>>} = deserialize2(B),
|
||||
T.
|
||||
|
||||
deserialize_one(B) -> deserialize2(B).
|
||||
|
||||
deserialize2(<<?POS_SIGN:1, I:6, ?SMALL_INT:1, Rest/binary>>) ->
|
||||
{?MAKE_FATE_INTEGER(I), Rest};
|
||||
deserialize2(<<?NEG_SIGN:1, I:6, ?SMALL_INT:1, Rest/binary>>) ->
|
||||
{?MAKE_FATE_INTEGER(-I), Rest};
|
||||
deserialize2(<<?NEG_BIG_INT, Rest/binary>>) ->
|
||||
{Bint, Rest2} = aeser_rlp:decode_one(Rest),
|
||||
{?MAKE_FATE_INTEGER(-binary:decode_unsigned(Bint) - ?SMALL_INT_SIZE),
|
||||
Rest2};
|
||||
deserialize2(<<?POS_BIG_INT, Rest/binary>>) ->
|
||||
{Bint, Rest2} = aeser_rlp:decode_one(Rest),
|
||||
{?MAKE_FATE_INTEGER(binary:decode_unsigned(Bint) + ?SMALL_INT_SIZE),
|
||||
Rest2};
|
||||
deserialize2(<<?NEG_BITS, Rest/binary>>) ->
|
||||
{Bint, Rest2} = aeser_rlp:decode_one(Rest),
|
||||
{?FATE_BITS(-binary:decode_unsigned(Bint)), Rest2};
|
||||
deserialize2(<<?POS_BITS, Rest/binary>>) ->
|
||||
{Bint, Rest2} = aeser_rlp:decode_one(Rest),
|
||||
{?FATE_BITS(binary:decode_unsigned(Bint)), Rest2};
|
||||
deserialize2(<<?LONG_STRING, Rest/binary>>) ->
|
||||
{String, Rest2} = aeser_rlp:decode_one(Rest),
|
||||
{?MAKE_FATE_STRING(String), Rest2};
|
||||
deserialize2(<<S:6, ?SHORT_STRING:2, Rest/binary>>) ->
|
||||
String = binary:part(Rest, 0, S),
|
||||
Rest2 = binary:part(Rest, byte_size(Rest), - (byte_size(Rest) - S)),
|
||||
{?MAKE_FATE_STRING(String), Rest2};
|
||||
deserialize2(<<?ADDRESS, Rest/binary>>) ->
|
||||
{A, Rest2} = aeser_rlp:decode_one(Rest),
|
||||
{?FATE_ADDRESS(A), Rest2};
|
||||
deserialize2(<<?TRUE, Rest/binary>>) ->
|
||||
{?FATE_TRUE, Rest};
|
||||
deserialize2(<<?FALSE, Rest/binary>>) ->
|
||||
{?FATE_FALSE, Rest};
|
||||
deserialize2(<<?NIL, Rest/binary>>) ->
|
||||
{?FATE_NIL, Rest};
|
||||
deserialize2(<<?EMPTY_TUPLE, Rest/binary>>) ->
|
||||
{?FATE_UNIT, Rest};
|
||||
deserialize2(<<?EMPTY_MAP, Rest/binary>>) ->
|
||||
{?MAKE_FATE_MAP(#{}), Rest};
|
||||
deserialize2(<<?EMPTY_STRING, Rest/binary>>) ->
|
||||
{?FATE_EMPTY_STRING, Rest};
|
||||
deserialize2(<<?LONG_TUPLE, Rest/binary>>) ->
|
||||
{BSize, Rest1} = aeser_rlp:decode_one(Rest),
|
||||
N = binary:decode_unsigned(BSize) + ?SHORT_TUPLE_SIZE,
|
||||
{List, Rest2} = deserialize_elements(N, Rest1),
|
||||
{?FATE_TUPLE(list_to_tuple(List)), Rest2};
|
||||
deserialize2(<<S:4, ?SHORT_TUPLE:4, Rest/binary>>) ->
|
||||
{List, Rest1} = deserialize_elements(S, Rest),
|
||||
{?FATE_TUPLE(list_to_tuple(List)), Rest1};
|
||||
deserialize2(<<?LONG_LIST, Rest/binary>>) ->
|
||||
{BLength, Rest1} = aeser_rlp:decode_one(Rest),
|
||||
Length = binary:decode_unsigned(BLength) + ?SHORT_LIST_SIZE,
|
||||
{List, Rest2} = deserialize_elements(Length, Rest1),
|
||||
{?MAKE_FATE_LIST(List), Rest2};
|
||||
deserialize2(<<S:4, ?SHORT_LIST:4, Rest/binary>>) ->
|
||||
{List, Rest1} = deserialize_elements(S, Rest),
|
||||
{?MAKE_FATE_LIST(List), Rest1};
|
||||
deserialize2(<<?MAP, Rest/binary>>) ->
|
||||
{BSize, Rest1} = aeser_rlp:decode_one(Rest),
|
||||
Size = binary:decode_unsigned(BSize),
|
||||
{List, Rest2} = deserialize_elements(2*Size, Rest1),
|
||||
Map = insert_kv(List, #{}),
|
||||
{?MAKE_FATE_MAP(Map), Rest2};
|
||||
deserialize2(<<?VARIANT, Size:8, Tag:8, Rest/binary>>) ->
|
||||
if Tag > Size -> exit({too_large_tag_in_variant, Tag, Size});
|
||||
true ->
|
||||
{?FATE_TUPLE(T), Rest2} = deserialize2(Rest),
|
||||
{?FATE_VARIANT(Size, Tag, T), Rest2}
|
||||
end.
|
||||
|
||||
insert_kv([], M) -> M;
|
||||
insert_kv([K,V|R], M) -> insert_kv(R, maps:put(K, V, M)).
|
||||
|
||||
deserialize_elements(0, Rest) ->
|
||||
{[], Rest};
|
||||
deserialize_elements(N, Es) ->
|
||||
{E, Rest} = deserialize2(Es),
|
||||
{Tail, Rest2} = deserialize_elements(N-1, Rest),
|
||||
{[E|Tail], Rest2}.
|
687
src/aeb_fate_generate_ops.erl
Normal file
687
src/aeb_fate_generate_ops.erl
Normal file
@ -0,0 +1,687 @@
|
||||
-module(aeb_fate_generate_ops).
|
||||
|
||||
-export([ gen_and_halt/1
|
||||
, generate/0
|
||||
, generate_documentation/1
|
||||
, test_asm_generator/1]).
|
||||
|
||||
gen_and_halt([SrcDirArg, IncludeDirArg]) ->
|
||||
generate(atom_to_list(SrcDirArg),
|
||||
atom_to_list(IncludeDirArg)),
|
||||
halt().
|
||||
|
||||
generate() ->
|
||||
generate("src/", "include/").
|
||||
|
||||
generate(Src, Include) ->
|
||||
Ops = gen(ops_defs()),
|
||||
%% io:format("ops: ~p\n", [Ops]),
|
||||
HrlFile = Include ++ "aeb_fate_opcodes.hrl",
|
||||
generate_header_file(HrlFile, Ops),
|
||||
generate_opcodes_ops(aeb_fate_opcodes, HrlFile, Src, Ops),
|
||||
generate_code_ops(aeb_fate_code, Src, Ops),
|
||||
generate_scanner("aeb_fate_asm_scan.template", "aeb_fate_asm_scan.xrl", Src, Ops),
|
||||
gen_asm_pp(aeb_fate_pp, Src, Ops).
|
||||
|
||||
%% TODO: Some real gas numbers...
|
||||
ops_defs() ->
|
||||
%% Opname, Opcode, args, end_bb, gas, format, Constructor, Documentation
|
||||
[ { 'RETURN', 16#00, 0, true, 2, atomic, return, "Return from function call pop stack to arg0. The type of the retun value has to match the return type of the function."}
|
||||
, { 'RETURNR', 16#01, 1, true, 2, [a], returnr, "Return from function call copy Arg0 to arg0. The type of the retun value has to match the return type of the function."}
|
||||
, { 'CALL', 16#02, 1, true, 4, [is], call, "Call given function with args on stack. The types of the arguments has to match the argument typs of the function."}
|
||||
, { 'CALL_R', 16#03, 2, true, 8, [a,is], call_r, "Remote call to given contract and function. The types of the arguments has to match the argument typs of the function."}
|
||||
, { 'CALL_T', 16#04, 1, true, 4, [is], call_t, "Tail call to given function. The types of the arguments has to match the argument typs of the function. And the return type of the called function has to match the type of the current function."}
|
||||
, { 'CALL_TR', 16#05, 2, true, 8, [a,is], call_tr, "Remote tail call to given contract and function. The types of the arguments has to match the argument typs of the function. And the return type of the called function has to match the type of the current function."}
|
||||
, { 'JUMP', 16#06, 1, true, 3, [ii], jump, "Jump to a basic block. The basic block has to exist in the current function."}
|
||||
, { 'JUMPIF', 16#07, 2, true, 4, [a,ii], jumpif, "Conditional jump to a basic block. If Arg0 then jump to Arg1."}
|
||||
, { 'SWITCH_V2', 16#08, 3, true, 4, [a,ii,ii], switch, "Conditional jump to a basic block on variant tag."}
|
||||
, { 'SWITCH_V3', 16#09, 4, true, 4, [a,ii,ii,ii], switch, "Conditional jump to a basic block on variant tag."}
|
||||
, { 'SWITCH_VN', 16#0a, 2, true, 4, [a, li], switch, "Conditional jump to a basic block on variant tag."}
|
||||
, { 'PUSH', 16#0b, 1, false, 2, [a], push, "Push argument to stack."}
|
||||
, { 'DUPA', 16#0c, 0, false, 3, atomic, dup, "push copy of accumulator on stack."}
|
||||
, { 'DUP', 16#0d, 1, false, 3, [a], dup, "push Arg0 stack pos on top of stack."}
|
||||
, { 'POP', 16#0e, 1, false, 3, [a], pop, "Arg0 := top of stack."}
|
||||
, { 'STORE', 16#0f, 2, false, 3, [a,a], store, "Arg0 := Arg1."}
|
||||
, { 'INCA', 16#10, 0, false, 2, atomic, inc, "Increment accumulator."}
|
||||
, { 'INC', 16#11, 1, false, 2, [a], inc, "Increment argument."}
|
||||
, { 'DECA', 16#12, 0, false, 2, atomic, dec, "Decrement accumulator."}
|
||||
, { 'DEC', 16#13, 1, false, 2, [a], dec, "Decrement argument."}
|
||||
, { 'ADD', 16#14, 3, false, 3, [a,a,a], add, "Arg0 := Arg1 + Arg2."}
|
||||
, { 'SUB', 16#15, 3, false, 3, [a,a,a], sub, "Arg0 := Arg1 - Arg2."}
|
||||
, { 'MUL', 16#16, 3, false, 3, [a,a,a], mul, "Arg0 := Arg1 * Arg2."}
|
||||
, { 'DIV', 16#17, 3, false, 3, [a,a,a], divide, "Arg0 := Arg1 / Arg2."}
|
||||
, { 'MOD', 16#18, 3, false, 3, [a,a,a], modulo, "Arg0 := Arg1 mod Arg2."}
|
||||
, { 'POW', 16#19, 3, false, 3, [a,a,a], pow, "Arg0 := Arg1 ^ Arg2."}
|
||||
, { 'LT', 16#20, 3, false, 3, [a,a,a], lt, "Arg0 := Arg1 < Arg2."}
|
||||
, { 'GT', 16#21, 3, false, 3, [a,a,a], gt, "Arg0 := Arg1 > Arg2."}
|
||||
, { 'EQ', 16#22, 3, false, 3, [a,a,a], eq, "Arg0 := Arg1 = Arg2."}
|
||||
, { 'ELT', 16#23, 3, false, 3, [a,a,a], elt, "Arg0 := Arg1 =< Arg2."}
|
||||
, { 'EGT', 16#24, 3, false, 3, [a,a,a], egt, "Arg0 := Arg1 >= Arg2."}
|
||||
, { 'NEQ', 16#25, 3, false, 3, [a,a,a], neq, "Arg0 := Arg1 /= Arg2."}
|
||||
, { 'AND', 16#26, 3, false, 3, [a,a,a], and_op, "Arg0 := Arg1 and Arg2."}
|
||||
, { 'OR', 16#27, 3, false, 3, [a,a,a], or_op, "Arg0 := Arg1 or Arg2."}
|
||||
, { 'NOT', 16#28, 2, false, 3, [a,a], not_op, "Arg0 := not Arg1."}
|
||||
, { 'TUPLE', 16#29, 1, false, 3, [ii], tuple, "Create a tuple of size = Arg0. Elements on stack."}
|
||||
, { 'ELEMENT', 16#2a, 4, false, 3, [t,a,a,a], element_op, "Arg1 := element(Arg2, Arg3). The element should be of type Arg1"}
|
||||
, { 'MAP_EMPTY', 16#2b, 1, false, 3, [a], map_empty, "Arg0 := #{}."}
|
||||
, { 'MAP_LOOKUP', 16#2c, 3, false, 3, [a,a,a], map_lookup, "Arg0 := lookup key Arg2 in map Arg1."}
|
||||
, { 'MAP_LOOKUPD', 16#2d, 4, false, 3, [a,a,a,a], map_lookup, "Arg0 := lookup key Arg2 in map Arg1 if key exists in map otherwise Arg0 := Arg3."}
|
||||
, { 'MAP_UPDATE', 16#2e, 4, false, 3, [a,a,a,a], map_update, "Arg0 := update key Arg2 in map Arg1 with value Arg3."}
|
||||
, { 'MAP_DELETE', 16#2f, 3, false, 3, [a,a,a], map_delete, "Arg0 := delete key Arg2 from map Arg1."}
|
||||
, { 'MAP_MEMBER', 16#30, 3, false, 3, [a,a,a], map_member, "Arg0 := true if key Arg2 is in map Arg1."}
|
||||
, { 'MAP_FROM_LIST',16#31, 2, false, 3, [a,a], map_from_list, "Arg0 := make a map from (key, value) list in Arg1."}
|
||||
, { 'NIL', 16#32, 1, false, 3, [a], nil, "Arg0 := []."}
|
||||
, { 'IS_NIL', 16#33, 2, false, 3, [a,a], is_nil, "Arg0 := true if Arg1 == []."}
|
||||
, { 'CONS', 16#34, 3, false, 3, [a,a,a], cons, "Arg0 := [Arg1|Arg2]."}
|
||||
, { 'HD', 16#35, 2, false, 3, [a,a], hd, "Arg0 := head of list Arg1."}
|
||||
, { 'TL', 16#36, 2, false, 3, [a,a], tl, "Arg0 := tail of list Arg1."}
|
||||
, { 'LENGTH', 16#37, 2, false, 3, [a,a], length, "Arg0 := length of list Arg1."}
|
||||
, { 'STR_EQ', 16#38, 3, false, 3, [a,a,a], str_eq, "Arg0 := true iff the strings Arg1 and Arg2 are the same."}
|
||||
, { 'STR_JOIN', 16#39, 3, false, 3, [a,a,a], str_join, "Arg0 := string Arg1 followed by string Arg2."}
|
||||
, { 'INT_TO_STR', 16#40, 2, false, 3, [a,a], int_to_str, "Arg0 := turn integer Arg1 into a string."}
|
||||
, { 'ADDR_TO_STR', 16#41, 2, false, 3, [a,a], addr_to_str, "Arg0 := turn address Arg1 into a string."}
|
||||
, { 'STR_REVERSE', 16#42, 2, false, 3, [a,a], str_reverse, "Arg0 := the reverse of string Arg1."}
|
||||
, { 'INT_TO_ADDR', 16#43, 2, false, 3, [a,a], int_to_addr, "Arg0 := turn integer Arg1 into an address."}
|
||||
, { 'VARIANT', 16#44, 4, false, 3, [a,a,a,a], variant, "Arg0 := create a variant of size Arg1 with the tag Arg2 (Arg2 < Arg1) and take Arg3 elements from the stack."}
|
||||
, { 'VARIANT_TEST', 16#45, 3, false, 3, [a,a,a], variant_test, "Arg0 := true if variant Arg1 has the tag Arg2."}
|
||||
, { 'VARIANT_ELEMENT',16#46, 3, false, 3, [a,a,a], variant_element, "Arg0 := element number Arg2 from variant Arg1."}
|
||||
, { 'BITS_NONEA', 16#47, 0, false, 3, atomic, bits_none, "accumulator := empty bitmap."}
|
||||
, { 'BITS_NONE', 16#48, 1, false, 3, [a], bits_none, "Arg0 := empty bitmap."}
|
||||
, { 'BITS_ALLA', 16#49, 0, false, 3, atomic, bits_all, "accumulator := full bitmap."}
|
||||
, { 'BITS_ALL', 16#50, 1, false, 3, [a], bits_all, "Arg0 := full bitmap."}
|
||||
, { 'BITS_ALL_N', 16#51, 2, false, 3, [a,a], bits_all_n, "Arg0 := bitmap with Arg1 bits set."}
|
||||
, { 'BITS_SET', 16#52, 3, false, 3, [a,a,a], bits_set, "Arg0 := set bit Arg2 of bitmap Arg1."}
|
||||
, { 'BITS_CLEAR', 16#53, 3, false, 3, [a,a,a], bits_clear, "Arg0 := clear bit Arg2 of bitmap Arg1."}
|
||||
, { 'BITS_TEST', 16#54, 3, false, 3, [a,a,a], bits_test, "Arg0 := true if bit Arg2 of bitmap Arg1 is set."}
|
||||
, { 'BITS_SUM', 16#55, 2, false, 3, [a,a], bits_sum, "Arg0 := sum of set bits in bitmap Arg1. Exception if infinit bitmap."}
|
||||
, { 'BITS_OR', 16#56, 3, false, 3, [a,a,a], bits_or, "Arg0 := Arg1 v Arg2."}
|
||||
, { 'BITS_AND', 16#57, 3, false, 3, [a,a,a], bits_and, "Arg0 := Arg1 ^ Arg2."}
|
||||
, { 'BITS_DIFF', 16#58, 3, false, 3, [a,a,a], bits_diff, "Arg0 := Arg1 - Arg2."}
|
||||
, { 'ADDRESS', 16#59, 1, false, 3, [a], address, "Arg0 := The current contract address."}
|
||||
, { 'BALANCE', 16#5a, 1, false, 3, [a], balance, "Arg0 := The current contract address."}
|
||||
, { 'ORIGIN', 16#5b, 1, false, 3, [a], origin, "Arg0 := Address of contract called by the call transaction."}
|
||||
, { 'CALLER', 16#5c, 1, false, 3, [a], caller, "Arg0 := The address that signed the call transaction."}
|
||||
, { 'GASPRICE', 16#5d, 1, false, 3, [a], gasprice, "Arg0 := The current gas price."}
|
||||
, { 'BLOCKHASH', 16#5e, 1, false, 3, [a], blockhash, "Arg0 := The current blockhash."} %% TODO: Do we support has at height?
|
||||
, { 'BENEFICIARY', 16#5f, 1, false, 3, [a], beneficiary, "Arg0 := The address of the current beneficiary."}
|
||||
, { 'TIMESTAMP', 16#60, 1, false, 3, [a], timestamp, "Arg0 := The current timestamp. Unrelaiable, don't use for anything."}
|
||||
, { 'GENERATION', 16#61, 1, false, 3, [a], generation, "Arg0 := The block height of the cureent generation."}
|
||||
, { 'MICROBLOCK', 16#62, 1, false, 3, [a], microblock, "Arg0 := The current micro block number."}
|
||||
, { 'DIFFICULTY', 16#63, 1, false, 3, [a], difficulty, "Arg0 := The current difficulty."}
|
||||
, { 'GASLIMIT', 16#64, 1, false, 3, [a], gaslimit, "Arg0 := The current gaslimit."}
|
||||
, { 'GAS', 16#65, 1, false, 3, [a], gas, "Arg0 := The amount of gas left."}
|
||||
|
||||
, { 'LOG0', 16#66, 2, false, 3, [a,a], log, "Create a log message in the call object."}
|
||||
, { 'LOG1', 16#67, 3, false, 3, [a,a,a], log, "Create a log message with one topic in the call object."}
|
||||
, { 'LOG2', 16#68, 4, false, 3, [a,a,a,a], log, "Create a log message with two topics in the call object."}
|
||||
, { 'LOG3', 16#69, 5, false, 3, [a,a,a,a,a], log, "Create a log message with three topics in the call object."}
|
||||
, { 'LOG4', 16#6a, 6, false, 3, [a,a,a,a,a,a], log, "Create a log message with four topics in the call object."}
|
||||
, { 'DEACTIVATE', 16#6b, 0, false, 3, atomic, deactivate, "Mark the current contract for deactication."}
|
||||
%% Transaction ops
|
||||
, { 'SPEND', 16#6c, 2, false,3, [a,a], spend, "Transfer Arg0 tokens to account Arg1. (If the contract account has at least that many tokens."}
|
||||
, { 'ORACLE_REGISTER', 16#6d, 6, false,3, [a,a,a,a,a,a], oracle_register, "Mark the current contract for deactication."}
|
||||
%% TODO:
|
||||
, { 'ORACLE_QUERY', 16#6e, 0, false,3, atomic, oracle_query, ""}
|
||||
, { 'ORACLE_RESPOND', 16#6f, 0, false,3, atomic, oracle_respond, ""}
|
||||
, { 'ORACLE_EXTEND', 16#70, 0, false,3, atomic, oracle_extend, ""}
|
||||
, { 'ORACLE_GET_ANSWER', 16#71, 0, false,3, atomic, oracle_get_answer, ""}
|
||||
, { 'ORACLE_GET_QUESTION', 16#72, 0, false,3, atomic,oracle_get_question, ""}
|
||||
, { 'ORACLE_QUERY_FEE', 16#73, 0, false,3, atomic, oracle_query_fee, ""}
|
||||
, { 'AENS_RESOLVE', 16#74, 0, false,3, atomic, aens_resolve, ""}
|
||||
, { 'AENS_PRECLAIM', 16#75, 0, false,3, atomic, aens_preclaim, ""}
|
||||
, { 'AENS_CLAIM', 16#76, 0, false,3, atomic, aens_claim, ""}
|
||||
, { 'AENS_UPDATE', 16#77, 0, false,3, atomic, aend_update, ""}
|
||||
, { 'AENS_TRANSFER', 16#78, 0, false,3, atomic, aens_transfer, ""}
|
||||
, { 'AENS_REVOKE', 16#79, 0, false,3, atomic, aens_revoke, ""}
|
||||
, { 'ECVERIFY', 16#7a, 0, false,3, atomic, ecverify, ""}
|
||||
, { 'SHA3', 16#7b, 0, false,3, atomic, sha3, ""}
|
||||
, { 'SHA256', 16#7c, 0, false,3, atomic, sha256, ""}
|
||||
, { 'BLAKE2B', 16#7d, 0, false,3, atomic, blake2b, ""}
|
||||
|
||||
|
||||
, { 'DUMMY7ARG', 16#f9, 7, false,3, [a,a,a,a,a,a,a], dummyarg, "Temporary dummy instruction to test 7 args."}
|
||||
, { 'DUMMY8ARG', 16#fa, 8, false,3, [a,a,a,a,a,a,a,a],dummyarg, "Temporary dummy instruction to test 8 args."}
|
||||
, { 'ABORT', 16#fb, 1, false, 3, [a], abort, "Abort execution (dont use all gas) with error message in Arg0."}
|
||||
, { 'EXIT', 16#fc, 1, false, 3, [a], exit, "Abort execution (use upp all gas) with error message in Arg0."}
|
||||
, { 'NOP', 16#fd, 0, false, 1, atomic, nop, "The no op. does nothing."}
|
||||
%% FUNCTION 16#fe "Function declaration and entrypoint."
|
||||
%% EXTEND 16#ff "Reserved for future extensions beyond one byte opcodes."
|
||||
].
|
||||
|
||||
|
||||
generate_header_file(Filename, Ops) ->
|
||||
{ok, File} = file:open(Filename, [write]),
|
||||
Defines = lists:flatten([gen_defines(Op) || Op <- Ops]),
|
||||
io:format(File, "~s", [prelude("Provides opcode defines.\n")]),
|
||||
io:format(File, "%% FATE opcodes\n~s", [Defines]),
|
||||
io:format(File, "~s",
|
||||
["-define('FUNCTION' , 16#fe).\n"
|
||||
"-define('EXTEND' , 16#ff).\n\n"]),
|
||||
file:close(File).
|
||||
|
||||
generate_opcodes_ops(Modulename, HrlFile, SrcDir, Ops) ->
|
||||
Filename = SrcDir ++ atom_to_list(Modulename) ++ ".erl",
|
||||
|
||||
{ok, File} = file:open(Filename, [write]),
|
||||
Mnemonic = lists:flatten([gen_mnemonic(Op) || Op <- Ops]),
|
||||
ToOp = lists:flatten([gen_m_to_op(Op) || Op <- Ops]),
|
||||
Args = lists:flatten([gen_args(Op) || Op <- Ops]),
|
||||
EndBB = lists:flatten([gen_bb(Op) || Op <- Ops]),
|
||||
|
||||
io:format(File, "~s", [prelude("Provides opcode primitives.\n")]),
|
||||
io:format(File, "~s", [ops_exports(Modulename, HrlFile,
|
||||
["args/1\n"
|
||||
" , end_bb/1\n"
|
||||
" , mnemonic/1\n"
|
||||
" , m_to_op/1\n"
|
||||
])]),
|
||||
|
||||
io:format(File, "%% FATE mnemonics\n~s", [Mnemonic]),
|
||||
io:format(File, "mnemonic(Op) -> exit({bad_opcode, Op}).\n\n", []),
|
||||
|
||||
io:format(File, "%% FATE opcodes\n~s", [ToOp]),
|
||||
io:format(File, "m_to_op(M) -> exit({bad_mnemonic, M}).\n\n", []),
|
||||
|
||||
io:format(File, "%% FATE numbers of args to op.\n~s", [Args]),
|
||||
io:format(File, "args(Op) -> exit({bad_opcode, Op}).\n\n", []),
|
||||
|
||||
io:format(File, "%% Does FATE Op end a Basic Block?\n~s", [EndBB]),
|
||||
io:format(File, "end_bb(_) -> false.\n\n", []),
|
||||
|
||||
file:close(File).
|
||||
|
||||
generate_code_ops(Modulename, SrcDir, Ops) ->
|
||||
Filename = SrcDir ++ atom_to_list(Modulename) ++ ".erl",
|
||||
|
||||
{ok, File} = file:open(Filename, [write]),
|
||||
Types = lists:flatten([gen_type(Op) || Op <- Ops]),
|
||||
TypeExports = lists:flatten([gen_type_exports(Op) || Op <- Ops]),
|
||||
[#{type_name := FirstType} | RestOfOps] = Ops,
|
||||
FateTypes = lists:flatten([gen_fate_code_type(Op) || Op <- RestOfOps]),
|
||||
ConstructorExports = lists:flatten([gen_constructor_exports(Op) || Op <- Ops]),
|
||||
Constructors = lists:flatten([gen_constructors(Op) || Op <- Ops]),
|
||||
|
||||
io:format(File, "~s", [prelude(" Provide constructor functuions for "
|
||||
"Fate instructions.\n%%% Provide types"
|
||||
" and documentation for Fate "
|
||||
"instructions.\n")]),
|
||||
io:format(File, "-module(~w).\n\n", [Modulename]),
|
||||
io:format(File, "-include_lib(\"aebytecode/include/aeb_fate_data.hrl\").\n\n"
|
||||
"-define(i(__X__), {immediate, __X__ }).\n\n"
|
||||
"-type fate_arg_immediate(T) :: {immediate, T}.\n"
|
||||
"-type fate_arg_var() :: {var, integer()}.\n"
|
||||
"-type fate_arg_arg() :: {arg, integer()}.\n"
|
||||
"-type fate_arg_stack() :: {stack, integer()}.\n"
|
||||
"-type fate_arg() :: fate_arg_immediate()\n"
|
||||
" | fate_arg_var()\n"
|
||||
" | fate_arg_arg()\n"
|
||||
" | fate_arg_stack().\n\n"
|
||||
"-type fate_arg_immediate() :: {immediate, aeb_fate_data:fate_type()}.\n"
|
||||
, []),
|
||||
io:format(File, "~s", [Types]),
|
||||
io:format(File, "-type fate_code() :: ~s\n~s .\n\n",
|
||||
[FirstType, FateTypes]),
|
||||
io:format(File, "-export_type([ fate_code/0\n~s ]).\n\n", [TypeExports]),
|
||||
io:format(File, "-export([ foo/0\n~s ]).\n\n", [ConstructorExports]),
|
||||
io:format(File, "~s\n", [Constructors]),
|
||||
|
||||
io:format(File, "foo() -> \"A temp hack.\".\n", []),
|
||||
|
||||
file:close(File).
|
||||
|
||||
gen_type(#{type_name := TypeName, type := Type}) ->
|
||||
lists:flatten(io_lib:format("-type ~-26s :: ~s.\n",
|
||||
[TypeName, Type])).
|
||||
|
||||
gen_fate_code_type(#{type_name := TypeName}) ->
|
||||
lists:flatten(io_lib:format(" | ~s\n", [TypeName])).
|
||||
|
||||
gen_type_exports(#{type_name := TypeName}) ->
|
||||
lists:flatten(io_lib:format(" , ~s/0\n", [TypeName--"()"])).
|
||||
|
||||
gen_constructor_exports(#{constructor_type := Function}) ->
|
||||
lists:flatten(io_lib:format(" , ~s\n", [Function])).
|
||||
|
||||
gen_constructors(#{constructor := Function, format := atomic,
|
||||
type_name := Type, opname := Name}) ->
|
||||
lists:flatten(io_lib:format("-spec ~s() -> ~s.\n"
|
||||
"~s() ->\n"
|
||||
" ~w.\n\n",
|
||||
[Function, Type, Function, Name]));
|
||||
gen_constructors(#{constructor := Function, format := ArgSpec,
|
||||
type_name := Type, opname := Name}) ->
|
||||
ArgTypeSpecs = gen_arg_type_specs(ArgSpec),
|
||||
Args = gen_arg_names(0, ArgSpec),
|
||||
UseArgs = gen_arg_uses(0, ArgSpec),
|
||||
lists:flatten(io_lib:format("-spec ~s(~s) -> ~s.\n"
|
||||
"~s(~s) ->\n"
|
||||
" {~w, ~s}.\n\n",
|
||||
[Function, ArgTypeSpecs, Type,
|
||||
Function, Args, Name, UseArgs])).
|
||||
|
||||
gen_arg_type_specs([]) -> [];
|
||||
gen_arg_type_specs([a]) -> "fate_arg()";
|
||||
gen_arg_type_specs([is]) -> "aeb_fate_data:fate_string()";
|
||||
gen_arg_type_specs([ii]) -> "aeb_fate_data:fate_integer()";
|
||||
gen_arg_type_specs([li]) -> "[aeb_fate_data:fate_integer()]";
|
||||
gen_arg_type_specs([t]) -> "aeb_fate_data:fate_type_type()";
|
||||
gen_arg_type_specs([a | Args]) -> "fate_arg(), " ++ gen_arg_type_specs(Args);
|
||||
gen_arg_type_specs([is | Args]) -> "aeb_fate_data:fate_string(), " ++ gen_arg_type_specs(Args);
|
||||
gen_arg_type_specs([ii | Args]) -> "aeb_fate_data:fate_integer(), " ++ gen_arg_type_specs(Args);
|
||||
gen_arg_type_specs([li | Args]) -> "[aeb_fate_data:fate_integer()], " ++ gen_arg_type_specs(Args);
|
||||
gen_arg_type_specs([t | Args]) -> "aeb_fate_data:fate_type_type(), " ++ gen_arg_type_specs(Args).
|
||||
|
||||
|
||||
gen_arg_names(_, []) ->
|
||||
[];
|
||||
gen_arg_names(N, [_]) -> io_lib:format("Arg~w", [N]);
|
||||
gen_arg_names(N, [_|Args]) ->
|
||||
io_lib:format("Arg~w, ", [N]) ++ gen_arg_names(N+1, Args).
|
||||
|
||||
gen_arg_uses(_, []) ->
|
||||
[];
|
||||
gen_arg_uses(N, [a]) -> io_lib:format("Arg~w", [N]);
|
||||
gen_arg_uses(N, [is]) -> io_lib:format("{immediate, Arg~w}", [N]);
|
||||
gen_arg_uses(N, [ii]) -> io_lib:format("{immediate, Arg~w}", [N]);
|
||||
gen_arg_uses(N, [li]) -> io_lib:format("[{immediate, I} || I <- Arg~w]", [N]);
|
||||
gen_arg_uses(N, [t]) -> io_lib:format("Arg~w", [N]);
|
||||
gen_arg_uses(N, [a | Args]) ->
|
||||
io_lib:format("Arg~w, ", [N]) ++ gen_arg_uses(N+1, Args);
|
||||
gen_arg_uses(N, [is | Args]) ->
|
||||
io_lib:format("{immediate, Arg~w}, ", [N]) ++ gen_arg_uses(N+1, Args);
|
||||
gen_arg_uses(N, [ii | Args]) ->
|
||||
io_lib:format("{immediate, Arg~w}, ", [N]) ++ gen_arg_uses(N+1, Args);
|
||||
gen_arg_uses(N, [li | Args]) ->
|
||||
io_lib:format("[{immediate, I} || I <- Arg~w], ", [N]) ++ gen_arg_uses(N+1, Args);
|
||||
gen_arg_uses(N, [t | Args]) ->
|
||||
io_lib:format("Arg~w, ", [N]) ++ gen_arg_uses(N+1, Args).
|
||||
|
||||
|
||||
ops_exports(Module, HrlFile, Exports) ->
|
||||
lists:flatten(io_lib:format(
|
||||
"-module(~w).\n\n"
|
||||
"-export([ ~s ]).\n\n"
|
||||
"-include_lib(\"aebytecode/" ++ HrlFile ++"\").\n\n"
|
||||
"%%====================================================================\n"
|
||||
"%% API\n"
|
||||
"%%====================================================================\n",
|
||||
[Module, Exports])).
|
||||
|
||||
gen_mnemonic(#{opname := Name, macro := Macro}) ->
|
||||
lists:flatten(io_lib:format("mnemonic(~21s) -> ~21w ;\n",
|
||||
[Macro, Name])).
|
||||
|
||||
|
||||
gen_m_to_op(#{opname := Name, macro := Macro}) ->
|
||||
lists:flatten(io_lib:format("m_to_op(~21w) -> ~21s ;\n",
|
||||
[Name, Macro])).
|
||||
|
||||
gen_args(#{macro := Macro, args := Args}) ->
|
||||
lists:flatten(io_lib:format("args(~21s) -> ~2w ;\n",
|
||||
[Macro, Args])).
|
||||
|
||||
gen_bb(#{macro := Macro, end_bb := EndBB}) ->
|
||||
lists:flatten(io_lib:format("end_bb(~21s) -> ~w ;\n",
|
||||
[Macro, EndBB])).
|
||||
|
||||
|
||||
prelude(Doc) ->
|
||||
"%%%-------------------------------------------------------------------\n"
|
||||
"%%% @copyright (C) 2019, Aeternity Anstalt\n"
|
||||
"%%%\n"
|
||||
"%%% === === N O T E : This file is generated do not edit. === ===\n"
|
||||
"%%%\n"
|
||||
"%%% Source is in aeb_fate_generate_ops.erl\n"
|
||||
"%%% @doc\n"
|
||||
"%%% "++Doc++
|
||||
"%%% @end\n"
|
||||
"%%%-------------------------------------------------------------------\n\n".
|
||||
|
||||
|
||||
gen_defines(#{opname := Name, opcode := OpCode}) ->
|
||||
lists:flatten(io_lib:format("-define(~-26w, 16#~2.16.0b).\n", [Name, OpCode])).
|
||||
|
||||
gen([]) ->
|
||||
[];
|
||||
gen([{OpName, OpCode, Args, EndBB, Gas, FateFormat, Constructor, Doc} | Rest]) ->
|
||||
Name = atom_to_list(OpName),
|
||||
LowerName = string:to_lower(Name),
|
||||
TypeName = "fate_" ++ LowerName ++ "()",
|
||||
Macro = "?" ++ Name,
|
||||
Type = case FateFormat of
|
||||
atomic -> io_lib:format("~w", [OpName]);
|
||||
ArgTypes ->
|
||||
io_lib:format("{~w, ~s}", [OpName, expand_types(ArgTypes)])
|
||||
end,
|
||||
ConstructorType = atom_to_list(Constructor) ++ "/" ++ io_lib:format("~w", [Args]),
|
||||
|
||||
[#{ opname => OpName
|
||||
, opcode => OpCode
|
||||
, args => Args
|
||||
, end_bb => EndBB
|
||||
, format => FateFormat
|
||||
, macro => Macro
|
||||
, type_name => TypeName
|
||||
, doc => Doc
|
||||
, gas => Gas
|
||||
, type => Type
|
||||
, constructor => Constructor
|
||||
, constructor_type => ConstructorType
|
||||
}| gen(Rest)].
|
||||
|
||||
|
||||
expand_types([]) -> "";
|
||||
expand_types([T]) -> expand_type(T);
|
||||
expand_types([T|Ts]) ->expand_type(T) ++ ", " ++ expand_types(Ts).
|
||||
|
||||
expand_type(a) -> "fate_arg()";
|
||||
expand_type(is) -> "fate_arg_immediate(aeb_fate_data:fate_string())";
|
||||
expand_type(ii) -> "fate_arg_immediate(aeb_fate_data:fate_integer())";
|
||||
expand_type(li) -> "[fate_arg_immediate(aeb_fate_data:fate_integer())]";
|
||||
expand_type(t) -> "aeb_fate_data:fate_type_type()".
|
||||
|
||||
generate_scanner(TemplateFile, Outfile, Path, Ops) ->
|
||||
{ok, Template} = file:read_file(filename:join(Path,TemplateFile)),
|
||||
Tokens = lists:flatten([gen_token(Op) || Op <- Ops]),
|
||||
NewFile = insert_tokens_in_template(Template, Tokens),
|
||||
file:write_file(filename:join(Path, Outfile), NewFile).
|
||||
|
||||
gen_token(#{opname := OpName}) ->
|
||||
Name = atom_to_list(OpName),
|
||||
io_lib:format("~-28s: {token, {mnemonic, TokenLine, ~w}}.\n",
|
||||
[Name, OpName]).
|
||||
|
||||
insert_tokens_in_template(<<"###REPLACEWITHOPTOKENS###", Rest/binary >>, Tokens) ->
|
||||
[Tokens, Rest];
|
||||
insert_tokens_in_template(<<"###REPLACEWITHNOTE###", Rest/binary >>, Tokens) ->
|
||||
[
|
||||
"%%%\n"
|
||||
"%%% === === N O T E : This file is generated do not edit. === ===\n"
|
||||
"%%%\n"
|
||||
"%%% Source is in aeb_fate_generate_ops.erl\n"
|
||||
"%%% and aeb_fate_asm_scan.template"
|
||||
| insert_tokens_in_template(Rest, Tokens)];
|
||||
insert_tokens_in_template(<<B,Rest/binary>>, Tokens) ->
|
||||
[B|insert_tokens_in_template(Rest, Tokens)].
|
||||
|
||||
gen_asm_pp(Module, Path, Ops) ->
|
||||
Filename = filename:join(Path, atom_to_list(Module)) ++ ".erl",
|
||||
{ok, File} = file:open(Filename, [write]),
|
||||
Formats = lists:flatten([gen_format(Op)++"\n" || Op <- Ops]),
|
||||
|
||||
io:format(File, "~s", [prelude(" Provide pretty printing functuions for "
|
||||
"Fate instructions.\n")]),
|
||||
io:format(File, "-module(~w).\n\n", [Module]),
|
||||
io:format(File,
|
||||
"-export([format_op/2]).\n\n"
|
||||
"format_arg(t, T) ->\n"
|
||||
" io_lib:format(\"~~p \", [T]);\n"
|
||||
"format_arg(li, {immediate, LI}) ->\n"
|
||||
" aeb_fate_data:format(LI);\n"
|
||||
"format_arg(_, {immediate, I}) ->\n"
|
||||
" aeb_fate_data:format(I);\n"
|
||||
"format_arg(a, {arg, N}) -> io_lib:format(\"arg~~p\", [N]);\n"
|
||||
"format_arg(a, {var, N}) -> io_lib:format(\"var~~p\", [N]);\n"
|
||||
"format_arg(a, {stack, 0}) -> \"a\";\n"
|
||||
"format_arg(a, {stack, N}) -> io_lib:format(\"a~~p\", [N]).\n\n"
|
||||
"lookup(Name, Symbols) ->\n"
|
||||
" maps:get(Name, Symbols, io_lib:format(\"~~w\",[Name])).\n\n"
|
||||
"~s"
|
||||
, [Formats]),
|
||||
|
||||
io:format(File, "format_op(Op, _Symbols) -> io_lib:format(\";; Bad Op: ~~w\\n\", [Op]).\n", []),
|
||||
file:close(File).
|
||||
|
||||
gen_format(#{opname := Name}) when ('CALL' =:= Name) or (Name =:= 'CALL_T') ->
|
||||
io_lib:format("format_op({~w, {immediate, Function}}, Symbols) ->\n"
|
||||
"[\"~s \", lookup(Function, Symbols)];",
|
||||
[Name, atom_to_list(Name)]);
|
||||
gen_format(#{opname := Name}) when (Name =:= 'CALL_R') or (Name =:= 'CALL_TR') ->
|
||||
io_lib:format("format_op({~w, {immediate, Contract}, {immediate, Function}}, Symbols) ->\n"
|
||||
"[\"~s \", lookup(Contract, Symbols), \".\", lookup(Function, Symbols)];\n"
|
||||
"format_op({~w, Contract, {immediate, Function}}, Symbols) ->\n"
|
||||
"[\"~s \", format_arg(a, Contract), \".\", lookup(Function, Symbols)];",
|
||||
[Name, atom_to_list(Name), Name, atom_to_list(Name)]);
|
||||
gen_format(#{opname := Name, format := atomic}) ->
|
||||
io_lib:format("format_op(~w, _) -> [\"~s\"];", [Name, atom_to_list(Name)]);
|
||||
gen_format(#{opname := Name, format := Args}) ->
|
||||
NameAsString = atom_to_list(Name),
|
||||
case Args of
|
||||
[T0] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0)];",
|
||||
[Name, NameAsString, T0]);
|
||||
[T0, T1] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0, Arg1}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0), "
|
||||
"\" \", format_arg(~w, Arg1)];",
|
||||
[Name, NameAsString, T0, T1]);
|
||||
[T0, T1, T2] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0, Arg1, Arg2}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0), "
|
||||
"\" \", format_arg(~w, Arg1),"
|
||||
"\" \", format_arg(~w, Arg2)];",
|
||||
[Name, NameAsString, T0, T1, T2]);
|
||||
[T0, T1, T2, T3] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0, Arg1, Arg2, Arg3}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0), "
|
||||
"\" \", format_arg(~w, Arg1),"
|
||||
"\" \", format_arg(~w, Arg2),"
|
||||
"\" \", format_arg(~w, Arg3)];",
|
||||
[Name, NameAsString, T0, T1, T2, T3]);
|
||||
[T0, T1, T2, T3, T4] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0, Arg1, Arg2, Arg3, Arg4}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0), "
|
||||
"\" \", format_arg(~w, Arg1),"
|
||||
"\" \", format_arg(~w, Arg2),"
|
||||
"\" \", format_arg(~w, Arg3),"
|
||||
"\" \", format_arg(~w, Arg4)];",
|
||||
[Name, NameAsString, T0, T1, T2, T3, T4]);
|
||||
[T0, T1, T2, T3, T4, T5] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0), "
|
||||
"\" \", format_arg(~w, Arg1),"
|
||||
"\" \", format_arg(~w, Arg2),"
|
||||
"\" \", format_arg(~w, Arg3),"
|
||||
"\" \", format_arg(~w, Arg4),"
|
||||
"\" \", format_arg(~w, Arg5)];",
|
||||
[Name, NameAsString, T0, T1, T2, T3, T4, T5]);
|
||||
[T0, T1, T2, T3, T4, T5, T6] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0), "
|
||||
"\" \", format_arg(~w, Arg1),"
|
||||
"\" \", format_arg(~w, Arg2),"
|
||||
"\" \", format_arg(~w, Arg3),"
|
||||
"\" \", format_arg(~w, Arg4),"
|
||||
"\" \", format_arg(~w, Arg5),"
|
||||
"\" \", format_arg(~w, Arg6)];",
|
||||
[Name, NameAsString, T0, T1, T2, T3, T4, T5, T6]);
|
||||
[T0, T1, T2, T3, T4, T5, T6, T7] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0), "
|
||||
"\" \", format_arg(~w, Arg1),"
|
||||
"\" \", format_arg(~w, Arg2),"
|
||||
"\" \", format_arg(~w, Arg3),"
|
||||
"\" \", format_arg(~w, Arg4),"
|
||||
"\" \", format_arg(~w, Arg5),"
|
||||
"\" \", format_arg(~w, Arg6),"
|
||||
"\" \", format_arg(~w, Arg7)];",
|
||||
[Name, NameAsString, T0, T1, T2, T3, T4, T5, T6, T7])
|
||||
end.
|
||||
|
||||
test_asm_generator(Filename) ->
|
||||
{ok, File} = file:open(Filename, [write]),
|
||||
Instructions = lists:flatten([gen_instruction(Op)++"\n" || Op <- gen(ops_defs())]),
|
||||
io:format(File,
|
||||
";; CONTRACT all_instructions\n\n"
|
||||
";; Dont expect this contract to typecheck or run.\n"
|
||||
";; Just used to check assembler rountrip of all instruction.\n\n"
|
||||
"FUNCTION foo () : {tuple, []}\n"
|
||||
"~s"
|
||||
, [Instructions]),
|
||||
io:format(File, " RETURNR ()\n", []),
|
||||
file:close(File).
|
||||
|
||||
|
||||
gen_instruction(#{opname := Name, format := atomic}) ->
|
||||
io_lib:format(" ~s\n", [Name]);
|
||||
gen_instruction(#{opname := Name, format := ArgTypes}) ->
|
||||
Args = lists:flatten(lists:join(" ", [gen_arg(A) || A <- ArgTypes])),
|
||||
I = io_lib:format(" ~s ~s\n", [Name, Args]),
|
||||
I.
|
||||
|
||||
%% This should be done with a Quick Check generator...
|
||||
gen_arg(a) -> any_arg();
|
||||
gen_arg(is) -> "foo";
|
||||
gen_arg(ii) -> gen_int();
|
||||
gen_arg(li) -> "[1, 2, 3]";
|
||||
gen_arg(t) -> "integer".
|
||||
|
||||
any_arg() ->
|
||||
element(rand:uniform(5), {"a", stack_arg(), var_arg(), arg_arg(), imm_arg()}).
|
||||
stack_arg() -> "a" ++ integer_to_list(rand:uniform(255)-1).
|
||||
arg_arg() -> "arg" ++ integer_to_list(rand:uniform(256)-1).
|
||||
var_arg() -> "var" ++ integer_to_list(rand:uniform(256)-1).
|
||||
imm_arg() ->
|
||||
case rand:uniform(15) of
|
||||
1 -> gen_int();
|
||||
2 -> gen_int();
|
||||
3 -> gen_int();
|
||||
4 -> gen_int();
|
||||
5 -> gen_int();
|
||||
6 -> gen_int();
|
||||
7 -> gen_int();
|
||||
8 -> gen_address();
|
||||
9 -> gen_boolean();
|
||||
10 -> gen_string();
|
||||
11 -> gen_map();
|
||||
12 -> gen_list();
|
||||
13 -> gen_bits();
|
||||
14 -> gen_tuple();
|
||||
15 -> gen_variant()
|
||||
end.
|
||||
|
||||
gen_key() ->
|
||||
case rand:uniform(15) of
|
||||
1 -> gen_int();
|
||||
2 -> gen_int();
|
||||
3 -> gen_int();
|
||||
4 -> gen_int();
|
||||
5 -> gen_int();
|
||||
6 -> gen_int();
|
||||
7 -> gen_int();
|
||||
8 -> gen_address();
|
||||
9 -> gen_boolean();
|
||||
10 -> gen_string();
|
||||
11 -> gen_string();
|
||||
12 -> gen_list();
|
||||
13 -> gen_bits();
|
||||
14 -> gen_tuple();
|
||||
15 -> gen_variant()
|
||||
end.
|
||||
|
||||
gen_boolean() ->
|
||||
element(rand:uniform(2), {"true", "false"}).
|
||||
|
||||
gen_int() ->
|
||||
element(rand:uniform(4),
|
||||
{ integer_to_list(rand:uniform(round(math:pow(10,40))))
|
||||
, integer_to_list(rand:uniform(10))
|
||||
, integer_to_list(rand:uniform(100))
|
||||
, io_lib:format("0x~.16b",[rand:uniform(round(math:pow(10,10)))])}).
|
||||
|
||||
gen_address() -> "#nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv".
|
||||
gen_string() -> "\"foo\"".
|
||||
gen_map() -> "{ " ++ gen_key() ++ " => " ++ imm_arg() ++ "}".
|
||||
gen_list() ->
|
||||
case rand:uniform(4) of
|
||||
1 -> "[]";
|
||||
2 -> "[" ++ lists:join(", ", gen_list_elements()) ++ " ]";
|
||||
3 -> "[ " ++ imm_arg() ++ " ]";
|
||||
4 -> "[ " ++ imm_arg() ++ ", " ++ imm_arg() ++ " ]"
|
||||
end.
|
||||
|
||||
%% Not type correct.
|
||||
gen_list_elements() ->
|
||||
case rand:uniform(3) of
|
||||
1 -> [imm_arg() | gen_list_elements()];
|
||||
2 -> [];
|
||||
3 -> [imm_arg()]
|
||||
end.
|
||||
|
||||
gen_bits() ->
|
||||
element(rand:uniform(3),
|
||||
{"<>"
|
||||
,"!<>"
|
||||
, "101010"}).
|
||||
|
||||
gen_tuple() ->
|
||||
case rand:uniform(3) of
|
||||
1 -> "()";
|
||||
2 -> "(42)";
|
||||
3 -> "(" ++ imm_arg() ++ ")"
|
||||
end.
|
||||
|
||||
gen_variant() ->
|
||||
case rand:uniform(3) of
|
||||
1 -> "(| 5 | 2 | (1, \"foo\", ()) |)";
|
||||
2 -> "(| 2 | 1 | ( " ++ imm_arg() ++ " ) |)";
|
||||
3 -> "(| 2 | 0 | ( " ++ imm_arg() ++ ", " ++ imm_arg() ++ " ) |)"
|
||||
end.
|
||||
|
||||
|
||||
%% TODO: add gas cost.
|
||||
generate_documentation(Filename) ->
|
||||
{ok, File} = file:open(Filename, [write]),
|
||||
Instructions = lists:flatten([gen_doc(Op)++"\n" || Op <- gen(ops_defs())]),
|
||||
io:format(File,
|
||||
"### Operations\n\n"
|
||||
"| OpCode | Name | Args | Description |\n"
|
||||
"| --- | --- | --- | --- |\n"
|
||||
"~s"
|
||||
, [Instructions]),
|
||||
io:format(File, "\n", []),
|
||||
file:close(File).
|
||||
|
||||
gen_doc(#{ opname := Name
|
||||
, opcode := OpCode
|
||||
, args := Args
|
||||
, end_bb := EndBB
|
||||
, format := FateFormat
|
||||
, macro := Macro
|
||||
, type_name := TypeName
|
||||
, doc := Doc
|
||||
, gas := Gas
|
||||
, type := Type
|
||||
, constructor := Constructor
|
||||
, constructor_type := ConstructorType
|
||||
}) ->
|
||||
Arguments =
|
||||
case FateFormat of
|
||||
atomic -> "";
|
||||
_ -> lists:join(" ",
|
||||
[format_arg_doc(A) ||
|
||||
A <-
|
||||
lists:zip(FateFormat,
|
||||
lists:seq(0,length(FateFormat)-1))])
|
||||
end,
|
||||
io_lib:format("| 0x~.16b | ~w | ~s | ~s |\n",
|
||||
[ OpCode
|
||||
, Name
|
||||
, Arguments
|
||||
, Doc]).
|
||||
|
||||
format_arg_doc({a, N}) -> io_lib:format("Arg~w", [N]);
|
||||
format_arg_doc({is,N}) -> "Identifier";
|
||||
format_arg_doc({ii,N}) -> "Integer";
|
||||
format_arg_doc({li,N}) -> "[Integers]";
|
||||
format_arg_doc({t,N}) -> "Type".
|
||||
|
@ -1,15 +1,12 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Opcodes
|
||||
%%% @end
|
||||
%%% Updated : 22 Jan 2025
|
||||
%%% Created : 02 Oct 2017
|
||||
%%% Created : 2 Oct 2017
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(gmb_opcodes).
|
||||
-vsn("3.4.1").
|
||||
-module(aeb_opcodes).
|
||||
|
||||
-export([ dup/1
|
||||
, mnemonic/1
|
||||
@ -20,7 +17,7 @@
|
||||
, swap/1
|
||||
]).
|
||||
|
||||
-include_lib("gmbytecode/include/gmb_opcodes.hrl").
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
|
||||
|
||||
%%====================================================================
|
||||
@ -54,7 +51,6 @@ opcode(?SHL) -> ?SHL;
|
||||
opcode(?SHR) -> ?SHR;
|
||||
opcode(?SAR) -> ?SAR;
|
||||
opcode(?SHA3) -> ?SHA3;
|
||||
opcode(?CREATOR) -> ?CREATOR;
|
||||
opcode(?ADDRESS) -> ?ADDRESS;
|
||||
opcode(?BALANCE) -> ?BALANCE;
|
||||
opcode(?ORIGIN) -> ?ORIGIN;
|
||||
@ -195,7 +191,6 @@ mnemonic(?SHL) -> 'SHL' ;
|
||||
mnemonic(?SHR) -> 'SHR' ;
|
||||
mnemonic(?SAR) -> 'SAR' ;
|
||||
mnemonic(?SHA3) -> 'SHA3' ;
|
||||
mnemonic(?CREATOR) -> 'CREATOR' ;
|
||||
mnemonic(?ADDRESS) -> 'ADDRESS' ;
|
||||
mnemonic(?BALANCE) -> 'BALANCE' ;
|
||||
mnemonic(?ORIGIN) -> 'ORIGIN' ;
|
||||
@ -337,7 +332,6 @@ m_to_op('SHL') -> ?SHL ;
|
||||
m_to_op('SHR') -> ?SHR ;
|
||||
m_to_op('SAR') -> ?SAR ;
|
||||
m_to_op('SHA3') -> ?SHA3 ;
|
||||
m_to_op('CREATOR') -> ?CREATOR ;
|
||||
m_to_op('ADDRESS') -> ?ADDRESS ;
|
||||
m_to_op('BALANCE') -> ?BALANCE ;
|
||||
m_to_op('ORIGIN') -> ?ORIGIN ;
|
@ -1,21 +1,18 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Handle interaction with the gmternity chain
|
||||
%%% Handle interaction with the aeternity chain
|
||||
%%% through calls to AEternity primitive operations at address 0.
|
||||
%%% @end
|
||||
%%% Updated : 22 Jan 2025
|
||||
%%% Created : 18 Dec 2018
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(gmb_primops).
|
||||
-vsn("3.4.1").
|
||||
-module(aeb_primops).
|
||||
-export([ is_local_primop_op/1
|
||||
, op_needs_type_check/1
|
||||
]).
|
||||
|
||||
-include("gmb_opcodes.hrl").
|
||||
-include("aeb_opcodes.hrl").
|
||||
|
||||
is_local_primop_op(Op) when ?PRIM_CALL_IN_MAP_RANGE(Op) -> true;
|
||||
is_local_primop_op(Op) when ?PRIM_CALL_IN_CRYPTO_RANGE(Op) -> true;
|
@ -1,12 +1,12 @@
|
||||
{application, gmbytecode,
|
||||
[{description, "Bytecode definitions, serialization and deserialization for the Gajumaru."},
|
||||
{vsn, "3.4.1"},
|
||||
{application, aebytecode,
|
||||
[{description, "Bytecode definitions, serialization and deserialization for aeternity."},
|
||||
{vsn, "2.0.1"},
|
||||
{registered, []},
|
||||
{applications,
|
||||
[kernel,
|
||||
stdlib,
|
||||
eblake2,
|
||||
gmserialization,
|
||||
aeserialization,
|
||||
getopt
|
||||
]},
|
||||
{env,[]},
|
@ -1,5 +1,4 @@
|
||||
-module(gmfateasm).
|
||||
-vsn("3.4.1").
|
||||
-module(aefateasm).
|
||||
|
||||
-export([main/1]).
|
||||
|
||||
@ -10,7 +9,7 @@
|
||||
, {outfile, $o, "out", string, "Output file (experimental)"} ]).
|
||||
|
||||
usage() ->
|
||||
getopt:usage(?OPT_SPEC, "gmfateasm").
|
||||
getopt:usage(?OPT_SPEC, "aefateasm").
|
||||
|
||||
main(Args) ->
|
||||
case getopt:parse(?OPT_SPEC, Args) of
|
||||
@ -44,8 +43,8 @@ assemble(File, Opts) ->
|
||||
Verbose = proplists:get_value(verbose, Opts, false),
|
||||
case proplists:get_value(outfile, Opts, undefined) of
|
||||
undefined ->
|
||||
Asm = gmb_fate_asm:read_file(File),
|
||||
{Env, BC} = gmb_fate_asm:asm_to_bytecode(Asm, Opts),
|
||||
Asm = aeb_fate_asm:read_file(File),
|
||||
{Env, BC} = aeb_fate_asm:asm_to_bytecode(Asm, Opts),
|
||||
case Verbose of
|
||||
true ->
|
||||
io:format("Env: ~0p~n", [Env]);
|
||||
@ -53,6 +52,6 @@ assemble(File, Opts) ->
|
||||
end,
|
||||
io:format("Code: ~0p~n", [BC]);
|
||||
OutFile ->
|
||||
gmb_fate_asm:assemble_file(File, OutFile, Opts)
|
||||
aeb_fate_asm:assemble_file(File, OutFile, Opts)
|
||||
end.
|
||||
|
@ -1,197 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Encode and decode data and function calls according to
|
||||
%%% Sophia-AEVM-ABI
|
||||
%%% @end
|
||||
%%% Updated : 22 Jan 2025
|
||||
%%% Created : 25 Jan 2018
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(gmb_aevm_abi).
|
||||
-vsn("3.4.1").
|
||||
-define(HASH_SIZE, 32).
|
||||
|
||||
-export([ create_calldata/4
|
||||
, check_calldata/3
|
||||
, function_type_info/4
|
||||
, function_type_hash/3
|
||||
, arg_typerep_from_function/2
|
||||
, type_hash_from_function_name/2
|
||||
, typereps_from_type_hash/2
|
||||
, function_name_from_type_hash/2
|
||||
, get_function_hash_from_calldata/1
|
||||
, is_payable/2
|
||||
, abi_version/0
|
||||
]).
|
||||
|
||||
-type hash() :: <<_:256>>. %% 256 = ?HASH_SIZE * 8.
|
||||
-type function_name() :: binary(). %% String
|
||||
-type typerep() :: gmb_aevm_data:type().
|
||||
-type function_type_info() :: { FunctionHash :: hash()
|
||||
, FunctionName :: function_name()
|
||||
, Payable :: boolean()
|
||||
, ArgType :: binary() %% binary typerep
|
||||
, OutType :: binary() %% binary typerep
|
||||
}.
|
||||
-type type_info() :: [function_type_info()].
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
|
||||
%% Shall match ?ABI_AEVM_SOPHIA_1
|
||||
-spec abi_version() -> integer().
|
||||
abi_version() ->
|
||||
1.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Handle calldata
|
||||
|
||||
create_calldata(FunName, Args, ArgTypes0, RetType) ->
|
||||
ArgTypes = {tuple, ArgTypes0},
|
||||
<<TypeHashInt:?HASH_SIZE/unit:8>> =
|
||||
function_type_hash(list_to_binary(FunName), ArgTypes, RetType),
|
||||
Data = gmb_heap:to_binary({TypeHashInt, list_to_tuple(Args)}),
|
||||
{ok, Data}.
|
||||
|
||||
-spec check_calldata(binary(), type_info(), boolean()) ->
|
||||
{'ok', typerep(), typerep()} | {'error', atom()}.
|
||||
check_calldata(CallData, TypeInfo, CheckPayable) ->
|
||||
%% The first element of the CallData should be the function name
|
||||
case get_function_hash_from_calldata(CallData) of
|
||||
{ok, Hash} ->
|
||||
check_calldata(Hash, CallData, TypeInfo, CheckPayable);
|
||||
{error, _What} ->
|
||||
{error, bad_call_data}
|
||||
end.
|
||||
|
||||
check_calldata(Hash, CallData, TypeInfo, true) ->
|
||||
case is_payable(Hash, TypeInfo) of
|
||||
{ok, true} -> check_calldata(Hash, CallData, TypeInfo, false);
|
||||
{ok, false} -> {error, function_is_not_payable};
|
||||
Err = {error, _} -> Err
|
||||
end;
|
||||
check_calldata(Hash, CallData, TypeInfo, false) ->
|
||||
case typereps_from_type_hash(Hash, TypeInfo) of
|
||||
{ok, ArgType, OutType} ->
|
||||
try gmb_heap:from_binary({tuple, [word, ArgType]}, CallData) of
|
||||
{ok, _Something} ->
|
||||
{ok, {tuple, [word, ArgType]}, OutType};
|
||||
{error, _} ->
|
||||
{error, bad_call_data}
|
||||
catch
|
||||
_T:_E ->
|
||||
{error, bad_call_data}
|
||||
end;
|
||||
{error, _} ->
|
||||
{error, unknown_function}
|
||||
end.
|
||||
|
||||
|
||||
-spec get_function_hash_from_calldata(CallData::binary()) ->
|
||||
{ok, binary()} | {error, term()}.
|
||||
get_function_hash_from_calldata(CallData) ->
|
||||
case gmb_heap:from_binary({tuple, [word]}, CallData) of
|
||||
{ok, {HashInt}} -> {ok, <<HashInt:?HASH_SIZE/unit:8>>};
|
||||
{error, _} = Error -> Error
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Handle type info from contract meta data
|
||||
|
||||
-spec function_type_info(function_name(), boolean(), [typerep()], typerep()) ->
|
||||
function_type_info().
|
||||
function_type_info(Name, Payable, ArgTypes, OutType) ->
|
||||
ArgType = {tuple, ArgTypes},
|
||||
{ function_type_hash(Name, ArgType, OutType)
|
||||
, Name
|
||||
, Payable
|
||||
, gmb_heap:to_binary(ArgType)
|
||||
, gmb_heap:to_binary(OutType)
|
||||
}.
|
||||
|
||||
-spec function_type_hash(function_name(), typerep(), typerep()) -> hash().
|
||||
function_type_hash(Name, ArgType, OutType) when is_binary(Name) ->
|
||||
Bin = iolist_to_binary([ Name
|
||||
, gmb_heap:to_binary(ArgType)
|
||||
, gmb_heap:to_binary(OutType)
|
||||
]),
|
||||
%% Calculate a 256 bit digest BLAKE2b hash value of a binary
|
||||
{ok, Hash} = eblake2:blake2b(?HASH_SIZE, Bin),
|
||||
Hash.
|
||||
|
||||
-spec arg_typerep_from_function(function_name(), type_info()) ->
|
||||
{'ok', typerep()} | {'error', 'bad_type_data' | 'unknown_function'}.
|
||||
arg_typerep_from_function(Function, TypeInfo) ->
|
||||
case lists:keyfind(Function, 2, TypeInfo) of
|
||||
{_TypeHash, Function, ArgTypeBin, _OutTypeBin} ->
|
||||
arg_typerep_from_type_binary(ArgTypeBin);
|
||||
{_TypeHash, Function, _Payable, ArgTypeBin, _OutTypeBin} ->
|
||||
arg_typerep_from_type_binary(ArgTypeBin);
|
||||
false ->
|
||||
{error, unknown_function}
|
||||
end.
|
||||
|
||||
arg_typerep_from_type_binary(ArgTBin) ->
|
||||
case gmb_heap:from_binary(typerep, ArgTBin) of
|
||||
{ok, ArgT} -> {ok, ArgT};
|
||||
{error,_} -> {error, bad_type_data}
|
||||
end.
|
||||
|
||||
-spec typereps_from_type_hash(hash(), type_info()) ->
|
||||
{'ok', typerep(), typerep()} | {'error', 'bad_type_data' | 'unknown_function'}.
|
||||
typereps_from_type_hash(TypeHash, TypeInfo) ->
|
||||
case lists:keyfind(TypeHash, 1, TypeInfo) of
|
||||
{TypeHash, _Function, ArgTypeBin, OutTypeBin} ->
|
||||
typereps_from_type_binaries(ArgTypeBin, OutTypeBin);
|
||||
{TypeHash, _Function, _Payable, ArgTypeBin, OutTypeBin} ->
|
||||
typereps_from_type_binaries(ArgTypeBin, OutTypeBin);
|
||||
false ->
|
||||
{error, unknown_function}
|
||||
end.
|
||||
|
||||
typereps_from_type_binaries(ArgTBin, OutTBin) ->
|
||||
case {gmb_heap:from_binary(typerep, ArgTBin), gmb_heap:from_binary(typerep, OutTBin)} of
|
||||
{{ok, ArgT}, {ok, OutT}} -> {ok, ArgT, OutT};
|
||||
{_, _} -> {error, bad_type_data}
|
||||
end.
|
||||
|
||||
-spec function_name_from_type_hash(hash(), type_info()) ->
|
||||
{'ok', function_name()}
|
||||
| {'error', 'unknown_function'}.
|
||||
function_name_from_type_hash(TypeHash, TypeInfo) ->
|
||||
case lists:keyfind(TypeHash, 1, TypeInfo) of
|
||||
{TypeHash, Function, _ArgTypeBin, _OutTypeBin} ->
|
||||
{ok, Function};
|
||||
{TypeHash, Function, _Payable, _ArgTypeBin, _OutTypeBin} ->
|
||||
{ok, Function};
|
||||
false ->
|
||||
{error, unknown_function}
|
||||
end.
|
||||
|
||||
-spec type_hash_from_function_name(function_name(), type_info()) ->
|
||||
{'ok', hash()}
|
||||
| {'error', 'unknown_function'}.
|
||||
type_hash_from_function_name(Name, TypeInfo) ->
|
||||
case lists:keyfind(Name, 2, TypeInfo) of
|
||||
{TypeHash, Name, _ArgTypeBin, _OutTypeBin} ->
|
||||
{ok, TypeHash};
|
||||
{TypeHash, Name, _Payable, _ArgTypeBin, _OutTypeBin} ->
|
||||
{ok, TypeHash};
|
||||
false ->
|
||||
{error, unknown_function}
|
||||
end.
|
||||
|
||||
-spec is_payable(hash(), type_info()) -> {ok, boolean()} | {error, 'unknown_function'}.
|
||||
is_payable(TypeHash, TypeInfo) ->
|
||||
case lists:keyfind(TypeHash, 1, TypeInfo) of
|
||||
{TypeHash, _Function, _ArgTypeBin, _OutTypeBin} ->
|
||||
{ok, true};
|
||||
{TypeHash, _Function, Payable, _ArgTypeBin, _OutTypeBin} ->
|
||||
{ok, Payable};
|
||||
false ->
|
||||
{error, unknown_function}
|
||||
end.
|
||||
|
@ -1,31 +0,0 @@
|
||||
-module(gmb_aevm_data).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-export_type([data/0,
|
||||
type/0,
|
||||
heap/0]).
|
||||
|
||||
-type type() :: word | signed_word | string | typerep | function
|
||||
| {list, type()}
|
||||
| {option, type()}
|
||||
| {tuple, [type()]}
|
||||
| {variant, [[type()]]}.
|
||||
|
||||
|
||||
-type data() :: none
|
||||
| {some, data()}
|
||||
| {option, data()}
|
||||
| word
|
||||
| string
|
||||
| {list, data()}
|
||||
| {tuple, [data()]}
|
||||
| {variant, integer(), [data()]}
|
||||
| integer()
|
||||
| binary()
|
||||
| [data()]
|
||||
| {}
|
||||
| {data()}
|
||||
| {data(), data()}.
|
||||
|
||||
-type heap() :: binary().
|
||||
|
@ -1,82 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Encode and decode data and function calls according to
|
||||
%%% Sophia-FATE-ABI
|
||||
%%% @end
|
||||
%%% Updated : 22 Jan 2025
|
||||
%%% Created : 11 Jun 2019
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(gmb_fate_abi).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-export([ create_calldata/2
|
||||
, decode_calldata/2
|
||||
, get_function_hash_from_calldata/1
|
||||
, get_function_name_from_function_hash/2
|
||||
, get_function_type_from_function_hash/2
|
||||
, abi_version/0 ]).
|
||||
|
||||
-include("../include/gmb_fate_data.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
|
||||
%% Shall match ?ABI_FATE_SOPHIA_1
|
||||
-spec abi_version() -> integer().
|
||||
abi_version() ->
|
||||
3.
|
||||
|
||||
-spec create_calldata(list(), [term()]) -> {ok, binary()}.
|
||||
create_calldata(FunName, Args) ->
|
||||
FunctionId = gmb_fate_code:symbol_identifier(list_to_binary(FunName)),
|
||||
{ok, gmb_fate_encoding:serialize(
|
||||
gmb_fate_data:make_tuple({FunctionId,
|
||||
gmb_fate_data:make_tuple(list_to_tuple(Args))}))}.
|
||||
|
||||
-spec decode_calldata(list(), binary()) -> {ok, term()} | {error, term()}.
|
||||
decode_calldata(FunName, Calldata) ->
|
||||
FunctionId = gmb_fate_code:symbol_identifier(list_to_binary(FunName)),
|
||||
try ?FATE_TUPLE_ELEMENTS(gmb_fate_encoding:deserialize(Calldata)) of
|
||||
[FunctionId, FateArgs] -> {ok, ?FATE_TUPLE_ELEMENTS(FateArgs)};
|
||||
_ -> {error, decode_error}
|
||||
catch _:_ ->
|
||||
{error, decode_error}
|
||||
end.
|
||||
|
||||
-spec get_function_name_from_function_hash(binary(), gmb_fate_code:fcode()) ->
|
||||
{ok, term()} | {error, term()}.
|
||||
get_function_name_from_function_hash(<<SymbolHash:4/binary, _:28/binary>>, FateCode) ->
|
||||
get_function_name_from_function_hash(SymbolHash, FateCode);
|
||||
get_function_name_from_function_hash(SymbolHash = <<_:4/binary>>, FateCode) ->
|
||||
Symbols = gmb_fate_code:symbols(FateCode),
|
||||
case maps:get(SymbolHash, Symbols, undefined) of
|
||||
undefined -> {error, no_function_matching_function_hash};
|
||||
Function -> {ok, Function}
|
||||
end.
|
||||
|
||||
-spec get_function_hash_from_calldata(binary()) ->
|
||||
{ok, binary()} | {error, term()}.
|
||||
get_function_hash_from_calldata(CallData) ->
|
||||
try ?FATE_TUPLE_ELEMENTS(gmb_fate_encoding:deserialize(CallData)) of
|
||||
[FunHash, _Args] -> {ok, FunHash};
|
||||
_ -> {error, bad_calldata}
|
||||
catch _:_ ->
|
||||
{error, bad_calldata}
|
||||
end.
|
||||
|
||||
-spec get_function_type_from_function_hash(binary(), gmb_fate_code:fcode()) ->
|
||||
{ok, term(), term()} | {error, term()}.
|
||||
get_function_type_from_function_hash(<<SymbolHash:4/binary, _:28/binary>>, FateCode) ->
|
||||
get_function_type_from_function_hash(SymbolHash, FateCode);
|
||||
get_function_type_from_function_hash(SymbolHash, FateCode) ->
|
||||
Functions = gmb_fate_code:functions(FateCode),
|
||||
case maps:get(SymbolHash, Functions, undefined) of
|
||||
undefined ->
|
||||
{error, no_function_matching_function_hash};
|
||||
{_Attrs, {ArgTypes, RetType}, _Code} ->
|
||||
{ok, ArgTypes, RetType}
|
||||
end.
|
@ -1,522 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||
%%% @doc Assembler for Fate machine code.
|
||||
%%% @end
|
||||
%%%
|
||||
%%% Fate code exists in 3 formats:
|
||||
%%%
|
||||
%%% 1. Fate byte code. This format is under consensus.
|
||||
%%% 2. Fate assembler. This is a text represenation of fate code.
|
||||
%%% This is not under consensus and other
|
||||
%%% implemenation and toolchains could have
|
||||
%%% their own format.
|
||||
%%% 3. Internal. This is an Erlang representation of fate code
|
||||
%%% Used by this particular engin implementation.
|
||||
%%%
|
||||
%%% This library handles all tree representations.
|
||||
%%% The byte code format is described in a separate document.
|
||||
%%% The internal format is described in a separate document.
|
||||
%%% The text representation is described here:
|
||||
%%%
|
||||
%%% Assembler code can be read from a file.
|
||||
%%% The assembler has the following format
|
||||
%%% Comments start with 2 semicolons and runs till end of line
|
||||
%%% ;; This is a comment
|
||||
%%% Opcode mnemonics start with an upper case letter.
|
||||
%%% DUP
|
||||
%%% Identifiers start with a lower case letter
|
||||
%%% an_identifier
|
||||
%%% References to function arguments start with arg followed by an integer
|
||||
%%% arg0
|
||||
%%% References to variables/registers start with var followed by an integer
|
||||
%%% var0
|
||||
%%% References to the top of the stack is the letter a (for accumulator)
|
||||
%%% a
|
||||
%%%
|
||||
%%% Immediate values can be of 10 types:
|
||||
%%% 1a. Integers as decimals: {Digits} or -{Digits}
|
||||
%%% 42
|
||||
%%% -2374683271468723648732648736498712634876147
|
||||
%%% 1b. Integers as Hexadecimals:: 0x{Hexdigits}
|
||||
%%% 0x0deadbeef0
|
||||
%%% 2a. account addresses, a base58c encoded string prefixed with @ak_
|
||||
%%% 2b. contract address: @ct_{base58char}+
|
||||
%%% 2c. oracle address: @ok_{base58char}+
|
||||
%%% 2d. oracle query id: @oq_{base58char}+
|
||||
%%% 2e. channel address: @ch_{base58char}+
|
||||
%%% 3. Boolean true or false
|
||||
%%% true
|
||||
%%% false
|
||||
%%% 4. Strings "{Characters}"
|
||||
%%% "Hello"
|
||||
%%% 5. Map { Key => Value }
|
||||
%%% {}
|
||||
%%% { 1 => { "foo" => true, "bar" => false}
|
||||
%%% 6. Lists [ Elements ]
|
||||
%%% []
|
||||
%%% [1, 2]
|
||||
%%% 7. Bit field < Bits > or !< Bits >
|
||||
%%% <000>
|
||||
%%% <1010 1010>
|
||||
%%% <>
|
||||
%%% !<>
|
||||
%%% 8. Tuples ( Elements )
|
||||
%%% ()
|
||||
%%% (1, "foo")
|
||||
%%% 9. Variants: (| [Arities] | Tag | ( Elements ) |)
|
||||
%%% (| [0,1,2] | 2 | ( "foo", 12) |)
|
||||
%%% 10. Bytes: #{base64char}+
|
||||
%%% #AQIDCioLFQ==
|
||||
%%%
|
||||
%%% Where Digits: [0123456789]
|
||||
%%% Hexdigits: [0123456789abcdef]
|
||||
%%% base58char: [123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]
|
||||
%%% base64char: [ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxy0123456789+/=]
|
||||
%%% Characters: as a code literal - any printable ascii character 0..255 (except " no quoting yet)
|
||||
%%% the type supports an array of bytes (all values 0..255).
|
||||
%%% Key: any value except for a map
|
||||
%%% Bits: 01 or space
|
||||
%%% Elements: Nothing or Value , Elements
|
||||
%%% Size: Digits
|
||||
%%% Tag: Digits
|
||||
%%%
|
||||
%%% Updated : 22 Jan 2025
|
||||
%%% Created : 21 Dec 2017
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(gmb_fate_asm).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-export([ assemble_file/3
|
||||
, asm_to_bytecode/2
|
||||
, function_call/1
|
||||
, pp/1
|
||||
, read_file/1
|
||||
, strip/1
|
||||
, to_asm/1
|
||||
]).
|
||||
|
||||
-include_lib("gmbytecode/include/gmb_fate_opcodes.hrl").
|
||||
-include_lib("gmbytecode/include/gmb_fate_data.hrl").
|
||||
-define(HASH_BYTES, 32).
|
||||
|
||||
assemble_file(InFile, OutFile, Options) ->
|
||||
Asm = read_file(InFile),
|
||||
{_Env, BC} = asm_to_bytecode(Asm, Options),
|
||||
ok = file:write_file(OutFile, BC).
|
||||
|
||||
function_call(String) ->
|
||||
{ok, Tokens, _} = gmb_fate_asm_scan:scan(String),
|
||||
parse_function_call(Tokens).
|
||||
|
||||
parse_function_call([{id,_,Name}, {'(',_}| Rest]) ->
|
||||
{Args, []} = to_args(Rest),
|
||||
gmb_fate_encoding:serialize(
|
||||
{tuple, {mk_hash(Name), {tuple, list_to_tuple(Args)}}}).
|
||||
|
||||
|
||||
to_args([{')', _}]) -> {[], []};
|
||||
to_args(Tokens) ->
|
||||
case parse_value(Tokens) of
|
||||
{Arg, [{',', _} | Rest]} ->
|
||||
{More, Rest2} = to_args(Rest),
|
||||
{[Arg|More], Rest2};
|
||||
{Arg, [{')', _} | Rest]} ->
|
||||
{[Arg], Rest}
|
||||
end.
|
||||
|
||||
pp(FateCode) ->
|
||||
Listing = to_asm(FateCode),
|
||||
io_lib:format("~ts~n",[Listing]).
|
||||
|
||||
|
||||
to_asm(FateCode) ->
|
||||
Functions = gmb_fate_code:functions(FateCode),
|
||||
Symbols = gmb_fate_code:symbols(FateCode),
|
||||
Annotations = gmb_fate_code:annotations(FateCode),
|
||||
insert_comments(get_comments(Annotations), 1,
|
||||
lists:flatten(
|
||||
io_lib:format("~s",
|
||||
[format_functions(Functions, Symbols)]))).
|
||||
|
||||
insert_comments([{L,C}|Comments], L, String) ->
|
||||
";; " ++ C ++ "\n" ++ insert_comments(Comments, L + 1, String);
|
||||
insert_comments(Comments, L, [$\n|String]) ->
|
||||
"\n" ++ insert_comments(Comments, L+1, String);
|
||||
insert_comments(Comments, L, [C|Rest]) ->
|
||||
[C|insert_comments(Comments, L, Rest)];
|
||||
insert_comments([],_,[]) -> [];
|
||||
insert_comments([{L,C}|Rest], _, []) ->
|
||||
";; " ++ C ++ "\n" ++ insert_comments(Rest, L + 1, []).
|
||||
|
||||
format_functions(Functions, Symbols) ->
|
||||
[format(lookup(Name, Symbols),
|
||||
Sig,
|
||||
lists:sort(maps:to_list(CodeMap)),
|
||||
Symbols)
|
||||
||
|
||||
{Name, {_Attrs, Sig, CodeMap}} <- maps:to_list(Functions)].
|
||||
|
||||
|
||||
format(Name, Sig, BBs, Symbols) ->
|
||||
[ "FUNCTION "
|
||||
, Name
|
||||
, format_sig(Sig)
|
||||
, "\n"
|
||||
, format_bbs(BBs, Symbols)].
|
||||
|
||||
format_sig({Args, RetType}) ->
|
||||
[ "( "
|
||||
, format_arg_types(Args)
|
||||
, ") : "
|
||||
, format_type(RetType)].
|
||||
|
||||
format_arg_types([]) -> "";
|
||||
format_arg_types([T]) -> format_type(T);
|
||||
format_arg_types([T|Ts]) ->
|
||||
[format_type(T)
|
||||
, ", "
|
||||
, format_arg_types(Ts)].
|
||||
|
||||
format_type(T) ->
|
||||
%% TODO: Limit to ok types.
|
||||
io_lib:format("~p", [T]).
|
||||
|
||||
format_bbs([], _) ->
|
||||
[];
|
||||
format_bbs([{BB, Code}|Rest], Symbols) ->
|
||||
[ io_lib:format(" ;; BB : ~p~n", [BB])
|
||||
, format_code(Code, Symbols)
|
||||
| format_bbs(Rest, Symbols)].
|
||||
|
||||
format_code([], _) ->
|
||||
"";
|
||||
format_code([Op|Rest], Symbols) ->
|
||||
[" ",
|
||||
gmb_fate_pp:format_op(Op, Symbols),
|
||||
"\n",
|
||||
format_code(Rest, Symbols)].
|
||||
|
||||
|
||||
read_file(Filename) ->
|
||||
{ok, File} = file:read_file(Filename),
|
||||
binary_to_list(File).
|
||||
|
||||
asm_to_bytecode(AssemblerCode, Options) ->
|
||||
{ok, Tokens, _} = gmb_fate_asm_scan:scan(AssemblerCode),
|
||||
|
||||
case proplists:lookup(pp_tokens, Options) of
|
||||
{pp_tokens, true} ->
|
||||
io:format("Tokens ~p~n",[Tokens]);
|
||||
none ->
|
||||
ok
|
||||
end,
|
||||
Env = #{ fate_code => gmb_fate_code:new()
|
||||
, functions => #{}
|
||||
},
|
||||
|
||||
Env1 = to_bytecode(Tokens, none, Env, [], Options),
|
||||
FateCode = maps:get(fate_code, Env1),
|
||||
FunctionsMap = maps:get(functions, Env1),
|
||||
Functions = [X || {_, X} <- lists:sort(maps:to_list(FunctionsMap))],
|
||||
FunctionsBin = iolist_to_binary(Functions),
|
||||
ByteCode = gmb_fate_code:serialize(FateCode, FunctionsBin, Options),
|
||||
{Env, ByteCode}.
|
||||
|
||||
strip(ByteCode) ->
|
||||
{Code, _Rest} = gmser_rlp:decode_one(ByteCode),
|
||||
Code.
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
%% Parser
|
||||
%% Asm tokens -> Fate code env
|
||||
%% -------------------------------------------------------------------
|
||||
|
||||
to_bytecode([{function,_line, 'FUNCTION'}|Rest], Address, Env, Code, Opts) ->
|
||||
Env2 = insert_fun(Address, Code, Env),
|
||||
{Fun, Rest2} = to_fun_def(Rest),
|
||||
to_bytecode(Rest2, Fun, Env2, [], Opts);
|
||||
to_bytecode([{mnemonic,_line, Op}|Rest], Address, Env, Code, Opts) ->
|
||||
OpCode = gmb_fate_opcodes:m_to_op(Op),
|
||||
to_bytecode(Rest, Address, Env, [OpCode|Code], Opts);
|
||||
to_bytecode([{arg,_line, N}|Rest], Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env, [{arg, N}|Code], Opts);
|
||||
to_bytecode([{var,_line, N}|Rest], Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env, [{var, N}|Code], Opts);
|
||||
to_bytecode([{stack,_line}|Rest], Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env, [{stack, 0}|Code], Opts);
|
||||
to_bytecode([{int,_line, Int}|Rest], Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env, [{immediate, Int}|Code], Opts);
|
||||
to_bytecode([{boolean,_line, Bool}|Rest], Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env, [{immediate, Bool}|Code], Opts);
|
||||
to_bytecode([{string,_line, String}|Rest], Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env,
|
||||
[{immediate, gmb_fate_data:make_string(String)}|Code],
|
||||
Opts);
|
||||
to_bytecode([{object,_line, {address, Value}}|Rest],
|
||||
Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env,
|
||||
[{immediate, gmb_fate_data:make_address(Value)}|Code],
|
||||
Opts);
|
||||
to_bytecode([{object,_line, {contract, Value}}|Rest],
|
||||
Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env,
|
||||
[{immediate, gmb_fate_data:make_contract(Value)}|Code],
|
||||
Opts);
|
||||
to_bytecode([{object,_line, {oracle, Value}}|Rest],
|
||||
Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env,
|
||||
[{immediate, gmb_fate_data:make_oracle(Value)}|Code],
|
||||
Opts);
|
||||
to_bytecode([{object,_line, {oracle_query, Value}}|Rest],
|
||||
Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env,
|
||||
[{immediate, gmb_fate_data:make_oracle_query(Value)}|Code],
|
||||
Opts);
|
||||
to_bytecode([{object,_line, {channel, Value}}|Rest],
|
||||
Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env,
|
||||
[{immediate, gmb_fate_data:make_contract(Value)}|Code],
|
||||
Opts);
|
||||
to_bytecode([{bytes,_line, Value}|Rest],
|
||||
Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env,
|
||||
[{immediate, gmb_fate_data:make_bytes(Value)}|Code],
|
||||
Opts);
|
||||
to_bytecode([{contract_bytearray,_line, FateCode}|Rest], Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env,
|
||||
[{immediate, gmb_fate_data:make_contract_bytearray(FateCode)}|Code],
|
||||
Opts);
|
||||
to_bytecode([{id,_line, ID}|Rest], Address, Env, Code, Opts) ->
|
||||
{Env2, Id} = insert_symbol(list_to_binary(ID), Env),
|
||||
to_bytecode(Rest, Address, Env2, [{immediate, Id}|Code], Opts);
|
||||
to_bytecode([{'{',_line}|Rest], Address, Env, Code, Opts) ->
|
||||
{Map, Rest2} = parse_map(Rest),
|
||||
to_bytecode(Rest2, Address, Env, [{immediate, Map}|Code], Opts);
|
||||
to_bytecode([{'[',_line}|Rest], Address, Env, Code, Opts) ->
|
||||
{List, Rest2} = parse_list(Rest),
|
||||
to_bytecode(Rest2, Address, Env, [{immediate, List}|Code], Opts);
|
||||
to_bytecode([{'(',_line}|Rest], Address, Env, Code, Opts) ->
|
||||
{Elements, Rest2} = parse_tuple(Rest),
|
||||
Tuple = gmb_fate_data:make_tuple(list_to_tuple(Elements)),
|
||||
to_bytecode(Rest2, Address, Env, [{immediate, Tuple}|Code], Opts);
|
||||
to_bytecode([{start_variant,_line}|_] = Tokens, Address, Env, Code, Opts) ->
|
||||
{Arities, Tag, Values, Rest} = parse_variant(Tokens),
|
||||
Variant = gmb_fate_data:make_variant(Arities, Tag, Values),
|
||||
to_bytecode(Rest, Address, Env, [{immediate, Variant}|Code], Opts);
|
||||
to_bytecode([{typerep,_line}|Rest], Address, Env, Code, Opts) ->
|
||||
{Type, Rest1} = to_type(Rest),
|
||||
TypeRep = gmb_fate_data:make_typerep(Type),
|
||||
to_bytecode(Rest1, Address, Env, [{immediate, TypeRep}|Code], Opts);
|
||||
to_bytecode([{bits,_line, Bits}|Rest], Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env,
|
||||
[{immediate, gmb_fate_data:make_bits(Bits)}|Code], Opts);
|
||||
|
||||
to_bytecode([{comment, Line, Comment}|Rest], Address, Env, Code, Opts) ->
|
||||
Env2 = insert_annotation(comment, Line, Comment, Env),
|
||||
to_bytecode(Rest, Address, Env2, Code, Opts);
|
||||
|
||||
to_bytecode([], Address, Env, Code,_Opts) ->
|
||||
insert_fun(Address, Code, Env).
|
||||
|
||||
parse_map([{'}',_line}|Rest]) ->
|
||||
{#{}, Rest};
|
||||
parse_map(Tokens) ->
|
||||
{Key, [{arrow, _} | Rest]} = parse_value(Tokens),
|
||||
{Value, Rest2} = parse_value(Rest),
|
||||
case Rest2 of
|
||||
[{',',_} | Rest3] ->
|
||||
{Map, Rest4} = parse_map(Rest3),
|
||||
{Map#{Key => Value}, Rest4};
|
||||
[{'}',_} | Rest3] ->
|
||||
{#{Key => Value}, Rest3}
|
||||
end.
|
||||
|
||||
parse_list([{']',_line}|Rest]) ->
|
||||
{[], Rest};
|
||||
parse_list(Tokens) ->
|
||||
{Head , Rest} = parse_value(Tokens),
|
||||
case Rest of
|
||||
[{',',_} | Rest2] ->
|
||||
{Tail, Rest3} = parse_list(Rest2),
|
||||
{[Head | Tail], Rest3};
|
||||
[{']',_} | Rest3] ->
|
||||
{[Head], Rest3}
|
||||
end.
|
||||
|
||||
parse_tuple([{')',_line}|Rest]) ->
|
||||
{[], Rest};
|
||||
parse_tuple(Tokens) ->
|
||||
{Head , Rest} = parse_value(Tokens),
|
||||
case Rest of
|
||||
[{',',_} | Rest2] ->
|
||||
{Tail, Rest3} = parse_tuple(Rest2),
|
||||
{[Head | Tail], Rest3};
|
||||
[{')',_} | Rest3] ->
|
||||
{[Head], Rest3}
|
||||
end.
|
||||
|
||||
|
||||
parse_variant([{start_variant,_line}
|
||||
, {'[', _}
|
||||
| Rest]) ->
|
||||
{Arities, Rest2} = parse_list(Rest),
|
||||
%% Make sure Arities is a list of bytes.
|
||||
Arities = [A || A <- Arities,
|
||||
is_integer(A), A < 256],
|
||||
|
||||
[{'|',_}
|
||||
, {int,_, Tag}
|
||||
, {'|',_}
|
||||
, {'(',_} | Rest3] = Rest2,
|
||||
{Elements , [{end_variant, _} | Rest4]} = parse_tuple(Rest3),
|
||||
Size = length(Arities),
|
||||
if 0 =< Tag, Tag < Size ->
|
||||
Arity = lists:nth(Tag+1, Arities),
|
||||
if length(Elements) =:= Arity ->
|
||||
{Arities, Tag, list_to_tuple(Elements), Rest4}
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
parse_value([{int,_line, Int} | Rest]) -> {Int, Rest};
|
||||
parse_value([{boolean,_line, Bool} | Rest]) -> {Bool, Rest};
|
||||
parse_value([{'{',_line} | Rest]) -> parse_map(Rest);
|
||||
parse_value([{'[',_line} | Rest]) -> parse_list(Rest);
|
||||
parse_value([{'(',_line} | Rest]) ->
|
||||
{T, Rest2} = parse_tuple(Rest),
|
||||
{gmb_fate_data:make_tuple(list_to_tuple(T)), Rest2};
|
||||
parse_value([{bits,_line, Bits} | Rest]) ->
|
||||
{gmb_fate_data:make_bits(Bits), Rest};
|
||||
parse_value([{start_variant,_line}|_] = Tokens) ->
|
||||
{Arities, Tag, Values, Rest} = parse_variant(Tokens),
|
||||
Variant = gmb_fate_data:make_variant(Arities, Tag, Values),
|
||||
{Variant, Rest};
|
||||
parse_value([{string,_line, String} | Rest]) ->
|
||||
{gmb_fate_data:make_string(String), Rest};
|
||||
parse_value([{object,_line, {address, Address}} | Rest]) ->
|
||||
{gmb_fate_data:make_address(Address), Rest};
|
||||
parse_value([{object,_line, {contract, Address}} | Rest]) ->
|
||||
{gmb_fate_data:make_contract(Address), Rest};
|
||||
parse_value([{object,_line, {oracle, Address}} | Rest]) ->
|
||||
{gmb_fate_data:make_oracle(Address), Rest};
|
||||
parse_value([{object,_line, {oracle_query, Address}} | Rest]) ->
|
||||
{gmb_fate_data:make_oracle_query(Address), Rest};
|
||||
parse_value([{object,_line, {channel, Address}} | Rest]) ->
|
||||
{gmb_fate_data:make_channel(Address), Rest};
|
||||
parse_value([{hash,_line, Hash} | Rest]) ->
|
||||
{gmb_fate_data:make_hash(Hash), Rest};
|
||||
parse_value([{signature,_line, Hash} | Rest]) ->
|
||||
{gmb_fate_data:make_signature(Hash), Rest};
|
||||
parse_value([{typerep,_line} | Rest]) ->
|
||||
to_type(Rest).
|
||||
|
||||
to_fun_def([{id, _, Name}, {'(', _} | Rest]) ->
|
||||
{ArgsType, [{'to', _} | Rest2]} = to_arg_types(Rest),
|
||||
{RetType, Rest3} = to_type(Rest2),
|
||||
{{Name, ArgsType, RetType}, Rest3}.
|
||||
|
||||
to_arg_types([{')', _} | Rest]) -> {[], Rest};
|
||||
to_arg_types(Tokens) ->
|
||||
case to_type(Tokens) of
|
||||
{Type, [{',', _} | Rest]} ->
|
||||
{MoreTypes, Rest2} = to_arg_types(Rest),
|
||||
{[Type|MoreTypes], Rest2};
|
||||
{Type, [{')', _} | Rest]} ->
|
||||
{[Type], Rest}
|
||||
end.
|
||||
|
||||
|
||||
%% Type handling
|
||||
|
||||
to_type([{id, _, "integer"} | Rest]) -> {integer, Rest};
|
||||
to_type([{id, _, "boolean"} | Rest]) -> {boolean, Rest};
|
||||
to_type([{id, _, "string"} | Rest]) -> {string, Rest};
|
||||
to_type([{id, _, "address"} | Rest]) -> {address, Rest};
|
||||
to_type([{id, _, "contract"} | Rest]) -> {contract, Rest};
|
||||
to_type([{id, _, "oracle"} | Rest]) -> {oracle, Rest};
|
||||
to_type([{id, _, "oracle_query"} | Rest]) -> {oracle_query, Rest};
|
||||
to_type([{id, _, "name"} | Rest]) -> {name, Rest};
|
||||
to_type([{id, _, "channel"} | Rest]) -> {channel, Rest};
|
||||
to_type([{id, _, "hash"} | Rest]) -> {hash, Rest};
|
||||
to_type([{id, _, "signature"} | Rest]) -> {signature, Rest};
|
||||
to_type([{id, _, "bits"} | Rest]) -> {bits, Rest};
|
||||
to_type([{'{', _}, {id, _, "list"}, {',', _} | Rest]) ->
|
||||
%% TODO: Error handling
|
||||
{ListType, [{'}', _}| Rest2]} = to_type(Rest),
|
||||
{{list, ListType}, Rest2};
|
||||
to_type([{'{', _}, {id, _, "tuple"}, {',', _}, {'[', _} | Rest]) ->
|
||||
%% TODO: Error handling
|
||||
{ElementTypes, [{'}', _}| Rest2]} = to_list_of_types(Rest),
|
||||
{{tuple, ElementTypes}, Rest2};
|
||||
to_type([{'{', _}, {id, _, "map"}, {',', _} | Rest]) ->
|
||||
%% TODO: Error handling
|
||||
{KeyType, [{',', _}| Rest2]} = to_type(Rest),
|
||||
{ValueType, [{'}', _}| Rest3]} = to_type(Rest2),
|
||||
{{map, KeyType, ValueType}, Rest3};
|
||||
to_type([{'{', _}, {id, _, "bytes"}, {',', _}, {int, _, Size}, {'}', _} | Rest]) ->
|
||||
%% TODO: Error handling
|
||||
{{bytes, Size}, Rest};
|
||||
to_type([{'{', _}
|
||||
, {id, _, "variant"}
|
||||
, {',', _}
|
||||
, {'[', _}
|
||||
| Rest]) ->
|
||||
{ElementTypes, [{'}', _}| Rest2]} = to_list_of_types(Rest),
|
||||
{{variant, ElementTypes}, Rest2}.
|
||||
|
||||
|
||||
to_list_of_types([{']', _} | Rest]) -> {[], Rest};
|
||||
to_list_of_types(Tokens) ->
|
||||
case to_type(Tokens) of
|
||||
{Type, [{',', _} | Rest]} ->
|
||||
{MoreTypes, Rest2} = to_list_of_types(Rest),
|
||||
{[Type|MoreTypes], Rest2};
|
||||
{Type, [{']', _} | Rest]} ->
|
||||
{[Type], Rest}
|
||||
end.
|
||||
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
%% Helper functions
|
||||
%% -------------------------------------------------------------------
|
||||
|
||||
%% State handling
|
||||
|
||||
insert_fun(none, [], Env) -> Env;
|
||||
insert_fun({NameString, ArgType, RetType}, Code, #{ fate_code := FateCode
|
||||
, functions := Funs} = Env) ->
|
||||
Name = list_to_binary(NameString),
|
||||
{FateCode1, Id} = gmb_fate_code:insert_symbol(Name, FateCode),
|
||||
BodyByteCode = gmb_fate_code:serialize_code(lists:reverse(Code)),
|
||||
SigByteCode = gmb_fate_code:serialize_signature({ArgType, RetType}),
|
||||
FunByteCode = [?FUNCTION, Id, gmb_fate_encoding:serialize(0), SigByteCode, BodyByteCode],
|
||||
Env#{ functions => Funs#{ Id => FunByteCode }
|
||||
, fate_code => FateCode1}.
|
||||
|
||||
insert_symbol(Name, #{ fate_code := FateCode } = Env) ->
|
||||
{FateCode1, Id} = gmb_fate_code:insert_symbol(Name, FateCode),
|
||||
{ Env#{ fate_code => FateCode1 }
|
||||
, Id}.
|
||||
|
||||
insert_annotation(comment, Line, Comment, #{ fate_code := FateCode } = Env) ->
|
||||
FateCode1 = gmb_fate_code:insert_annotation(comment, Line, Comment, FateCode),
|
||||
Env#{ fate_code => FateCode1}.
|
||||
|
||||
mk_hash(Id) ->
|
||||
%% Use first 4 bytes of blake hash
|
||||
{ok, <<A:8, B:8, C:8, D:8,_/binary>> } = eblake2:blake2b(?HASH_BYTES, list_to_binary(Id)),
|
||||
<<A,B,C,D>>.
|
||||
|
||||
%% Handle annotations
|
||||
|
||||
get_comments(Annotations) ->
|
||||
[ {Line, Comment} ||
|
||||
{?FATE_TUPLE({?FATE_STRING_VALUE("comment"), Line}),
|
||||
?FATE_STRING_VALUE(Comment)} <- maps:to_list(Annotations)].
|
||||
|
||||
%% Symbol table handling
|
||||
|
||||
lookup(Name, Symbols) ->
|
||||
maps:get(Name, Symbols, Name).
|
@ -1,448 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% ADT for fate byte code/fate code
|
||||
%%% @end
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(gmb_fate_code).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-export([ annotations/1
|
||||
, deserialize/1
|
||||
, functions/1
|
||||
, insert_annotation/4
|
||||
, insert_fun/5
|
||||
, insert_symbol/2
|
||||
, new/0
|
||||
, serialize/1
|
||||
, serialize/2
|
||||
, serialize/3
|
||||
, serialize_code/1
|
||||
, serialize_signature/1
|
||||
, strip_init_function/1
|
||||
, symbol_identifier/1
|
||||
, symbols/1
|
||||
]).
|
||||
|
||||
-include("../include/gmb_fate_opcodes.hrl").
|
||||
-include("../include/gmb_fate_data.hrl").
|
||||
|
||||
-export([ update_annotations/2
|
||||
, update_functions/2
|
||||
, update_symbols/2]).
|
||||
|
||||
-record(fcode, { functions = #{} :: map()
|
||||
, symbols = #{} :: map()
|
||||
, annotations = #{} :: map()
|
||||
}).
|
||||
|
||||
-define(HASH_BYTES, 32).
|
||||
|
||||
-type fcode() :: #fcode{}.
|
||||
-export_type([fcode/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
|
||||
new() ->
|
||||
#fcode{}.
|
||||
|
||||
annotations(#fcode{ annotations = As }) ->
|
||||
As.
|
||||
|
||||
functions(#fcode{ functions = Fs }) ->
|
||||
Fs.
|
||||
|
||||
symbols(#fcode{ symbols = Ss}) ->
|
||||
Ss.
|
||||
|
||||
update_annotations(#fcode{ annotations = As } = FCode, Anns) ->
|
||||
FCode#fcode{ annotations = maps:merge(As, Anns) }.
|
||||
|
||||
update_functions(#fcode{ functions = Fs } = FCode, Funs) ->
|
||||
FCode#fcode{ functions = maps:merge(Fs, Funs) }.
|
||||
|
||||
update_symbols(#fcode{ symbols = Ss } = FCode, Symbs) ->
|
||||
FCode#fcode{ symbols = maps:merge(Ss, Symbs) }.
|
||||
|
||||
symbol_identifier(Bin) ->
|
||||
%% First 4 bytes of blake hash
|
||||
{ok, <<X:4/binary,_/binary>> } = eblake2:blake2b(?HASH_BYTES, Bin),
|
||||
X.
|
||||
|
||||
insert_fun(Name, Attrs, {ArgType, RetType}, #{} = BBs, FCode) ->
|
||||
{F1, ID} = insert_symbol(Name, FCode),
|
||||
update_functions(F1, #{ID => {Attrs, {ArgType, RetType}, BBs}}).
|
||||
|
||||
insert_symbol(Name, #fcode{ symbols = Syms } = F) ->
|
||||
ID = symbol_identifier(Name),
|
||||
case maps:find(ID, Syms) of
|
||||
{ok, Name} ->
|
||||
{F, ID};
|
||||
{ok, X} ->
|
||||
error({two_symbols_with_same_hash, Name, X});
|
||||
error ->
|
||||
{update_symbols(F, #{ID => Name}), ID}
|
||||
end.
|
||||
|
||||
insert_annotation(comment =_Type, Line, Comment, FCode) ->
|
||||
Key = gmb_fate_data:make_tuple({gmb_fate_data:make_string("comment"), Line}),
|
||||
Value = gmb_fate_data:make_string(Comment),
|
||||
update_annotations(FCode, #{ Key => Value }).
|
||||
|
||||
strip_init_function(#fcode{ functions = Funs,
|
||||
symbols = Syms } = FCode) ->
|
||||
Funs1 = maps:remove(?FATE_INIT_ID, Funs),
|
||||
Syms1 = maps:remove(?FATE_INIT_ID, Syms),
|
||||
FCode#fcode{ functions = Funs1, symbols = Syms1 }.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Serialization
|
||||
%%%===================================================================
|
||||
|
||||
serialize(#fcode{} = F) ->
|
||||
serialize(F, []).
|
||||
|
||||
serialize(#fcode{} = F, Options) ->
|
||||
sanity_check(F),
|
||||
serialize(F, serialize_functions(F), Options).
|
||||
|
||||
serialize(#fcode{} = F, Functions, Options) ->
|
||||
SymbolTable = serialize_symbol_table(F),
|
||||
Annotatations = serialize_annotations(F),
|
||||
ByteCode = << (gmser_rlp:encode(Functions))/binary,
|
||||
(gmser_rlp:encode(SymbolTable))/binary,
|
||||
(gmser_rlp:encode(Annotatations))/binary
|
||||
>>,
|
||||
|
||||
case proplists:lookup(pp_hex_string, Options) of
|
||||
{pp_hex_string, true} ->
|
||||
io:format("Code: ~s~n",[to_hexstring(Functions)]);
|
||||
none ->
|
||||
ok
|
||||
end,
|
||||
ByteCode.
|
||||
|
||||
to_hexstring(ByteList) ->
|
||||
"0x" ++ lists:flatten(
|
||||
[io_lib:format("~2.16.0b", [X])
|
||||
|| X <- ByteList]).
|
||||
|
||||
|
||||
serialize_functions(#fcode{ functions = Functions }) ->
|
||||
%% Sort the functions on name to get a canonical serialisation.
|
||||
iolist_to_binary(
|
||||
lists:foldr(fun({Id, {Attrs, Sig, C}}, Acc) ->
|
||||
[[?FUNCTION, Id, serialize_attributes(Attrs), serialize_signature(Sig), serialize_bbs(C)] | Acc]
|
||||
end, [], lists:sort(maps:to_list(Functions)))).
|
||||
|
||||
serialize_attributes(Attrs) ->
|
||||
AttrVal = lists:sum([ attr_value(Attr) || Attr <- Attrs ]),
|
||||
gmb_fate_encoding:serialize(?MAKE_FATE_INTEGER(AttrVal)).
|
||||
|
||||
attr_value(private) -> 1;
|
||||
attr_value(payable) -> 2.
|
||||
|
||||
serialize_signature({Args, RetType}) ->
|
||||
[gmb_fate_encoding:serialize_type({tuple, Args}) |
|
||||
gmb_fate_encoding:serialize_type(RetType)].
|
||||
|
||||
serialize_symbol_table(#fcode{ symbols = Symbols }) ->
|
||||
gmb_fate_encoding:serialize(gmb_fate_data:make_map(Symbols)).
|
||||
|
||||
serialize_annotations(#fcode{ annotations = Annotations }) ->
|
||||
gmb_fate_encoding:serialize(gmb_fate_data:make_map(Annotations)).
|
||||
|
||||
serialize_bbs(#{} = BBs) ->
|
||||
serialize_bbs(BBs, 0, []).
|
||||
|
||||
serialize_bbs(BBs, N, Acc) ->
|
||||
case maps:get(N, BBs, none) of
|
||||
none -> lists:reverse(Acc);
|
||||
BB -> serialize_bbs(BBs, N + 1, [serialize_bb(BB, [])|Acc])
|
||||
end.
|
||||
|
||||
serialize_bb([Op], Acc) ->
|
||||
lists:reverse([serialize_op(Op)|Acc]);
|
||||
serialize_bb([Op|Rest], Acc) ->
|
||||
serialize_bb(Rest, [serialize_op(Op)|Acc]).
|
||||
|
||||
serialize_op(Op) ->
|
||||
[Mnemonic|Args] =
|
||||
case is_tuple(Op) of
|
||||
true -> tuple_to_list(Op);
|
||||
false -> [Op]
|
||||
end,
|
||||
[gmb_fate_opcodes:m_to_op(Mnemonic) | serialize_code(Args)].
|
||||
|
||||
sanity_check(#fcode{ functions = Funs }) ->
|
||||
_ = [ case Def of
|
||||
{_, _, BBs} when byte_size(Id) == 4 -> sanity_check_bbs(BBs);
|
||||
_ -> error({illegal_function_id, Id})
|
||||
end || {Id, Def} <- maps:to_list(Funs) ],
|
||||
ok.
|
||||
|
||||
sanity_check_bbs(#{} = BBs) ->
|
||||
sanity_check_bbs(BBs, 0).
|
||||
|
||||
sanity_check_bbs(BBs, N) ->
|
||||
case maps:get(N, BBs, none) of
|
||||
none ->
|
||||
%% Assert that the BBs were contiguous
|
||||
case maps:size(BBs) =:= N of
|
||||
true -> ok;
|
||||
false -> error({not_contiguous_labels, lists:sort(maps:keys(BBs))})
|
||||
end;
|
||||
[] ->
|
||||
error({empty_code_block, N});
|
||||
BB ->
|
||||
sanity_check_bb(BB),
|
||||
sanity_check_bbs(BBs, N + 1)
|
||||
end.
|
||||
|
||||
sanity_check_bb([Op]) ->
|
||||
sanity_check_op(true, Op);
|
||||
sanity_check_bb([Op|Rest]) ->
|
||||
sanity_check_op(false, Op),
|
||||
sanity_check_bb(Rest).
|
||||
|
||||
sanity_check_op(IsLast, Op) ->
|
||||
[Mnemonic|Args] =
|
||||
case is_tuple(Op) of
|
||||
true -> tuple_to_list(Op);
|
||||
false -> [Op]
|
||||
end,
|
||||
safe_sanity_check(IsLast, gmb_fate_opcodes:m_to_op(Mnemonic), Args).
|
||||
|
||||
safe_sanity_check(IsLast, Op, Args) ->
|
||||
case length(Args) == gmb_fate_opcodes:args(Op) of
|
||||
true ->
|
||||
case IsLast == gmb_fate_opcodes:end_bb(Op) of
|
||||
true -> ok;
|
||||
false -> error({wrong_opcode_in_bb, Op})
|
||||
end;
|
||||
false -> error({wrong_nr_args_opcode, Op})
|
||||
end.
|
||||
|
||||
|
||||
%% Argument encoding
|
||||
%% Argument Specification Byte
|
||||
%% bitpos: 6 4 2 0
|
||||
%% xx xx xx xx
|
||||
%% Arg3 Arg2 Arg1 Arg0
|
||||
%% For 5-8 args another Argument Spec Byte is used
|
||||
%% bitpos: 6 4 2 0 | 6 4 2 0
|
||||
%% xx xx xx xx | xx xx xx xx
|
||||
%% Arg7 Arg6 Arg5 Arg4 | Arg3 Arg2 Arg1 Arg0
|
||||
%% Bit pattern
|
||||
%% 00 : stack/unused (depending on instruction)
|
||||
%% 01 : argN
|
||||
%% 10 : varN
|
||||
%% 11 : immediate
|
||||
|
||||
serialize_code([{_,_}|_] = List ) ->
|
||||
%% Take out the full argument list.
|
||||
{Args, Rest} = lists:splitwith(fun({_, _}) -> true; (_) -> false end, List),
|
||||
%% Create the appropriate number of modifier bytes.
|
||||
Mods = << <<(modifier_bits(Type, X)):2>> || {Type, X} <- pad_args(lists:reverse(Args)) >>,
|
||||
case Mods of
|
||||
<<M1:8, M2:8>> ->
|
||||
[M1, M2 | [serialize_data(Type, Arg) || {Type, Arg} <- Args, Type =/= stack]] ++
|
||||
serialize_code(Rest);
|
||||
<<M1:8>> ->
|
||||
[M1 | [serialize_data(Type, Arg) || {Type, Arg} <- Args, Type =/= stack]] ++
|
||||
serialize_code(Rest)
|
||||
end;
|
||||
serialize_code([Op|Rest]) ->
|
||||
[Op|serialize_code(Rest)];
|
||||
serialize_code([]) ->
|
||||
[].
|
||||
|
||||
pad_args(List) ->
|
||||
case length(List) of
|
||||
0 -> List;
|
||||
N when N =< 4 ->
|
||||
lists:duplicate(4 - N, {stack, 0}) ++ List;
|
||||
N when N =< 8 ->
|
||||
lists:duplicate(8 - N, {stack, 0}) ++ List
|
||||
end.
|
||||
|
||||
serialize_data(_, Data) ->
|
||||
gmb_fate_encoding:serialize(Data).
|
||||
|
||||
%% 00 : stack/unused (depending on instruction)
|
||||
%% 01 : argN
|
||||
%% 10 : varN
|
||||
%% 11 : immediate
|
||||
modifier_bits(immediate, _) -> 2#11;
|
||||
modifier_bits(var, _) -> 2#10;
|
||||
modifier_bits(arg, _) -> 2#01;
|
||||
modifier_bits(stack, 0) -> 2#00;
|
||||
modifier_bits(Type, X) -> error({illegal_argument, Type, X}).
|
||||
|
||||
bits_to_modifier(2#11) -> immediate;
|
||||
bits_to_modifier(2#10) -> var;
|
||||
bits_to_modifier(2#01) -> arg;
|
||||
bits_to_modifier(2#00) -> stack.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Deserialization
|
||||
%%%===================================================================
|
||||
|
||||
deserialize(Bytes) ->
|
||||
{ByteCode, Rest1} = gmser_rlp:decode_one(Bytes),
|
||||
{SymbolTable, Rest2} = gmser_rlp:decode_one(Rest1),
|
||||
{Annotations, <<>>} = gmser_rlp:decode_one(Rest2),
|
||||
|
||||
Env = #{ function => none
|
||||
, bb => 0
|
||||
, current_bb_code => []
|
||||
, functions => #{}
|
||||
, code => #{}
|
||||
},
|
||||
Fcode =
|
||||
#fcode{ functions = deserialize_functions(ByteCode, Env)
|
||||
, annotations = deserialize_annotations(Annotations)
|
||||
, symbols = deserialize_symbols(SymbolTable)
|
||||
},
|
||||
sanity_check(Fcode),
|
||||
Fcode.
|
||||
|
||||
|
||||
deserialize_functions(<<?FUNCTION:8, A, B, C, D, Rest/binary>>,
|
||||
#{ function := none
|
||||
, bb := 0
|
||||
, current_bb_code := []
|
||||
} = Env) ->
|
||||
{Attrs, Rest2} = deserialize_attributes(Rest),
|
||||
{Sig, Rest3} = deserialize_signature(Rest2),
|
||||
Env2 = Env#{function => {<<A,B,C,D>>, Attrs, Sig}},
|
||||
deserialize_functions(Rest3, Env2);
|
||||
deserialize_functions(<<?FUNCTION:8, A, B, C, D, Rest/binary>>,
|
||||
#{ function := {F, Attrs, Sig}
|
||||
, bb := BB
|
||||
, current_bb_code := Code
|
||||
, code := Program
|
||||
, functions := Funs} = Env) ->
|
||||
{NewAttrs, Rest2} = deserialize_attributes(Rest),
|
||||
{NewSig, Rest3} = deserialize_signature(Rest2),
|
||||
case Code of
|
||||
[] ->
|
||||
Env2 = Env#{ bb => 0
|
||||
, current_bb_code => []
|
||||
, function => {<<A,B,C,D>>, NewAttrs, NewSig}
|
||||
, code => #{}
|
||||
, functions => Funs#{F => {Attrs, Sig, Program}}},
|
||||
deserialize_functions(Rest3, Env2);
|
||||
_ ->
|
||||
Env2 = Env#{ bb => 0
|
||||
, current_bb_code => []
|
||||
, function => {<<A,B,C,D>>, NewAttrs, NewSig}
|
||||
, code => #{}
|
||||
, functions =>
|
||||
Funs#{F => {Attrs, Sig,
|
||||
Program#{ BB => lists:reverse(Code)}}}},
|
||||
deserialize_functions(Rest3, Env2)
|
||||
end;
|
||||
deserialize_functions(<<_Op:8, _Rest/binary>>,
|
||||
#{ function := none }) ->
|
||||
error({code_without_function});
|
||||
deserialize_functions(<<Op:8, Rest/binary>>,
|
||||
#{ bb := BB
|
||||
, current_bb_code := Code
|
||||
, code := Program} = Env) ->
|
||||
{Rest2, OpCode} = deserialize_op(Op, Rest, Code),
|
||||
case gmb_fate_opcodes:end_bb(Op) of
|
||||
true ->
|
||||
deserialize_functions(Rest2, Env#{ bb => BB+1
|
||||
, current_bb_code => []
|
||||
, code => Program#{BB =>
|
||||
lists:reverse(OpCode)}});
|
||||
false ->
|
||||
deserialize_functions(Rest2, Env#{ current_bb_code => OpCode})
|
||||
end;
|
||||
deserialize_functions(<<>>, #{ function := none
|
||||
, functions := Funs}) ->
|
||||
Funs;
|
||||
deserialize_functions(<<>>, #{ function := {F, Attrs, Sig}
|
||||
, bb := BB
|
||||
, current_bb_code := Code
|
||||
, code := Program
|
||||
, functions := Funs}) ->
|
||||
FunctionCode =
|
||||
case Code of
|
||||
[] -> Program;
|
||||
_ -> Program#{ BB => lists:reverse(Code)}
|
||||
end,
|
||||
Funs#{F => {Attrs, Sig, FunctionCode}}.
|
||||
|
||||
deserialize_op(Op, Rest, Code) ->
|
||||
OpName = gmb_fate_opcodes:mnemonic(Op),
|
||||
case gmb_fate_opcodes:args(Op) of
|
||||
0 ->
|
||||
{Rest, [OpName | Code]};
|
||||
N ->
|
||||
{Args, Rest1} = deserialize_n_args(N, Rest),
|
||||
{Rest1, [list_to_tuple([OpName|Args])|Code]}
|
||||
end.
|
||||
|
||||
deserialize_n_args(N, <<M3:2, M2:2, M1:2, M0:2, Rest/binary>>) when N =< 4 ->
|
||||
{ArgMods, Zeros} = lists:split(N, [M0, M1, M2, M3]),
|
||||
assert_zero(Zeros),
|
||||
lists:mapfoldl(fun(M, Acc) ->
|
||||
case bits_to_modifier(M) of
|
||||
stack ->
|
||||
{{stack, 0}, Acc};
|
||||
Modifier ->
|
||||
{Arg, Acc2} = gmb_fate_encoding:deserialize_one(Acc),
|
||||
{{Modifier, Arg}, Acc2}
|
||||
end
|
||||
end, Rest, ArgMods);
|
||||
deserialize_n_args(N, <<M7:2, M6:2, M5:2, M4:2, M3:2, M2:2, M1:2, M0:2,
|
||||
Rest/binary>>) when N =< 8 ->
|
||||
{ArgMods, Zeros} = lists:split(N, [M0, M1, M2, M3, M4, M5, M6, M7]),
|
||||
assert_zero(Zeros),
|
||||
lists:mapfoldl(fun(M, Acc) ->
|
||||
case bits_to_modifier(M) of
|
||||
stack ->
|
||||
{{stack, 0}, Acc};
|
||||
Modifier ->
|
||||
{Arg, Acc2} = gmb_fate_encoding:deserialize_one(Acc),
|
||||
{{Modifier, Arg}, Acc2}
|
||||
end
|
||||
end, Rest, ArgMods).
|
||||
|
||||
deserialize_attributes(Binary) ->
|
||||
{AttrVal, Rest} = gmb_fate_encoding:deserialize_one(Binary),
|
||||
Attrs = [ attr(AVal) || AVal <- attr_vals(1, AttrVal) ],
|
||||
{lists:sort(Attrs), Rest}.
|
||||
|
||||
attr_vals(_, 0) -> [];
|
||||
attr_vals(X, N) when N rem 2 == 0 -> attr_vals(X + 1, N div 2);
|
||||
attr_vals(X, N) -> [X | attr_vals(X + 1, N div 2)].
|
||||
|
||||
attr(1) -> private;
|
||||
attr(2) -> payable.
|
||||
|
||||
deserialize_signature(Binary) ->
|
||||
{{tuple, Args}, Rest} = gmb_fate_encoding:deserialize_type(Binary),
|
||||
{RetType, Rest2} = gmb_fate_encoding:deserialize_type(Rest),
|
||||
{{Args, RetType}, Rest2}.
|
||||
|
||||
deserialize_symbols(Table) ->
|
||||
?FATE_MAP_VALUE(SymbolTable) = gmb_fate_encoding:deserialize(Table),
|
||||
SymbolTable.
|
||||
|
||||
deserialize_annotations(AnnotationsBin) ->
|
||||
?FATE_MAP_VALUE(Annotations) = gmb_fate_encoding:deserialize(AnnotationsBin),
|
||||
Annotations.
|
||||
|
||||
assert_zero([]) ->
|
||||
true;
|
||||
assert_zero([0|Rest]) ->
|
||||
assert_zero(Rest);
|
||||
assert_zero([_|_]) ->
|
||||
error(argument_defined_outside_range).
|
@ -1,396 +0,0 @@
|
||||
%% FATE data representation.
|
||||
%%
|
||||
-include("gmb_fate_data.hrl").
|
||||
|
||||
-module(gmb_fate_data).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-type fate_integer() :: ?FATE_INTEGER_T.
|
||||
-type fate_boolean() :: ?FATE_BOOLEAN_T.
|
||||
-type fate_nil() :: ?FATE_NIL_T.
|
||||
-type fate_list() :: ?FATE_LIST_T.
|
||||
-type fate_unit() :: ?FATE_UNIT_T.
|
||||
-type fate_map() :: ?FATE_MAP_T.
|
||||
-type fate_store_map() :: ?FATE_STORE_MAP_T.
|
||||
-type fate_string() :: ?FATE_STRING_T.
|
||||
-type fate_address() :: ?FATE_ADDRESS_T.
|
||||
-type fate_hash() :: ?FATE_BYTES_T(32).
|
||||
-type fate_signature() :: ?FATE_BYTES_T(64).
|
||||
-type fate_contract() :: ?FATE_CONTRACT_T.
|
||||
-type fate_oracle() :: ?FATE_ORACLE_T.
|
||||
-type fate_oracle_q() :: ?FATE_ORACLE_Q_T.
|
||||
-type fate_channel() :: ?FATE_CHANNEL_T.
|
||||
-type fate_variant() :: ?FATE_VARIANT_T.
|
||||
-type fate_tuple() :: ?FATE_TUPLE_T.
|
||||
-type fate_bits() :: ?FATE_BITS_T.
|
||||
-type fate_typerep() :: ?FATE_TYPEREP_T.
|
||||
-type fate_contract_bytearray() :: ?FATE_CONTRACT_BYTEARRAY_T.
|
||||
|
||||
-type fate_type_type() :: integer
|
||||
| boolean
|
||||
| {list, fate_type_type()}
|
||||
| {map, fate_type_type(), fate_type_type()}
|
||||
| {tuple, [fate_type_type()]}
|
||||
| address
|
||||
| hash
|
||||
| signature
|
||||
| contract
|
||||
| oracle
|
||||
| oracle_query
|
||||
| channel
|
||||
| bits
|
||||
| string
|
||||
| {variant, [fate_type_type()]}
|
||||
| contract_bytearray.
|
||||
|
||||
|
||||
-type fate_type() ::
|
||||
fate_boolean()
|
||||
| fate_integer()
|
||||
| fate_nil()
|
||||
| fate_list()
|
||||
| fate_unit()
|
||||
| fate_tuple()
|
||||
| fate_string()
|
||||
| fate_address()
|
||||
| fate_hash()
|
||||
| fate_signature()
|
||||
| fate_contract()
|
||||
| fate_oracle()
|
||||
| fate_oracle_q()
|
||||
| fate_channel()
|
||||
| fate_variant()
|
||||
| fate_map()
|
||||
| fate_bits()
|
||||
| fate_typerep()
|
||||
| fate_contract_bytearray().
|
||||
|
||||
-export_type([fate_type/0
|
||||
, fate_boolean/0
|
||||
, fate_integer/0
|
||||
, fate_nil/0
|
||||
, fate_list/0
|
||||
, fate_unit/0
|
||||
, fate_tuple/0
|
||||
, fate_string/0
|
||||
, fate_address/0
|
||||
, fate_hash/0
|
||||
, fate_signature/0
|
||||
, fate_contract/0
|
||||
, fate_oracle/0
|
||||
, fate_channel/0
|
||||
, fate_variant/0
|
||||
, fate_map/0
|
||||
, fate_store_map/0
|
||||
, fate_bits/0
|
||||
, fate_type_type/0
|
||||
]).
|
||||
|
||||
-export([ make_integer/1
|
||||
, make_boolean/1
|
||||
, make_list/1
|
||||
, make_variant/3
|
||||
, make_tuple/1
|
||||
, make_string/1
|
||||
, make_map/1
|
||||
, make_store_map/1
|
||||
, make_store_map/2
|
||||
, make_address/1
|
||||
, make_bytes/1
|
||||
, make_hash/1
|
||||
, make_signature/1
|
||||
, make_contract/1
|
||||
, make_oracle/1
|
||||
, make_oracle_query/1
|
||||
, make_channel/1
|
||||
, make_bits/1
|
||||
, make_unit/0
|
||||
, make_typerep/1
|
||||
, make_contract_bytearray/1
|
||||
]).
|
||||
-export([
|
||||
elt/2
|
||||
, lt/2
|
||||
, format/1
|
||||
, ordinal/1]).
|
||||
|
||||
|
||||
make_boolean(true) -> ?FATE_TRUE;
|
||||
make_boolean(false) -> ?FATE_FALSE.
|
||||
make_list([]) -> ?FATE_NIL;
|
||||
make_list(L) -> ?MAKE_FATE_LIST(L).
|
||||
make_unit() -> ?FATE_UNIT.
|
||||
make_tuple(T) -> ?FATE_TUPLE(T).
|
||||
make_map(M) -> ?MAKE_FATE_MAP(M).
|
||||
make_store_map(Id) -> make_store_map(#{}, Id).
|
||||
make_store_map(Cache, Id) -> ?FATE_STORE_MAP(Cache, Id).
|
||||
make_address(X) -> ?FATE_ADDRESS(X).
|
||||
make_bytes(X) -> ?FATE_BYTES(X).
|
||||
make_hash(X) -> make_bytes(X).
|
||||
make_signature(X) -> make_bytes(X).
|
||||
make_contract(X) -> ?FATE_CONTRACT(X).
|
||||
make_oracle(X) -> ?FATE_ORACLE(X).
|
||||
make_oracle_query(X) -> ?FATE_ORACLE_Q(X).
|
||||
make_channel(X) -> ?FATE_CHANNEL(X).
|
||||
make_integer(I) when is_integer(I) -> ?MAKE_FATE_INTEGER(I).
|
||||
make_bits(I) when is_integer(I) -> ?FATE_BITS(I).
|
||||
make_string(S) when is_list(S) ->
|
||||
?FATE_STRING(iolist_to_binary(S));
|
||||
make_string(S) when is_binary(S) -> ?FATE_STRING(S).
|
||||
make_typerep(T) -> ?FATE_TYPEREP(T).
|
||||
make_contract_bytearray(B) -> ?FATE_CONTRACT_BYTEARRAY(B).
|
||||
|
||||
%% Tag points to the selected variant (zero based)
|
||||
%% The arity of this variant is read from the list of provided arities
|
||||
%% and should match the size of the given tuple.
|
||||
make_variant(Arities, Tag, Values) ->
|
||||
Arities = [A || A <- Arities, is_integer(A), A < 256],
|
||||
Size = length(Arities),
|
||||
if is_integer(Tag)
|
||||
, 0 =< Tag
|
||||
, Tag < Size
|
||||
, is_tuple(Values) ->
|
||||
Arity = lists:nth(Tag+1, Arities),
|
||||
if size(Values) =:= Arity ->
|
||||
?FATE_VARIANT(Arities, Tag, Values)
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
|
||||
-spec format(fate_type()) -> iolist().
|
||||
format(I) when ?IS_FATE_INTEGER(I) -> integer_to_list(?MAKE_FATE_INTEGER(I));
|
||||
format(?FATE_TRUE) -> "true";
|
||||
format(?FATE_FALSE) -> "false";
|
||||
format(?FATE_NIL) -> "[]";
|
||||
format(L) when ?IS_FATE_LIST(L) -> format_list(?FATE_LIST_VALUE(L));
|
||||
format(?FATE_UNIT) -> "()";
|
||||
format(?FATE_TUPLE(T)) ->
|
||||
["( ", lists:join(", ", [ format(E) || E <- erlang:tuple_to_list(T)]), " )"];
|
||||
format(S) when ?IS_FATE_STRING(S) -> ["\"", S, "\""];
|
||||
format(?FATE_BITS(B)) when B >= 0 ->
|
||||
["<", format_bits(B, "") , ">"];
|
||||
format(?FATE_BITS(B)) when B < 0 ->
|
||||
["!< ", format_nbits(-B-1, "") , " >"];
|
||||
format(?FATE_VARIANT(Arities, Tag, T)) ->
|
||||
["(| ",
|
||||
lists:join("| ",
|
||||
[format_arities(Arities),
|
||||
integer_to_list(Tag) |
|
||||
[format(make_tuple(T))]]),
|
||||
" |)"];
|
||||
format(M) when ?IS_FATE_MAP(M) ->
|
||||
["{ ", format_kvs(maps:to_list(?FATE_MAP_VALUE(M))), " }"];
|
||||
format(?FATE_BYTES(X)) -> ["#", base64:encode(X)];
|
||||
format(?FATE_ADDRESS(X)) ->
|
||||
["@", gmser_api_encoder:encode(account_pubkey, X)];
|
||||
format(?FATE_CONTRACT(X)) ->
|
||||
["@", gmser_api_encoder:encode(contract_pubkey, X)];
|
||||
format(?FATE_ORACLE(X)) ->
|
||||
["@", gmser_api_encoder:encode(oracle_pubkey, X)];
|
||||
format(?FATE_ORACLE_Q(X)) ->
|
||||
["@", gmser_api_encoder:encode(oracle_query_id, X)];
|
||||
format(?FATE_CHANNEL(X)) ->
|
||||
["@", gmser_api_encoder:encode(channel, X)];
|
||||
format(?FATE_TYPEREP(X)) ->
|
||||
["'", io_lib:format("~p", [X])];
|
||||
format(?FATE_CONTRACT_BYTEARRAY(B)) ->
|
||||
["@", gmser_api_encoder:encode(contract_bytearray, B)];
|
||||
format(V) -> exit({not_a_fate_type, V}).
|
||||
|
||||
format_bits(0, Acc) -> Acc;
|
||||
format_bits(N, Acc) ->
|
||||
Bit = $0 + (N band 1),
|
||||
format_bits(N bsr 1, [Bit|Acc]).
|
||||
|
||||
format_nbits(0, Acc) -> Acc;
|
||||
format_nbits(N, Acc) ->
|
||||
Bit = $1 - (N band 1),
|
||||
format_nbits(N bsr 1, [Bit|Acc]).
|
||||
|
||||
format_arities(Arities) ->
|
||||
["[ ", lists:join(", ", [integer_to_list(E) || E <- Arities]), " ]"].
|
||||
|
||||
format_list(List) ->
|
||||
["[ ", lists:join(", ", [format(E) || E <- List]), " ]"].
|
||||
|
||||
format_kvs(List) ->
|
||||
lists:join(", ", [ [format(K), " => ", format(V)] || {K, V} <- List]).
|
||||
|
||||
|
||||
%% Total order of FATE terms.
|
||||
%% Integers < Booleans < Address < Channel < Contract < Oracle
|
||||
%% < Hash < Signature < Bits < String < Tuple < Map < List < Variant
|
||||
%% < Oracle query < FATE code
|
||||
-define(ORD_INTEGER , 0).
|
||||
-define(ORD_BOOLEAN , 1).
|
||||
-define(ORD_ADDRESS , 2).
|
||||
-define(ORD_CHANNEL , 3).
|
||||
-define(ORD_CONTRACT , 4).
|
||||
-define(ORD_ORACLE , 5).
|
||||
-define(ORD_BYTES , 6).
|
||||
-define(ORD_BITS , 7).
|
||||
-define(ORD_STRING , 8).
|
||||
-define(ORD_TUPLE , 9).
|
||||
-define(ORD_MAP , 10).
|
||||
-define(ORD_LIST , 11).
|
||||
-define(ORD_VARIANT , 12).
|
||||
-define(ORD_ORACLE_Q , 13).
|
||||
-define(ORD_CONTRACT_BYTEARRAY , 14).
|
||||
|
||||
-spec ordinal(fate_type()) -> integer().
|
||||
ordinal(T) when ?IS_FATE_INTEGER(T) -> ?ORD_INTEGER;
|
||||
ordinal(T) when ?IS_FATE_BOOLEAN(T) -> ?ORD_BOOLEAN;
|
||||
ordinal(T) when ?IS_FATE_ADDRESS(T) -> ?ORD_ADDRESS;
|
||||
ordinal(T) when ?IS_FATE_CHANNEL(T) -> ?ORD_CHANNEL;
|
||||
ordinal(T) when ?IS_FATE_CONTRACT(T) -> ?ORD_CONTRACT;
|
||||
ordinal(T) when ?IS_FATE_ORACLE(T) -> ?ORD_ORACLE;
|
||||
ordinal(T) when ?IS_FATE_BYTES(T) -> ?ORD_BYTES;
|
||||
ordinal(T) when ?IS_FATE_BITS(T) -> ?ORD_BITS;
|
||||
ordinal(T) when ?IS_FATE_STRING(T) -> ?ORD_STRING;
|
||||
ordinal(T) when ?IS_FATE_TUPLE(T) -> ?ORD_TUPLE;
|
||||
ordinal(T) when ?IS_FATE_MAP(T) -> ?ORD_MAP;
|
||||
ordinal(T) when ?IS_FATE_LIST(T) -> ?ORD_LIST;
|
||||
ordinal(T) when ?IS_FATE_VARIANT(T) -> ?ORD_VARIANT;
|
||||
ordinal(T) when ?IS_FATE_ORACLE_Q(T) -> ?ORD_ORACLE_Q;
|
||||
ordinal(T) when ?IS_FATE_CONTRACT_BYTEARRAY(T) -> ?ORD_CONTRACT_BYTEARRAY.
|
||||
|
||||
|
||||
-spec lt(fate_type(), fate_type()) -> boolean().
|
||||
lt(A, B) ->
|
||||
O1 = ordinal(A),
|
||||
O2 = ordinal(B),
|
||||
if O1 == O2 -> lt(O1, A, B);
|
||||
true -> O1 < O2
|
||||
end.
|
||||
|
||||
%% Integers are ordered as usual.
|
||||
lt(?ORD_INTEGER, A, B) when ?IS_FATE_INTEGER(A), ?IS_FATE_INTEGER(B) ->
|
||||
?FATE_INTEGER_VALUE(A) < ?FATE_INTEGER_VALUE(B);
|
||||
%% false is smaller than true (true also for erlang booleans).
|
||||
lt(?ORD_BOOLEAN, A, B) when ?IS_FATE_BOOLEAN(A), ?IS_FATE_BOOLEAN(B) ->
|
||||
?FATE_BOOLEAN_VALUE(A) < ?FATE_BOOLEAN_VALUE(B);
|
||||
lt(?ORD_BITS, A, B) when ?IS_FATE_BITS(A), ?IS_FATE_BITS(B) ->
|
||||
BitsA = ?FATE_BITS_VALUE(A),
|
||||
BitsB = ?FATE_BITS_VALUE(B),
|
||||
if BitsA < 0 ->
|
||||
if BitsB < 0 -> BitsA < BitsB;
|
||||
true -> false
|
||||
end;
|
||||
BitsB < 0 ->
|
||||
true;
|
||||
true -> BitsA < BitsB
|
||||
end;
|
||||
lt(?ORD_TUPLE, ?FATE_TUPLE(A), ?FATE_TUPLE(B)) ->
|
||||
SizeA = tuple_size(A),
|
||||
SizeB = tuple_size(B),
|
||||
case SizeA - SizeB of
|
||||
0 -> tuple_elements_lt(0, A, B, SizeA);
|
||||
N -> N < 0
|
||||
end;
|
||||
lt(?ORD_MAP, ?FATE_MAP_VALUE(A), ?FATE_MAP_VALUE(B)) ->
|
||||
SizeA = maps:size(A),
|
||||
SizeB = maps:size(B),
|
||||
case SizeA - SizeB of
|
||||
0 -> maps_lt(A, B);
|
||||
N -> N < 0
|
||||
end;
|
||||
lt(?ORD_LIST, ?FATE_LIST_VALUE(_), ?FATE_LIST_VALUE([])) -> false;
|
||||
lt(?ORD_LIST, ?FATE_LIST_VALUE([]), ?FATE_LIST_VALUE(_)) -> true;
|
||||
lt(?ORD_LIST, ?FATE_LIST_VALUE([A|RA]), ?FATE_LIST_VALUE([B|RB])) ->
|
||||
if A == B -> lt(RA, RB);
|
||||
true -> lt(A, B)
|
||||
end;
|
||||
lt(?ORD_VARIANT, ?FATE_VARIANT(AritiesA, TagA, TA),
|
||||
?FATE_VARIANT(AritiesB, TagB, TB)) ->
|
||||
if length(AritiesA) < length(AritiesB) -> true;
|
||||
length(AritiesA) > length(AritiesB) -> false;
|
||||
true ->
|
||||
% Compare element by element consistent with Erlang compare
|
||||
if AritiesA < AritiesB -> true;
|
||||
AritiesA > AritiesB -> false;
|
||||
true ->
|
||||
if TagA < TagB -> true;
|
||||
TagA > TagB -> false;
|
||||
true -> lt(make_tuple(TA), make_tuple(TB))
|
||||
end
|
||||
end
|
||||
end;
|
||||
lt(?ORD_ADDRESS, ?FATE_ADDRESS(A), ?FATE_ADDRESS(B)) ->
|
||||
A < B;
|
||||
lt(?ORD_CHANNEL, ?FATE_CHANNEL(A), ?FATE_CHANNEL(B)) ->
|
||||
A < B;
|
||||
lt(?ORD_CONTRACT, ?FATE_CONTRACT(A), ?FATE_CONTRACT(B)) ->
|
||||
A < B;
|
||||
lt(?ORD_ORACLE, ?FATE_ORACLE(A), ?FATE_ORACLE(B)) ->
|
||||
A < B;
|
||||
lt(?ORD_ORACLE_Q, ?FATE_ORACLE_Q(A), ?FATE_ORACLE_Q(B)) ->
|
||||
A < B;
|
||||
lt(?ORD_STRING, ?FATE_STRING(A), ?FATE_STRING(B)) ->
|
||||
compare_bytes(A, B);
|
||||
lt(?ORD_BYTES, ?FATE_BYTES(A), ?FATE_BYTES(B)) ->
|
||||
compare_bytes(A, B);
|
||||
lt(?ORD_CONTRACT_BYTEARRAY, ?FATE_CONTRACT_BYTEARRAY(A), ?FATE_CONTRACT_BYTEARRAY(B)) ->
|
||||
compare_bytes(A, B).
|
||||
|
||||
% Shorter comes first
|
||||
% On same length compare by first different bit
|
||||
compare_bytes(A, B) ->
|
||||
SizeA = byte_size(A),
|
||||
SizeB = byte_size(B),
|
||||
case SizeA - SizeB of
|
||||
0 -> A < B;
|
||||
N -> N < 0
|
||||
end.
|
||||
|
||||
tuple_elements_lt(N,_A,_B, N) ->
|
||||
false;
|
||||
tuple_elements_lt(N, A, B, Size) ->
|
||||
E = N + 1,
|
||||
EA = element(E, A),
|
||||
EB = element(E, B),
|
||||
if EA =:= EB -> tuple_elements_lt(E, A, B, Size);
|
||||
true -> lt(EA, EB)
|
||||
end.
|
||||
|
||||
maps_lt(A, B) ->
|
||||
IA = maps_iterator(A),
|
||||
IB = maps_iterator(B),
|
||||
maps_i_lt(IA, IB).
|
||||
|
||||
maps_i_lt(IA, IB) ->
|
||||
case {maps_next(IA), maps_next(IB)} of
|
||||
{none, none} -> false;
|
||||
{_, none} -> false;
|
||||
{none, _} -> true;
|
||||
{{KA1, VA1, IA2}, {KB1, VB1, IB2}} ->
|
||||
case lt(KA1, KB1) of
|
||||
true -> true;
|
||||
false ->
|
||||
case lt(KB1, KA1) of
|
||||
true -> false;
|
||||
false ->
|
||||
case lt(VA1, VB1) of
|
||||
true -> true;
|
||||
false ->
|
||||
case lt(VB1, VA1) of
|
||||
true -> false;
|
||||
false ->
|
||||
maps_i_lt(IA2, IB2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
maps_iterator(M) -> lists:sort(fun ({K1,_}, {K2,_}) -> lt(K1, K2) end, maps:to_list(M)).
|
||||
maps_next([]) -> none;
|
||||
maps_next([{K,V}|Rest]) -> {K, V, Rest}.
|
||||
|
||||
|
||||
-spec elt(fate_type(), fate_type()) -> boolean().
|
||||
elt(A, A) -> true;
|
||||
elt(A, B) ->
|
||||
R = lt(A, B),
|
||||
R.
|
||||
|
@ -1,519 +0,0 @@
|
||||
%% Fate data (and instruction) serialization.
|
||||
%%
|
||||
%% Assuming
|
||||
%% S is seralize/1 (fate_type() -> binary())
|
||||
%% D is deserialize/1 (binary) -> fate_type())
|
||||
%% V, V1, V2 are of the type fate_type()
|
||||
%% B is of the type binary()
|
||||
%% Then
|
||||
%% The FATE serialization has to fullfill the following properties:
|
||||
%% * For each value (V) in FATE there has to be a bytecode sequence (B)
|
||||
%% representing that value.
|
||||
%% * A valid byte sequence has to be deserializable to a FATE value.
|
||||
%% * A valid byte sequence must not contain any trailing bytes.
|
||||
%% * A serialization is a sequence of 8-bit bytes.
|
||||
%% The serialization function (S) should fullfill the following:
|
||||
%% * A valid FATE value should be serialized to a byte sequence.
|
||||
%% * Any other argument, not representing a valid FATE value should
|
||||
%% throw an exception
|
||||
%% The deserialization function (D) should fullfill the following:
|
||||
%% * A valid byte sequence should be deserialized to a valid FATE value.
|
||||
%% * Any other argument, not representing a valid byte sequence should
|
||||
%% throw an exception
|
||||
%% The following equalities should hold:
|
||||
%% * D(S(V)) == V
|
||||
%% * if V1 == V2 then S(V1) == S(V2)
|
||||
%%
|
||||
%%
|
||||
%% History
|
||||
%% * First draft of FATE serialization encoding/decoding.
|
||||
%% Initial experiment with tags
|
||||
%% * Second draft
|
||||
%% * FATE data is now defined in gmfa_data.erl
|
||||
%% * Third draft
|
||||
%% * Added Bit strings
|
||||
%%
|
||||
%% TODO:
|
||||
%% * Make the code production ready.
|
||||
%% (add tests, document exported functions).
|
||||
%% * Handle instructions.
|
||||
%%
|
||||
%% ------------------------------------------------------------------------
|
||||
-module(gmb_fate_encoding).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-export([ deserialize/1
|
||||
, deserialize_one/1
|
||||
, deserialize_type/1
|
||||
, serialize/1
|
||||
, serialize_type/1
|
||||
]).
|
||||
|
||||
-ifdef(EQC).
|
||||
-export([sort/1]).
|
||||
-endif.
|
||||
|
||||
-include("gmb_fate_data.hrl").
|
||||
|
||||
%% Definition of tag scheme.
|
||||
%% This has to follow the protocol specification.
|
||||
|
||||
-define(SMALL_INT , 2#0). %% sxxxxxx 0 - 6 bit integer with sign bit
|
||||
%% 1 Set below
|
||||
-define(LONG_STRING , 2#00000001). %% 000000 01 | RLP encoded array - when size >= 64
|
||||
-define(SHORT_STRING , 2#01). %% xxxxxx 01 | [bytes] - when 0 < xxxxxx:size < 64
|
||||
%% 11 Set below
|
||||
-define(SHORT_LIST , 2#0011). %% xxxx 0011 | [encoded elements] when 0 < length < 16
|
||||
%% 0111 Set below
|
||||
-define(TYPE_INTEGER , 2#00000111). %% 0000 0111 - Integer typedef
|
||||
-define(TYPE_BOOLEAN , 2#00010111). %% 0001 0111 - Boolean typedef
|
||||
-define(TYPE_LIST , 2#00100111). %% 0010 0111 | Type
|
||||
-define(TYPE_TUPLE , 2#00110111). %% 0011 0111 | Size | [Element Types]
|
||||
-define(TYPE_OBJECT , 2#01000111). %% 0100 0111 | ObjectType
|
||||
-define(TYPE_BITS , 2#01010111). %% 0101 0111 - Bits typedef
|
||||
-define(TYPE_MAP , 2#01100111). %% 0110 0111 | Type | Type
|
||||
-define(TYPE_STRING , 2#01110111). %% 0111 0111 - string typedef
|
||||
-define(TYPE_VARIANT , 2#10000111). %% 1000 0111 | [Arities] | [Type]
|
||||
-define(TYPE_BYTES , 2#10010111). %% 1001 0111 - Bytes typedef
|
||||
-define(TYPE_CONTRACT_BYTEARRAY,2#10100111). %% 1010 0111 - Fate code typedef
|
||||
%% 1011 0111
|
||||
%% 1100 0111
|
||||
%% 1101 0111
|
||||
-define(TYPE_VAR , 2#11100111). %% 1110 0111 | Id when 0 =< Id < 256 (type variable)
|
||||
-define(TYPE_ANY , 2#11110111). %% 1111 0111 - Any typedef
|
||||
-define(LONG_TUPLE , 2#00001011). %% 0000 1011 | RLP encoded (size - 16) | [encoded elements],
|
||||
-define(SHORT_TUPLE , 2#1011). %% xxxx 1011 | [encoded elements] when 0 < size < 16
|
||||
%% 1111 Set below
|
||||
-define(LONG_LIST , 2#00011111). %% 0001 1111 | RLP encoded (length - 16) | [encoded lements]
|
||||
-define(MAP , 2#00101111). %% 0010 1111 | RLP encoded size | [encoded key, encoded value]
|
||||
-define(EMPTY_TUPLE , 2#00111111). %% 0011 1111
|
||||
-define(POS_BITS , 2#01001111). %% 0100 1111 | RLP encoded integer (to be interpreted as bitfield)
|
||||
-define(EMPTY_STRING , 2#01011111). %% 0101 1111
|
||||
-define(POS_BIG_INT , 2#01101111). %% 0110 1111 | RLP encoded (integer - 64)
|
||||
-define(FALSE , 2#01111111). %% 0111 1111
|
||||
-define(
|
||||
CONTRACT_BYTEARRAY, 2#10001111). %% 1000 1111
|
||||
-define(OBJECT , 2#10011111). %% 1001 1111 | ObjectType | RLP encoded Array
|
||||
-define(VARIANT , 2#10101111). %% 1010 1111 | [encoded arities] | encoded tag | [encoded values]
|
||||
-define(MAP_ID , 2#10111111). %% 1011 1111 | RLP encoded integer (store map id)
|
||||
-define(NEG_BITS , 2#11001111). %% 1100 1111 | RLP encoded integer (infinite 1:s bitfield)
|
||||
-define(EMPTY_MAP , 2#11011111). %% 1101 1111
|
||||
-define(NEG_BIG_INT , 2#11101111). %% 1110 1111 | RLP encoded (integer - 64)
|
||||
-define(TRUE , 2#11111111). %% 1111 1111
|
||||
|
||||
-define(SHORT_TUPLE_SIZE, 16).
|
||||
-define(SHORT_LIST_SIZE, 16).
|
||||
-define(SMALL_INT_SIZE, 64).
|
||||
-define(SHORT_STRING_SIZE, 64).
|
||||
|
||||
-define(POS_SIGN, 0).
|
||||
-define(NEG_SIGN, 1).
|
||||
|
||||
%% Object types
|
||||
-define(OTYPE_ADDRESS, 0).
|
||||
-define(OTYPE_BYTES, 1).
|
||||
-define(OTYPE_CONTRACT, 2).
|
||||
-define(OTYPE_ORACLE, 3).
|
||||
-define(OTYPE_ORACLE_Q, 4).
|
||||
-define(OTYPE_CHANNEL, 5).
|
||||
|
||||
-define(IS_TYPE_TAG(X), (X =:= ?TYPE_INTEGER orelse
|
||||
X =:= ?TYPE_BOOLEAN orelse
|
||||
X =:= ?TYPE_ANY orelse
|
||||
X =:= ?TYPE_VAR orelse
|
||||
X =:= ?TYPE_LIST orelse
|
||||
X =:= ?TYPE_TUPLE orelse
|
||||
X =:= ?TYPE_OBJECT orelse
|
||||
X =:= ?TYPE_BITS orelse
|
||||
X =:= ?TYPE_BYTES orelse
|
||||
X =:= ?TYPE_MAP orelse
|
||||
X =:= ?TYPE_STRING orelse
|
||||
X =:= ?TYPE_VARIANT orelse
|
||||
X =:= ?TYPE_CONTRACT_BYTEARRAY)).
|
||||
|
||||
%% --------------------------------------------------
|
||||
%% Serialize
|
||||
%% Serialized a Fate data value into a sequence of bytes
|
||||
%% according to the Fate serialization specification.
|
||||
%% TODO: The type Fate Data is not final yet.
|
||||
-spec serialize(gmb_fate_data:fate_type()) -> binary().
|
||||
serialize(?FATE_TRUE) -> <<?TRUE>>;
|
||||
serialize(?FATE_FALSE) -> <<?FALSE>>;
|
||||
serialize(?FATE_UNIT) -> <<?EMPTY_TUPLE>>; %% ! Untyped
|
||||
serialize(?FATE_EMPTY_STRING) -> <<?EMPTY_STRING>>;
|
||||
serialize(I) when ?IS_FATE_INTEGER(I) -> serialize_integer(I);
|
||||
serialize(?FATE_BITS(Bits)) when is_integer(Bits) -> serialize_bits(Bits);
|
||||
serialize(String) when ?IS_FATE_STRING(String),
|
||||
?FATE_STRING_SIZE(String) > 0,
|
||||
?FATE_STRING_SIZE(String) < ?SHORT_STRING_SIZE ->
|
||||
Size = ?FATE_STRING_SIZE(String),
|
||||
Bytes = ?FATE_STRING_VALUE(String),
|
||||
<<Size:6, ?SHORT_STRING:2, Bytes/binary>>;
|
||||
serialize(String) when ?IS_FATE_STRING(String),
|
||||
?FATE_STRING_SIZE(String) > 0,
|
||||
?FATE_STRING_SIZE(String) >= ?SHORT_STRING_SIZE ->
|
||||
Bytes = ?FATE_STRING_VALUE(String),
|
||||
<<?LONG_STRING,
|
||||
(serialize_integer(?FATE_STRING_SIZE(String) - ?SHORT_STRING_SIZE))/binary
|
||||
, Bytes/binary>>;
|
||||
serialize(?FATE_BYTES(Bytes)) when is_binary(Bytes) ->
|
||||
<<?OBJECT, ?OTYPE_BYTES, (serialize(?FATE_STRING(Bytes)))/binary>>;
|
||||
serialize(?FATE_ADDRESS(Address)) when is_binary(Address) ->
|
||||
<<?OBJECT, ?OTYPE_ADDRESS, (gmser_rlp:encode(Address))/binary>>;
|
||||
serialize(?FATE_CONTRACT(Address)) when is_binary(Address) ->
|
||||
<<?OBJECT, ?OTYPE_CONTRACT, (gmser_rlp:encode(Address))/binary>>;
|
||||
serialize(?FATE_ORACLE(Address)) when is_binary(Address) ->
|
||||
<<?OBJECT, ?OTYPE_ORACLE, (gmser_rlp:encode(Address))/binary>>;
|
||||
serialize(?FATE_ORACLE_Q(Address)) when is_binary(Address) ->
|
||||
<<?OBJECT, ?OTYPE_ORACLE_Q, (gmser_rlp:encode(Address))/binary>>;
|
||||
serialize(?FATE_CHANNEL(Address)) when is_binary(Address) ->
|
||||
<<?OBJECT, ?OTYPE_CHANNEL, (gmser_rlp:encode(Address))/binary>>;
|
||||
serialize(?FATE_TUPLE(T)) when size(T) > 0 ->
|
||||
S = size(T),
|
||||
L = tuple_to_list(T),
|
||||
Rest = << <<(serialize(E))/binary>> || E <- L >>,
|
||||
if S < ?SHORT_TUPLE_SIZE ->
|
||||
<<S:4, ?SHORT_TUPLE:4, Rest/binary>>;
|
||||
true ->
|
||||
Size = rlp_encode_int(S - ?SHORT_TUPLE_SIZE),
|
||||
<<?LONG_TUPLE:8, Size/binary, Rest/binary>>
|
||||
end;
|
||||
serialize(L) when ?IS_FATE_LIST(L) ->
|
||||
List = ?FATE_LIST_VALUE(L),
|
||||
S = length(List),
|
||||
Rest = << <<(serialize(El))/binary>> || El <- List >>,
|
||||
if S < ?SHORT_LIST_SIZE ->
|
||||
<<S:4, ?SHORT_LIST:4, Rest/binary>>;
|
||||
true ->
|
||||
Val = rlp_encode_int(S - ?SHORT_LIST_SIZE),
|
||||
<<?LONG_LIST, Val/binary, Rest/binary>>
|
||||
end;
|
||||
serialize(Map) when ?IS_FATE_MAP(Map) ->
|
||||
L = maps:to_list(?FATE_MAP_VALUE(Map)),
|
||||
Size = length(L),
|
||||
%% TODO: check all K same type, and all V same type
|
||||
%% check K =/= map
|
||||
Elements =
|
||||
list_to_binary([ <<(serialize(K))/binary, (serialize(V))/binary>> || {K, V} <- sort_and_check(L) ]),
|
||||
<<?MAP,
|
||||
(rlp_encode_int(Size))/binary,
|
||||
(Elements)/binary>>;
|
||||
serialize(?FATE_STORE_MAP(Cache, Id)) when Cache =:= #{} ->
|
||||
%% We should never get to serialization without having flushed the caches.
|
||||
<<?MAP_ID, (rlp_encode_int(Id))/binary>>;
|
||||
serialize(?FATE_VARIANT(Arities, Tag, Values)) ->
|
||||
Arities = [A || A <- Arities, is_integer(A), A < 256],
|
||||
Size = length(Arities),
|
||||
if is_integer(Tag)
|
||||
, 0 =< Tag
|
||||
, Tag < Size
|
||||
, is_tuple(Values) ->
|
||||
Arity = lists:nth(Tag+1, Arities),
|
||||
if size(Values) =:= Arity ->
|
||||
EncodedArities = gmser_rlp:encode(list_to_binary(Arities)),
|
||||
<<?VARIANT,
|
||||
EncodedArities/binary,
|
||||
Tag:8,
|
||||
(serialize(?FATE_TUPLE(Values)))/binary
|
||||
>>
|
||||
end
|
||||
end;
|
||||
serialize(?FATE_TYPEREP(T)) ->
|
||||
iolist_to_binary(serialize_type(T));
|
||||
serialize(?FATE_CONTRACT_BYTEARRAY(B)) ->
|
||||
<<?CONTRACT_BYTEARRAY,
|
||||
(serialize_integer(?FATE_CONTRACT_BYTEARRAY_SIZE(B)))/binary
|
||||
, B/binary>>.
|
||||
|
||||
|
||||
%% -----------------------------------------------------
|
||||
|
||||
-spec serialize_type(gmb_fate_data:fate_type_type()) -> [byte()].
|
||||
serialize_type(integer) -> [?TYPE_INTEGER];
|
||||
serialize_type(boolean) -> [?TYPE_BOOLEAN];
|
||||
serialize_type(any) -> [?TYPE_ANY];
|
||||
serialize_type({tvar, N}) when 0 =< N, N =< 255 -> [?TYPE_VAR, N];
|
||||
serialize_type({list, T}) -> [?TYPE_LIST | serialize_type(T)];
|
||||
serialize_type({tuple, Ts}) ->
|
||||
case length(Ts) of
|
||||
N when N =< 255 ->
|
||||
[?TYPE_TUPLE, N | [serialize_type(T) || T <- Ts]]
|
||||
end;
|
||||
serialize_type({bytes, any}) ->
|
||||
[?TYPE_BYTES | binary_to_list(serialize_integer(-1))];
|
||||
serialize_type({bytes, N}) when 0 =< N ->
|
||||
[?TYPE_BYTES | binary_to_list(serialize_integer(N))];
|
||||
serialize_type(address) -> [?TYPE_OBJECT, ?OTYPE_ADDRESS];
|
||||
serialize_type(contract) -> [?TYPE_OBJECT, ?OTYPE_CONTRACT];
|
||||
serialize_type(oracle) -> [?TYPE_OBJECT, ?OTYPE_ORACLE];
|
||||
serialize_type(oracle_query)-> [?TYPE_OBJECT, ?OTYPE_ORACLE_Q];
|
||||
serialize_type(channel) -> [?TYPE_OBJECT, ?OTYPE_CHANNEL];
|
||||
serialize_type(bits) -> [?TYPE_BITS];
|
||||
serialize_type({map, K, V}) -> [?TYPE_MAP
|
||||
| serialize_type(K) ++ serialize_type(V)];
|
||||
serialize_type(string) -> [?TYPE_STRING];
|
||||
serialize_type({variant, ListOfVariants}) ->
|
||||
Size = length(ListOfVariants),
|
||||
if Size < 256 ->
|
||||
[?TYPE_VARIANT, Size | [serialize_type(T) || T <- ListOfVariants]]
|
||||
end;
|
||||
serialize_type(contract_bytearray) -> [?TYPE_CONTRACT_BYTEARRAY].
|
||||
|
||||
|
||||
-spec deserialize_type(binary()) -> {gmb_fate_data:fate_type_type(), binary()}.
|
||||
deserialize_type(<<?TYPE_INTEGER, Rest/binary>>) -> {integer, Rest};
|
||||
deserialize_type(<<?TYPE_BOOLEAN, Rest/binary>>) -> {boolean, Rest};
|
||||
deserialize_type(<<?TYPE_ANY, Rest/binary>>) -> {any, Rest};
|
||||
deserialize_type(<<?TYPE_VAR, Id, Rest/binary>>) -> {{tvar, Id}, Rest};
|
||||
deserialize_type(<<?TYPE_LIST, Rest/binary>>) ->
|
||||
{T, Rest2} = deserialize_type(Rest),
|
||||
{{list, T}, Rest2};
|
||||
deserialize_type(<<?TYPE_TUPLE, N, Rest/binary>>) ->
|
||||
{Ts, Rest2} = deserialize_types(N, Rest, []),
|
||||
{{tuple, Ts}, Rest2};
|
||||
deserialize_type(<<?TYPE_BYTES, Rest/binary>>) ->
|
||||
{N, Rest2} = deserialize_one(Rest),
|
||||
true = is_integer(N),
|
||||
if N == -1 ->
|
||||
{{bytes, any}, Rest2};
|
||||
0 =< N ->
|
||||
{{bytes, N}, Rest2}
|
||||
end;
|
||||
deserialize_type(<<?TYPE_OBJECT, ObjectType, Rest/binary>>) ->
|
||||
case ObjectType of
|
||||
?OTYPE_ADDRESS -> {address, Rest};
|
||||
?OTYPE_CONTRACT -> {contract, Rest};
|
||||
?OTYPE_ORACLE -> {oracle, Rest};
|
||||
?OTYPE_ORACLE_Q -> {oracle_query, Rest};
|
||||
?OTYPE_CHANNEL -> {channel, Rest}
|
||||
end;
|
||||
deserialize_type(<<?TYPE_BITS, Rest/binary>>) -> {bits, Rest};
|
||||
deserialize_type(<<?TYPE_MAP, Rest/binary>>) ->
|
||||
{K, Rest2} = deserialize_type(Rest),
|
||||
{V, Rest3} = deserialize_type(Rest2),
|
||||
{{map, K, V}, Rest3};
|
||||
deserialize_type(<<?TYPE_STRING, Rest/binary>>) ->
|
||||
{string, Rest};
|
||||
deserialize_type(<<?TYPE_VARIANT, Size, Rest/binary>>) ->
|
||||
{Variants, Rest2} = deserialize_variants(Size, Rest, []),
|
||||
{{variant, Variants}, Rest2};
|
||||
deserialize_type(<<?TYPE_CONTRACT_BYTEARRAY, Rest/binary>>) -> {contract_bytearray, Rest}.
|
||||
|
||||
deserialize_variants(0, Rest, Variants) ->
|
||||
{lists:reverse(Variants), Rest};
|
||||
deserialize_variants(N, Rest, Variants) ->
|
||||
{T, Rest2} = deserialize_type(Rest),
|
||||
deserialize_variants(N-1, Rest2, [T|Variants]).
|
||||
|
||||
|
||||
|
||||
deserialize_types(0, Binary, Acc) ->
|
||||
{lists:reverse(Acc), Binary};
|
||||
deserialize_types(N, Binary, Acc) ->
|
||||
{T, Rest} = deserialize_type(Binary),
|
||||
deserialize_types(N-1, Rest, [T | Acc]).
|
||||
|
||||
|
||||
%% -----------------------------------------------------
|
||||
|
||||
rlp_encode_int(S) when S >= 0 ->
|
||||
gmser_rlp:encode(binary:encode_unsigned(S)).
|
||||
|
||||
|
||||
%% first byte of the binary gives the number of bytes we need <<129>> is 1, <<130>> = 2,
|
||||
%% so <<129, 0>> is <<0>> and <<130, 0, 0>> is <<0, 0>>
|
||||
rlp_decode_int(Binary) ->
|
||||
{Bin1, Rest} = gmser_rlp:decode_one(Binary),
|
||||
Int = binary:decode_unsigned(Bin1),
|
||||
ReEncode = rlp_encode_int(Int),
|
||||
case <<ReEncode/binary, Rest/binary>> == Binary of
|
||||
true ->
|
||||
{Int, Rest};
|
||||
false ->
|
||||
error({none_unique_encoding, Bin1, ReEncode})
|
||||
end.
|
||||
|
||||
serialize_integer(I) when ?IS_FATE_INTEGER(I) ->
|
||||
V = ?FATE_INTEGER_VALUE(I),
|
||||
Abs = abs(V),
|
||||
Sign = case V < 0 of
|
||||
true -> ?NEG_SIGN;
|
||||
false -> ?POS_SIGN
|
||||
end,
|
||||
if Abs < ?SMALL_INT_SIZE -> <<Sign:1, Abs:6, ?SMALL_INT:1>>;
|
||||
Sign =:= ?NEG_SIGN -> <<?NEG_BIG_INT,
|
||||
(rlp_encode_int(Abs - ?SMALL_INT_SIZE))/binary>>;
|
||||
Sign =:= ?POS_SIGN -> <<?POS_BIG_INT,
|
||||
(rlp_encode_int(Abs - ?SMALL_INT_SIZE))/binary>>
|
||||
end.
|
||||
|
||||
serialize_bits(B) when is_integer(B) ->
|
||||
Abs = abs(B),
|
||||
if
|
||||
B < 0 -> <<?NEG_BITS, (rlp_encode_int(Abs))/binary>>;
|
||||
B >= 0 -> <<?POS_BITS, (rlp_encode_int(Abs))/binary>>
|
||||
end.
|
||||
|
||||
-spec deserialize(binary()) -> gmb_fate_data:fate_type().
|
||||
deserialize(B) ->
|
||||
{T, <<>>} = deserialize2(B),
|
||||
T.
|
||||
|
||||
deserialize_one(B) -> deserialize2(B).
|
||||
|
||||
deserialize2(<<?POS_SIGN:1, I:6, ?SMALL_INT:1, Rest/binary>>) ->
|
||||
{?MAKE_FATE_INTEGER(I), Rest};
|
||||
deserialize2(<<?NEG_SIGN:1, I:6, ?SMALL_INT:1, Rest/binary>>) ->
|
||||
if I =/= 0 -> {?MAKE_FATE_INTEGER(-I), Rest};
|
||||
I == 0 -> error({illegal_sign, I})
|
||||
end;
|
||||
deserialize2(<<?NEG_BIG_INT, Rest/binary>>) ->
|
||||
{Bint, Rest2} = rlp_decode_int(Rest),
|
||||
{?MAKE_FATE_INTEGER(-Bint - ?SMALL_INT_SIZE),
|
||||
Rest2};
|
||||
deserialize2(<<?POS_BIG_INT, Rest/binary>>) ->
|
||||
{Bint, Rest2} = rlp_decode_int(Rest),
|
||||
{?MAKE_FATE_INTEGER(Bint + ?SMALL_INT_SIZE),
|
||||
Rest2};
|
||||
deserialize2(<<?NEG_BITS, Rest/binary>>) ->
|
||||
case rlp_decode_int(Rest) of
|
||||
{Pos, Rest2} when Pos > 0 ->
|
||||
{?FATE_BITS(-Pos), Rest2};
|
||||
{N, _} ->
|
||||
error({illegal_parameter, neg_bits, N})
|
||||
end;
|
||||
deserialize2(<<?POS_BITS, Rest/binary>>) ->
|
||||
{Bint, Rest2} = rlp_decode_int(Rest),
|
||||
{?FATE_BITS(Bint), Rest2};
|
||||
deserialize2(<<?LONG_STRING, Rest/binary>>) ->
|
||||
{S, Rest2} = deserialize_one(Rest),
|
||||
true = is_integer(S) andalso S >= 0,
|
||||
Size = S + ?SHORT_STRING_SIZE,
|
||||
String = binary:part(Rest2, 0, Size),
|
||||
Rest3 = binary:part(Rest2, byte_size(Rest2), - (byte_size(Rest2) - Size)),
|
||||
{?MAKE_FATE_STRING(String), Rest3};
|
||||
deserialize2(<<?CONTRACT_BYTEARRAY, Rest/binary>>) ->
|
||||
{Size, Rest2} = deserialize_one(Rest),
|
||||
true = is_integer(Size) andalso Size >= 0,
|
||||
FateCode = binary:part(Rest2, 0, Size),
|
||||
Rest3 = binary:part(Rest2, byte_size(Rest2), - (byte_size(Rest2) - Size)),
|
||||
{?MAKE_FATE_CONTRACT_BYTEARRAY(FateCode), Rest3};
|
||||
deserialize2(<<S:6, ?SHORT_STRING:2, Rest/binary>>) ->
|
||||
String = binary:part(Rest, 0, S),
|
||||
Rest2 = binary:part(Rest, byte_size(Rest), - (byte_size(Rest) - S)),
|
||||
{?MAKE_FATE_STRING(String), Rest2};
|
||||
deserialize2(<<?OBJECT, ?OTYPE_BYTES, Rest/binary>>) ->
|
||||
{String, Rest2} = deserialize_one(Rest),
|
||||
true = ?IS_FATE_STRING(String),
|
||||
{?FATE_BYTES(?FATE_STRING_VALUE(String)), Rest2};
|
||||
deserialize2(<<?OBJECT, ObjectType, Rest/binary>>) ->
|
||||
{A, Rest2} = gmser_rlp:decode_one(Rest),
|
||||
Val =
|
||||
case ObjectType of
|
||||
?OTYPE_ADDRESS -> ?FATE_ADDRESS(A);
|
||||
?OTYPE_CONTRACT -> ?FATE_CONTRACT(A);
|
||||
?OTYPE_ORACLE -> ?FATE_ORACLE(A);
|
||||
?OTYPE_ORACLE_Q -> ?FATE_ORACLE_Q(A);
|
||||
?OTYPE_CHANNEL -> ?FATE_CHANNEL(A)
|
||||
end,
|
||||
{Val, Rest2};
|
||||
deserialize2(<<?TRUE, Rest/binary>>) ->
|
||||
{?FATE_TRUE, Rest};
|
||||
deserialize2(<<?FALSE, Rest/binary>>) ->
|
||||
{?FATE_FALSE, Rest};
|
||||
deserialize2(<<?EMPTY_TUPLE, Rest/binary>>) ->
|
||||
{?FATE_UNIT, Rest};
|
||||
deserialize2(<<?EMPTY_STRING, Rest/binary>>) ->
|
||||
{?FATE_EMPTY_STRING, Rest};
|
||||
deserialize2(<<?LONG_TUPLE, Rest/binary>>) ->
|
||||
{Size, Rest1} = rlp_decode_int(Rest),
|
||||
N = Size + ?SHORT_TUPLE_SIZE,
|
||||
{List, Rest2} = deserialize_elements(N, Rest1),
|
||||
{?FATE_TUPLE(list_to_tuple(List)), Rest2};
|
||||
deserialize2(<<S:4, ?SHORT_TUPLE:4, Rest/binary>>) ->
|
||||
{List, Rest1} = deserialize_elements(S, Rest),
|
||||
{?FATE_TUPLE(list_to_tuple(List)), Rest1};
|
||||
deserialize2(<<?LONG_LIST, Rest/binary>>) ->
|
||||
{Size, Rest1} = rlp_decode_int(Rest),
|
||||
Length = Size + ?SHORT_LIST_SIZE,
|
||||
{List, Rest2} = deserialize_elements(Length, Rest1),
|
||||
{?MAKE_FATE_LIST(List), Rest2};
|
||||
deserialize2(<<S:4, ?SHORT_LIST:4, Rest/binary>>) ->
|
||||
{List, Rest1} = deserialize_elements(S, Rest),
|
||||
{?MAKE_FATE_LIST(List), Rest1};
|
||||
deserialize2(<<?MAP, Rest/binary>>) ->
|
||||
{Size, Rest1} = rlp_decode_int(Rest),
|
||||
{List, Rest2} = deserialize_elements(2*Size, Rest1),
|
||||
KVList = insert_kv(List),
|
||||
case sort_and_check(KVList) == KVList of
|
||||
true ->
|
||||
Map = maps:from_list(KVList),
|
||||
{?MAKE_FATE_MAP(Map), Rest2};
|
||||
false ->
|
||||
error({unknown_map_serialization_format, KVList})
|
||||
end;
|
||||
deserialize2(<<?MAP_ID, Rest/binary>>) ->
|
||||
{Id, Rest1} = rlp_decode_int(Rest),
|
||||
{?FATE_STORE_MAP(#{}, Id), Rest1};
|
||||
deserialize2(<<?VARIANT, Rest/binary>>) ->
|
||||
{AritiesBin, <<Tag:8, Rest2/binary>>} = gmser_rlp:decode_one(Rest),
|
||||
Arities = binary_to_list(AritiesBin),
|
||||
Size = length(Arities),
|
||||
if Tag > Size -> exit({too_large_tag_in_variant, Tag, Size});
|
||||
true ->
|
||||
{?FATE_TUPLE(T), Rest3} = deserialize2(Rest2),
|
||||
Arity = lists:nth(Tag+1, Arities),
|
||||
NumElements = size(T),
|
||||
if NumElements =/= Arity ->
|
||||
exit({tag_does_not_match_type_in_variant, Tag, Arity});
|
||||
true ->
|
||||
{?FATE_VARIANT(Arities, Tag, T), Rest3}
|
||||
end
|
||||
end;
|
||||
deserialize2(<<TypeTag, _/binary>> = Bin) when ?IS_TYPE_TAG(TypeTag) ->
|
||||
{Type, Rest} = deserialize_type(Bin),
|
||||
{?FATE_TYPEREP(Type), Rest}.
|
||||
|
||||
insert_kv([]) -> [];
|
||||
insert_kv([K, V | R]) -> [{K, V} | insert_kv(R)].
|
||||
|
||||
deserialize_elements(0, Rest) ->
|
||||
{[], Rest};
|
||||
deserialize_elements(N, Es) ->
|
||||
{E, Rest} = deserialize2(Es),
|
||||
{Tail, Rest2} = deserialize_elements(N-1, Rest),
|
||||
{[E|Tail], Rest2}.
|
||||
|
||||
|
||||
%% It is important to remove duplicated keys.
|
||||
%% For deserialize this check is needed to observe illegal duplicates.
|
||||
sort_and_check(List) ->
|
||||
UniqKeyList =
|
||||
lists:foldr(fun({K, V}, Acc) ->
|
||||
case valid_key_type(K) andalso not lists:keymember(K, 1, Acc) of
|
||||
true -> [{K,V}|Acc];
|
||||
false -> Acc
|
||||
end
|
||||
end, [], List),
|
||||
sort(UniqKeyList).
|
||||
|
||||
%% Sorting is used to get a unique result.
|
||||
%% Deserialization is checking whether the provided key-value pairs are sorted
|
||||
%% and raises an exception if not.
|
||||
|
||||
sort(KVList) ->
|
||||
SortFun = fun({K1, _}, {K2, _}) ->
|
||||
gmb_fate_data:elt(K1, K2)
|
||||
end,
|
||||
lists:sort(SortFun, KVList).
|
||||
|
||||
valid_key_type(K) when ?IS_FATE_MAP(K) ->
|
||||
error({map_as_key_in_map, K});
|
||||
valid_key_type(?FATE_STORE_MAP(_, _) = K) ->
|
||||
error({map_as_key_in_map, K});
|
||||
valid_key_type(K) when is_list(K) ->
|
||||
lists:all(fun(E) -> valid_key_type(E) end, K);
|
||||
valid_key_type(K) when is_tuple(K) ->
|
||||
lists:all(fun(E) -> valid_key_type(E) end, tuple_to_list(K));
|
||||
valid_key_type(_K) ->
|
||||
true.
|
@ -1,133 +0,0 @@
|
||||
-module(gmb_fate_generate_docs).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-export([generate_documentation/2, generate_documentation/3]).
|
||||
|
||||
-export(
|
||||
[ gen_protocol_opcodes_flags_and_gas/1
|
||||
, gen_protocol_description_of_operations/1
|
||||
, gen_protocol_opcodes/1
|
||||
]).
|
||||
|
||||
-define(LIMA_PROTOCOL_VSN, 4).
|
||||
-define(IRIS_PROTOCOL_VSN, 5).
|
||||
|
||||
generate_documentation(Filename, Fields) ->
|
||||
generate_documentation(Filename, Fields, fun(_) -> true end).
|
||||
generate_documentation(Filename, Fields, Filter) when is_function(Filter, 1) ->
|
||||
{ok, File} = file:open(Filename, [write, {encoding, utf8}]),
|
||||
Header =
|
||||
lists:flatten(
|
||||
"|" ++ [" " ++ header_name(F) ++ " |" || F <- Fields] ++ "\n"
|
||||
),
|
||||
Separator =
|
||||
lists:flatten(
|
||||
"|" ++ [" " ++ ["-" || _ <- header_name(F)] ++ " |" || F <- Fields] ++ "\n"
|
||||
),
|
||||
Instructions =
|
||||
lists:flatten(
|
||||
[gen_doc_for_op(Op, Fields)
|
||||
++ "\n" || Op <- gmb_fate_generate_ops:get_ops(), Filter(Op)]),
|
||||
io:format(File, "~ts~ts~ts\n", [Header, Separator, Instructions]),
|
||||
file:close(File).
|
||||
|
||||
header_name(opname) ->
|
||||
"Name";
|
||||
header_name(opcode) ->
|
||||
"Opcode";
|
||||
header_name(arity) ->
|
||||
"Arity";
|
||||
header_name(end_bb) ->
|
||||
"Ends basic block";
|
||||
header_name(in_auth) ->
|
||||
"Allowed in auth";
|
||||
header_name(offchain) ->
|
||||
"Allowed offchain";
|
||||
header_name(format) ->
|
||||
"Args";
|
||||
header_name(doc) ->
|
||||
"Description";
|
||||
header_name(gas) ->
|
||||
"Gas cost";
|
||||
header_name(arg_types) ->
|
||||
"Arg types";
|
||||
header_name(res_type) ->
|
||||
"Res type".
|
||||
|
||||
gen_doc_for_op(#{ opname := OpName
|
||||
, opcode := OpCode
|
||||
, arity := Arity
|
||||
, end_bb := EndBB
|
||||
, in_auth := InAuth
|
||||
, offchain := AllowedOffchain
|
||||
, format := FateFormat
|
||||
, doc := Doc
|
||||
, gas := Gas
|
||||
, arg_types := ArgTypes
|
||||
, res_type := ResType
|
||||
}, Fields) ->
|
||||
"| " ++
|
||||
string:join(
|
||||
[ case Field of
|
||||
opname -> io_lib:format("`~s`", [OpName]);
|
||||
opcode -> io_lib:format("0x~.16b", [OpCode]);
|
||||
arity -> io_lib:format("~p", [Arity]);
|
||||
end_bb -> io_lib:format("~p", [EndBB]);
|
||||
in_auth -> io_lib:format("~p", [InAuth]);
|
||||
offchain -> io_lib:format("~p", [AllowedOffchain]);
|
||||
format ->
|
||||
case FateFormat of
|
||||
[] -> "";
|
||||
_ -> lists:join(
|
||||
" ",
|
||||
[format_arg_doc(A) ||
|
||||
A <-
|
||||
lists:zip(FateFormat,
|
||||
lists:seq(0,length(FateFormat)-1))])
|
||||
end;
|
||||
doc -> Doc;
|
||||
gas when is_integer(Gas) -> io_lib:format("~p", [Gas]);
|
||||
gas when is_list(Gas) ->
|
||||
lists:flatten(
|
||||
string:join(
|
||||
[ io_lib:format(
|
||||
"~p (~s)",
|
||||
[GasVal, protocol_name(Prot)]
|
||||
)
|
||||
|| {Prot, GasVal} <- Gas
|
||||
], ", "));
|
||||
arg_types -> io_lib:format("~p", [ArgTypes]);
|
||||
res_type -> io_lib:format("~p", [ResType])
|
||||
end
|
||||
|| Field <- Fields
|
||||
],
|
||||
" | ") ++ " |".
|
||||
|
||||
protocol_name(?LIMA_PROTOCOL_VSN) ->
|
||||
"lima";
|
||||
protocol_name(?IRIS_PROTOCOL_VSN) ->
|
||||
"iris".
|
||||
|
||||
format_arg_doc({a, N}) -> io_lib:format("Arg~w", [N]);
|
||||
format_arg_doc({is,_N}) -> "Identifier";
|
||||
format_arg_doc({ii,_N}) -> "Integer";
|
||||
format_arg_doc({li,_N}) -> "[Integers]";
|
||||
format_arg_doc({t,_N}) -> "Type".
|
||||
|
||||
|
||||
%% --- protocol documentation ---
|
||||
|
||||
gen_protocol_description_of_operations(Filename) ->
|
||||
generate_documentation(
|
||||
Filename, [opname, format, doc, arg_types, res_type]
|
||||
).
|
||||
|
||||
gen_protocol_opcodes_flags_and_gas(Filename) ->
|
||||
generate_documentation(
|
||||
Filename, [opcode, opname, end_bb, in_auth, offchain, gas]
|
||||
).
|
||||
|
||||
gen_protocol_opcodes(Filename) ->
|
||||
generate_documentation(
|
||||
Filename, [opcode, opname]
|
||||
).
|
@ -1,800 +0,0 @@
|
||||
-module(gmb_fate_generate_ops).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-export([ gen_and_halt/1
|
||||
, generate/0
|
||||
, get_ops/0
|
||||
, test_asm_generator/1 ]).
|
||||
|
||||
gen_and_halt([SrcDirArg, IncludeDirArg]) ->
|
||||
generate(atom_to_list(SrcDirArg),
|
||||
atom_to_list(IncludeDirArg)),
|
||||
halt().
|
||||
|
||||
generate() -> generate("src/", "include/").
|
||||
|
||||
get_ops() -> gen(ops_defs()).
|
||||
|
||||
generate(Src, Include) ->
|
||||
check_defs(ops_defs()),
|
||||
Ops = get_ops(),
|
||||
%% io:format("ops: ~p\n", [Ops]),
|
||||
HrlFile = Include ++ "gmb_fate_opcodes.hrl",
|
||||
generate_header_file(HrlFile, Ops),
|
||||
generate_opcodes_ops(gmb_fate_opcodes, HrlFile, Src, Ops),
|
||||
generate_code_ops(gmb_fate_ops, Src, Ops),
|
||||
generate_scanner("gmb_fate_asm_scan.template", "gmb_fate_asm_scan.xrl", Src, Ops),
|
||||
gen_asm_pp(gmb_fate_pp, Src, Ops).
|
||||
|
||||
check_defs(List) ->
|
||||
true = check_numbering(0, lists:keysort(2, List)).
|
||||
|
||||
check_numbering(N, [T|Rest]) ->
|
||||
OpCode = element(2, T),
|
||||
case OpCode of
|
||||
N -> check_numbering(N+1, Rest);
|
||||
16#6d -> check_numbering(16#6d+1, Rest); %% Oracles
|
||||
16#7b -> check_numbering(16#7b+1, Rest); %% Oracles
|
||||
16#9b -> check_numbering(16#9b+1, Rest); %% Oracles
|
||||
16#f0 -> check_numbering(16#f0+1, Rest);
|
||||
16#fa -> check_numbering(16#fa+1, Rest);
|
||||
_ when OpCode < N -> {duplicate_opcode, OpCode};
|
||||
_ when OpCode > N -> {missing_opcode, N}
|
||||
end;
|
||||
check_numbering(_, []) -> true.
|
||||
|
||||
-define(LIMA_PROTOCOL_VSN, 4).
|
||||
-define(IRIS_PROTOCOL_VSN, 5).
|
||||
|
||||
-define(GAS(A), A).
|
||||
-define(GAS_IRIS(A, B), [{?IRIS_PROTOCOL_VSN, B}, {?LIMA_PROTOCOL_VSN, A}]).
|
||||
|
||||
ops_defs() ->
|
||||
%% Opname, Opcode, end_bb, in_auth,offchain, gas, format, Constructor, ArgType, ResType, Documentation
|
||||
[ { 'RETURN', 16#00, true, true, true, ?GAS(10), [], return, {}, any, "Return from function call, top of stack is return value . The type of the retun value has to match the return type of the function."}
|
||||
, { 'RETURNR', 16#01, true, true, true, ?GAS(10), [a], returnr, {any}, any, "Push Arg0 and return from function. The type of the retun value has to match the return type of the function."}
|
||||
, { 'CALL', 16#02, true, true, true, ?GAS(10), [a], call, {string}, any, "Call the function Arg0 with args on stack. The types of the arguments has to match the argument typs of the function."}
|
||||
, { 'CALL_R', 16#03, true, false, true, ?GAS(100), [a,is,a,a,a], call_r, {contract, string, typerep, typerep, integer}, any, "Remote call to contract Arg0 and function Arg1 of type Arg2 => Arg3 with value Arg4. The types of the arguments has to match the argument types of the function."}
|
||||
, { 'CALL_T', 16#04, true, true, true, ?GAS(10), [a], call_t, {string}, any, "Tail call to function Arg0. The types of the arguments has to match the argument typs of the function. And the return type of the called function has to match the type of the current function."}
|
||||
, { 'CALL_GR', 16#05, true, false, true, ?GAS(100), [a,is,a,a,a,a], call_gr, {contract, string, typerep, typerep, integer, integer}, any, "Remote call with gas cap in Arg4. Otherwise as CALL_R."}
|
||||
, { 'JUMP', 16#06, true, true, true, ?GAS(10), [ii], jump, {integer}, none, "Jump to a basic block. The basic block has to exist in the current function."}
|
||||
, { 'JUMPIF', 16#07, true, true, true, ?GAS(10), [a,ii], jumpif, {boolean, integer}, none, "Conditional jump to a basic block. If Arg0 then jump to Arg1."}
|
||||
, { 'SWITCH_V2', 16#08, true, true, true, ?GAS(10), [a,ii,ii], switch, {variant, integer, ingeger}, none, "Conditional jump to a basic block on variant tag."}
|
||||
, { 'SWITCH_V3', 16#09, true, true, true, ?GAS(10), [a,ii,ii,ii], switch, {variant, integer, integer, ingeger}, none, "Conditional jump to a basic block on variant tag."}
|
||||
, { 'SWITCH_VN', 16#0a, true, true, true, ?GAS(10), [a, li], switch, {variant, {list, integer}}, none, "Conditional jump to a basic block on variant tag."}
|
||||
, { 'CALL_VALUE', 16#0b, false, true, true, ?GAS(10), [a], call_value, {}, integer, "The value sent in the current remote call."}
|
||||
, { 'PUSH', 16#0c, false, true, true, ?GAS(10), [a], push, {any}, any, "Push argument to stack."}
|
||||
, { 'DUPA', 16#0d, false, true, true, ?GAS(10), [], dup, {any}, any, "Duplicate top of stack."}
|
||||
, { 'DUP', 16#0e, false, true, true, ?GAS(10), [a], dup, {any}, any, "push Arg0 stack pos on top of stack."}
|
||||
, { 'POP', 16#0f, false, true, true, ?GAS(10), [a], pop, {integer}, integer, "Arg0 := top of stack."}
|
||||
, { 'INCA', 16#10, false, true, true, ?GAS(10), [], inc, {integer}, integer, "Increment accumulator."}
|
||||
, { 'INC', 16#11, false, true, true, ?GAS(10), [a], inc, {integer}, integer, "Increment argument."}
|
||||
, { 'DECA', 16#12, false, true, true, ?GAS(10), [], dec, {integer}, integer, "Decrement accumulator."}
|
||||
, { 'DEC', 16#13, false, true, true, ?GAS(10), [a], dec, {integer}, integer, "Decrement argument."}
|
||||
, { 'ADD', 16#14, false, true, true, ?GAS(10), [a,a,a], add, {integer, integer}, integer, "Arg0 := Arg1 + Arg2."}
|
||||
, { 'SUB', 16#15, false, true, true, ?GAS(10), [a,a,a], sub, {integer, integer}, integer, "Arg0 := Arg1 - Arg2."}
|
||||
, { 'MUL', 16#16, false, true, true, ?GAS(10), [a,a,a], mul, {integer, integer}, integer, "Arg0 := Arg1 * Arg2."}
|
||||
, { 'DIV', 16#17, false, true, true, ?GAS(10), [a,a,a], divide, {integer, integer}, integer, "Arg0 := Arg1 / Arg2."}
|
||||
, { 'MOD', 16#18, false, true, true, ?GAS(10), [a,a,a], modulo, {integer, integer}, integer, "Arg0 := Arg1 mod Arg2."}
|
||||
, { 'POW', 16#19, false, true, true, ?GAS(10), [a,a,a], pow, {integer, integer}, integer, "Arg0 := Arg1 ^ Arg2."}
|
||||
, { 'STORE', 16#1a, false, true, true, ?GAS(10), [a,a], store, {any}, any, "Arg0 := Arg1."}
|
||||
, { 'SHA3', 16#1b, false, true, true, ?GAS(100), [a,a], sha3, {any}, hash, "Arg0 := sha3(Arg1)."}
|
||||
, { 'SHA256', 16#1c, false, true, true, ?GAS(100), [a,a], sha256, {any}, hash, "Arg0 := sha256(Arg1)."}
|
||||
, { 'BLAKE2B', 16#1d, false, true, true, ?GAS(100), [a,a], blake2b, {any}, hash, "Arg0 := blake2b(Arg1)."}
|
||||
, { 'LT', 16#1e, false, true, true, ?GAS(10), [a,a,a], lt, {integer, integer}, boolean, "Arg0 := Arg1 < Arg2."}
|
||||
, { 'GT', 16#1f, false, true, true, ?GAS(10), [a,a,a], gt, {integer, integer}, boolean, "Arg0 := Arg1 > Arg2."}
|
||||
, { 'EQ', 16#20, false, true, true, ?GAS(10), [a,a,a], eq, {integer, integer}, boolean, "Arg0 := Arg1 = Arg2."}
|
||||
, { 'ELT', 16#21, false, true, true, ?GAS(10), [a,a,a], elt, {integer, integer}, boolean, "Arg0 := Arg1 =< Arg2."}
|
||||
, { 'EGT', 16#22, false, true, true, ?GAS(10), [a,a,a], egt, {integer, integer}, boolean, "Arg0 := Arg1 >= Arg2."}
|
||||
, { 'NEQ', 16#23, false, true, true, ?GAS(10), [a,a,a], neq, {integer, integer}, boolean, "Arg0 := Arg1 /= Arg2."}
|
||||
, { 'AND', 16#24, false, true, true, ?GAS(10), [a,a,a], and_op, {boolean, boolean}, boolean, "Arg0 := Arg1 and Arg2."}
|
||||
, { 'OR', 16#25, false, true, true, ?GAS(10), [a,a,a], or_op, {boolean, boolean}, boolean, "Arg0 := Arg1 or Arg2."}
|
||||
, { 'NOT', 16#26, false, true, true, ?GAS(10), [a,a], not_op, {boolean}, boolean, "Arg0 := not Arg1."}
|
||||
, { 'TUPLE', 16#27, false, true, true, ?GAS(10), [a,ii], tuple, {integer}, tuple, "Arg0 := tuple of size = Arg1. Elements on stack."}
|
||||
, { 'ELEMENT', 16#28, false, true, true, ?GAS(10), [a,a,a], element_op, {integer, tuple}, any, "Arg1 := element(Arg2, Arg3)."}
|
||||
, { 'SETELEMENT', 16#29, false, true, true, ?GAS(10), [a,a,a,a], setelement, {integer, tuple, any}, tuple, "Arg0 := a new tuple similar to Arg2, but with element number Arg1 replaced by Arg3."}
|
||||
, { 'MAP_EMPTY', 16#2a, false, true, true, ?GAS(10), [a], map_empty, {}, map, "Arg0 := #{}."}
|
||||
, { 'MAP_LOOKUP', 16#2b, false, true, true, ?GAS(10), [a,a,a], map_lookup, {map, any}, any, "Arg0 := lookup key Arg2 in map Arg1."}
|
||||
, { 'MAP_LOOKUPD', 16#2c, false, true, true, ?GAS(10), [a,a,a,a], map_lookup, {map, any, any}, any, "Arg0 := lookup key Arg2 in map Arg1 if key exists in map otherwise Arg0 := Arg3."}
|
||||
, { 'MAP_UPDATE', 16#2d, false, true, true, ?GAS(10), [a,a,a,a], map_update, {map, any, any}, map, "Arg0 := update key Arg2 in map Arg1 with value Arg3."}
|
||||
, { 'MAP_DELETE', 16#2e, false, true, true, ?GAS(10), [a,a,a], map_delete, {map, any}, map, "Arg0 := delete key Arg2 from map Arg1."}
|
||||
, { 'MAP_MEMBER', 16#2f, false, true, true, ?GAS(10), [a,a,a], map_member, {map, any}, boolean, "Arg0 := true if key Arg2 is in map Arg1."}
|
||||
, { 'MAP_FROM_LIST', 16#30, false, true, true, ?GAS(10), [a,a], map_from_list, {{list, {tuple, [any, any]}}}, map, "Arg0 := make a map from (key, value) list in Arg1."}
|
||||
, { 'MAP_SIZE', 16#31, false, true, true, ?GAS(10), [a,a], map_size_, {map}, integer, "Arg0 := The size of the map Arg1."}
|
||||
, { 'MAP_TO_LIST', 16#32, false, true, true, ?GAS(10), [a,a], map_to_list, {map}, list, "Arg0 := The tuple list representation of the map Arg1."}
|
||||
, { 'IS_NIL', 16#33, false, true, true, ?GAS(10), [a,a], is_nil, {list}, boolean, "Arg0 := true if Arg1 == []."}
|
||||
, { 'CONS', 16#34, false, true, true, ?GAS(10), [a,a,a], cons, {any, list}, list, "Arg0 := [Arg1|Arg2]."}
|
||||
, { 'HD', 16#35, false, true, true, ?GAS(10), [a,a], hd, {list}, any, "Arg0 := head of list Arg1."}
|
||||
, { 'TL', 16#36, false, true, true, ?GAS(10), [a,a], tl, {list}, list, "Arg0 := tail of list Arg1."}
|
||||
, { 'LENGTH', 16#37, false, true, true, ?GAS(10), [a,a], length, {list}, integer, "Arg0 := length of list Arg1."}
|
||||
, { 'NIL', 16#38, false, true, true, ?GAS(10), [a], nil, {}, list, "Arg0 := []."}
|
||||
, { 'APPEND', 16#39, false, true, true, ?GAS(10), [a,a,a], append, {list, list}, list, "Arg0 := Arg1 ++ Arg2."}
|
||||
, { 'STR_JOIN', 16#3a, false, true, true, ?GAS(10), [a,a,a], str_join, {string, string}, string, "Arg0 := string Arg1 followed by string Arg2."}
|
||||
, { 'INT_TO_STR', 16#3b, false, true, true, ?GAS(100), [a,a], int_to_str, {integer}, string, "Arg0 := turn integer Arg1 into a string."}
|
||||
, { 'ADDR_TO_STR', 16#3c, false, true, true, ?GAS(100), [a,a], addr_to_str, {address}, string, "Arg0 := turn address Arg1 into a string."}
|
||||
, { 'STR_REVERSE', 16#3d, false, true, true, ?GAS(100), [a,a], str_reverse, {string}, string, "Arg0 := the reverse of string Arg1."}
|
||||
, { 'STR_LENGTH', 16#3e, false, true, true, ?GAS(10), [a,a], str_length, {string}, integer, "Arg0 := The length of the string Arg1."}
|
||||
, { 'BYTES_TO_INT', 16#3f, false, true, true, ?GAS(10), [a,a], bytes_to_int, {bytes}, integer, "Arg0 := bytes_to_int(Arg1)"}
|
||||
, { 'BYTES_TO_STR', 16#40, false, true, true, ?GAS(100), [a,a], bytes_to_str, {bytes}, string, "Arg0 := bytes_to_str(Arg1)"}
|
||||
, { 'BYTES_CONCAT', 16#41, false, true, true, ?GAS(10), [a,a,a], bytes_concat, {bytes, bytes}, bytes, "Arg0 := bytes_concat(Arg1, Arg2)"}
|
||||
, { 'BYTES_SPLIT', 16#42, false, true, true, ?GAS(10), [a,a,a], bytes_split, {bytes, integer}, bytes, "Arg0 := bytes_split(Arg2, Arg1), where Arg2 is the length of the first chunk."}
|
||||
, { 'INT_TO_ADDR', 16#43, false, true, true, ?GAS(10), [a,a], int_to_addr, {integer}, address, "Arg0 := turn integer Arg1 into an address."}
|
||||
, { 'VARIANT', 16#44, false, true, true, ?GAS(10), [a,a,a,a], variant, {integer, integer, integer}, variant, "Arg0 := create a variant of size Arg1 with the tag Arg2 (Arg2 < Arg1) and take Arg3 elements from the stack."}
|
||||
, { 'VARIANT_TEST', 16#45, false, true, true, ?GAS(10), [a,a,a], variant_test, {variant, integer}, boolean, "Arg0 := true if variant Arg1 has the tag Arg2."}
|
||||
, { 'VARIANT_ELEMENT', 16#46, false, true, true, ?GAS(10), [a,a,a], variant_element, {variant, integer}, any, "Arg0 := element number Arg2 from variant Arg1."}
|
||||
, { 'BITS_NONEA', 16#47, false, true, true, ?GAS(10), [], bits_none, {}, bits, "push an empty bitmap on the stack."}
|
||||
, { 'BITS_NONE', 16#48, false, true, true, ?GAS(10), [a], bits_none, {}, bits, "Arg0 := empty bitmap."}
|
||||
, { 'BITS_ALLA', 16#49, false, true, true, ?GAS(10), [], bits_all, {}, bits, "push a full bitmap on the stack."}
|
||||
, { 'BITS_ALL', 16#4a, false, true, true, ?GAS(10), [a], bits_all, {}, bits, "Arg0 := full bitmap."}
|
||||
, { 'BITS_ALL_N', 16#4b, false, true, true, ?GAS(10), [a,a], bits_all_n, {integer}, bits, "Arg0 := bitmap with Arg1 bits set."}
|
||||
, { 'BITS_SET', 16#4c, false, true, true, ?GAS(10), [a,a,a], bits_set, {bits, integer}, bits, "Arg0 := set bit Arg2 of bitmap Arg1."}
|
||||
, { 'BITS_CLEAR', 16#4d, false, true, true, ?GAS(10), [a,a,a], bits_clear, {bits, integer}, bits, "Arg0 := clear bit Arg2 of bitmap Arg1."}
|
||||
, { 'BITS_TEST', 16#4e, false, true, true, ?GAS(10), [a,a,a], bits_test, {bits, integer}, boolean, "Arg0 := true if bit Arg2 of bitmap Arg1 is set."}
|
||||
, { 'BITS_SUM', 16#4f, false, true, true, ?GAS(10), [a,a], bits_sum, {bits}, integer, "Arg0 := sum of set bits in bitmap Arg1. Exception if infinit bitmap."}
|
||||
, { 'BITS_OR', 16#50, false, true, true, ?GAS(10), [a,a,a], bits_or, {bits, bits}, bits, "Arg0 := Arg1 v Arg2."}
|
||||
, { 'BITS_AND', 16#51, false, true, true, ?GAS(10), [a,a,a], bits_and, {bits, bits}, bits, "Arg0 := Arg1 ^ Arg2."}
|
||||
, { 'BITS_DIFF', 16#52, false, true, true, ?GAS(10), [a,a,a], bits_diff, {bits, bits}, bits, "Arg0 := Arg1 - Arg2."}
|
||||
, { 'BALANCE', 16#53, false, true, true, ?GAS(10), [a], balance, {}, integer, "Arg0 := The current contract balance."}
|
||||
, { 'ORIGIN', 16#54, false, true, true, ?GAS(10), [a], origin, {}, address, "Arg0 := Address of contract called by the call transaction."}
|
||||
, { 'CALLER', 16#55, false, true, true, ?GAS(10), [a], caller, {}, address, "Arg0 := The address that signed the call transaction."}
|
||||
, { 'BLOCKHASH', 16#56, false, true, true, ?GAS_IRIS(10, 1000), [a,a], blockhash, {integer}, variant, "Arg0 := The blockhash at height."}
|
||||
, { 'BENEFICIARY', 16#57, false, true, true, ?GAS(10), [a], beneficiary, {}, address, "Arg0 := The address of the current beneficiary."}
|
||||
, { 'TIMESTAMP', 16#58, false, true, true, ?GAS(10), [a], timestamp, {}, integer, "Arg0 := The current timestamp. Unrelaiable, don't use for anything."}
|
||||
, { 'GENERATION', 16#59, false, true, true, ?GAS(10), [a], generation, {}, integer, "Arg0 := The block height of the cureent generation."}
|
||||
, { 'MICROBLOCK', 16#5a, false, true, true, ?GAS(10), [a], microblock, {}, integer, "Arg0 := The current micro block number."}
|
||||
, { 'DIFFICULTY', 16#5b, false, true, true, ?GAS(10), [a], difficulty, {}, integer, "Arg0 := The current difficulty."}
|
||||
, { 'GASLIMIT', 16#5c, false, true, true, ?GAS(10), [a], gaslimit, {}, integer, "Arg0 := The current gaslimit."}
|
||||
, { 'GAS', 16#5d, false, true, true, ?GAS(10), [a], gas, {}, integer, "Arg0 := The amount of gas left."}
|
||||
, { 'ADDRESS', 16#5e, false, true, true, ?GAS(10), [a], address, {}, address, "Arg0 := The current contract address."}
|
||||
, { 'GASPRICE', 16#5f, false, true, true, ?GAS(10), [a], gasprice, {}, integer, "Arg0 := The current gas price."}
|
||||
|
||||
, { 'LOG0', 16#60, false, true, true, ?GAS(1000), [a], log, {string}, none, "Create a log message in the call object."}
|
||||
, { 'LOG1', 16#61, false, true, true, ?GAS(1100), [a,a], log, {integer, string}, none, "Create a log message with one topic in the call object."}
|
||||
, { 'LOG2', 16#62, false, true, true, ?GAS(1200), [a,a,a], log, {integer, integer, string}, none, "Create a log message with two topics in the call object."}
|
||||
, { 'LOG3', 16#63, false, true, true, ?GAS(1300), [a,a,a,a], log, {integer, integer, integer, string}, none, "Create a log message with three topics in the call object."}
|
||||
, { 'LOG4', 16#64, false, true, true, ?GAS(1400), [a,a,a,a,a], log, {integer, integer, integer, integer, string}, none, "Create a log message with four topics in the call object."}
|
||||
%% Transaction ops
|
||||
, { 'SPEND', 16#65, false, false, true, ?GAS_IRIS(100, 5000), [a,a], spend, {address, integer}, none, "Transfer Arg1 tokens to account Arg0. (If the contract account has at least that many tokens."}
|
||||
%% Intentional gap (was oracles)
|
||||
|
||||
, { 'AENS_RESOLVE', 16#6d, false, false, true, ?GAS_IRIS(100, 2000), [a,a,a,a], aens_resolve, {string, string, typerep}, variant, "Resolve name in Arg0 with tag Arg1. Arg2 describes the type parameter of the resolved name."}
|
||||
, { 'AENS_PRECLAIM', 16#6e, false, false, false, ?GAS_IRIS(100, 10000), [a,a,a], aens_preclaim, {signature, address, hash}, none, "Preclaim the hash in Arg2 for address in Arg1. Arg0 contains delegation signature."}
|
||||
, { 'AENS_CLAIM', 16#6f, false, false, false, ?GAS_IRIS(100, 10000), [a,a,a,a,a], aens_claim, {signature, address, string, integer, integer}, none, "Attempt to claim the name in Arg2 for address in Arg1 at a price in Arg4. Arg3 contains the salt used to hash the preclaim. Arg0 contains delegation signature."}
|
||||
, { 'AENS_UPDATE', 16#70, false, false, false, ?GAS_IRIS(100, 10000), [a,a,a,a,a,a], aens_update, {signature, address, string, variant, variant, variant}, none, "Updates name in Arg2 for address in Arg1. Arg3 contains optional ttl (of type Chain.ttl), Arg4 contains optional client_ttl (of type int), Arg5 contains optional pointers (of type map(string, pointee))"}
|
||||
, { 'AENS_TRANSFER', 16#71, false, false, false, ?GAS_IRIS(100, 10000), [a,a,a,a], aens_transfer,{signature, address, address, string}, none, "Transfer ownership of name Arg3 from account Arg1 to Arg2. Arg0 contains delegation signature."}
|
||||
, { 'AENS_REVOKE', 16#72, false, false, false, ?GAS_IRIS(100, 10000), [a,a,a], aens_revoke, {signature, address, string}, none, "Revoke the name in Arg2 from owner Arg1. Arg0 contains delegation signature."}
|
||||
, { 'BALANCE_OTHER', 16#73, false, true, true, ?GAS_IRIS( 50, 2000), [a,a], balance_other, {address}, integer, "Arg0 := The balance of address Arg1."}
|
||||
|
||||
, { 'VERIFY_SIG', 16#74, false, true, true, ?GAS(1300), [a,a,a,a], verify_sig, {bytes, address, bytes}, boolean, "Arg0 := verify_sig(Hash, PubKey, Signature)"}
|
||||
, { 'VERIFY_SIG_SECP256K1',16#75, false, true, true, ?GAS(1300), [a,a,a,a], verify_sig_secp256k1, {bytes, bytes, bytes}, boolean, "Arg0 := verify_sig_secp256k1(Hash, PubKey, Signature)"}
|
||||
|
||||
, { 'CONTRACT_TO_ADDRESS', 16#76, false, true, true, ?GAS(10), [a,a], contract_to_address, {contract}, address, "Arg0 := Arg1 - A no-op type conversion"}
|
||||
, { 'AUTH_TX_HASH', 16#77, false, true, true, ?GAS(10), [a], auth_tx_hash, {}, variant, "If in GA authentication context return Some(TxHash) otherwise None."}
|
||||
|
||||
%% Intentional gap (was oracles)
|
||||
|
||||
, { 'IS_CONTRACT', 16#7b, false, false, true, ?GAS(100), [a,a], is_contract, {address}, bool, "Arg0 := is Arg1 a contract"}
|
||||
, { 'IS_PAYABLE', 16#7c, false, false, true, ?GAS(100), [a,a], is_payable, {address}, bool, "Arg0 := is Arg1 a payable address"}
|
||||
, { 'CREATOR', 16#7d, false, true, true, ?GAS(10), [a], contract_creator, {}, address, "Arg0 := contract creator"}
|
||||
|
||||
, { 'ECVERIFY_SECP256K1', 16#7e, false, true, true, ?GAS(1300), [a,a,a,a], ecverify_secp256k1, {bytes, bytes, bytes}, bytes, "Arg0 := ecverify_secp256k1(Hash, Addr, Signature)"}
|
||||
, { 'ECRECOVER_SECP256K1', 16#7f, false, true, true, ?GAS(1300), [a,a,a], ecrecover_secp256k1, {bytes, bytes}, bytes, "Arg0 := ecrecover_secp256k1(Hash, Signature)"}
|
||||
|
||||
, { 'ADDRESS_TO_CONTRACT', 16#80, false, true, true, ?GAS(10), [a,a], address_to_contract, {address}, contract, "Arg0 := Arg1 - A no-op type conversion"}
|
||||
|
||||
, { 'BLS12_381_G1_NEG', 16#81, false, true, true, ?GAS(100), [a,a], bls12_381_g1_neg, {tuple}, tuple, "Arg0 := BLS12_381.g1_neg(Arg1) - Negate a G1-value"}
|
||||
, { 'BLS12_381_G1_NORM', 16#82, false, true, true, ?GAS(100), [a,a], bls12_381_g1_norm, {tuple}, tuple, "Arg0 := BLS12_381.g1_normalize(Arg1) - Normalize a G1-value"}
|
||||
, { 'BLS12_381_G1_VALID', 16#83, false, true, true, ?GAS(2000), [a,a], bls12_381_g1_valid, {tuple}, bool, "Arg0 := BLS12_381.g1_valid(Arg1) - Check if G1-value is a valid group member"}
|
||||
, { 'BLS12_381_G1_IS_ZERO', 16#84, false, true, true, ?GAS(30), [a,a], bls12_381_g1_is_zero, {tuple}, bool, "Arg0 := BLS12_381.g1_is_zero(Arg1) - Check if G1-value is zero"}
|
||||
, { 'BLS12_381_G1_ADD', 16#85, false, true, true, ?GAS(100), [a,a,a], bls12_381_g1_add, {tuple, tuple}, tuple, "Arg0 := BLS12_381.g1_add(Arg1, Arg2) - Add two G1-values"}
|
||||
, { 'BLS12_381_G1_MUL', 16#86, false, true, true, ?GAS(1000), [a,a,a], bls12_381_g1_mul, {tuple, tuple}, tuple, "Arg0 := BLS12_381.g1_mul(Arg1, Arg2) - Scalar multiplication for a G1-value (Arg1), and an Fr-value"}
|
||||
|
||||
, { 'BLS12_381_G2_NEG', 16#87, false, true, true, ?GAS(100), [a,a], bls12_381_g2_neg, {tuple}, tuple, "Arg0 := BLS12_381.g2_neg(Arg1) - Negate a G2-value"}
|
||||
, { 'BLS12_381_G2_NORM', 16#88, false, true, true, ?GAS(100), [a,a], bls12_381_g2_norm, {tuple}, tuple, "Arg0 := BLS12_381.g2_normalize(Arg1) - Normalize a G2-value"}
|
||||
, { 'BLS12_381_G2_VALID', 16#89, false, true, true, ?GAS(2000), [a,a], bls12_381_g2_valid, {tuple}, bool, "Arg0 := BLS12_381.g2_valid(Arg1) - Check if G2-value is a valid group member"}
|
||||
, { 'BLS12_381_G2_IS_ZERO', 16#8a, false, true, true, ?GAS(30), [a,a], bls12_381_g2_is_zero, {tuple}, bool, "Arg0 := BLS12_381.g2_is_zero(Arg1) - Check if G2-value is zero"}
|
||||
, { 'BLS12_381_G2_ADD', 16#8b, false, true, true, ?GAS(100), [a,a,a], bls12_381_g2_add, {tuple, tuple}, tuple, "Arg0 := BLS12_381.g2_add(Arg1, Arg2) - Add two G2-values"}
|
||||
, { 'BLS12_381_G2_MUL', 16#8c, false, true, true, ?GAS(1000), [a,a,a], bls12_381_g2_mul, {tuple, tuple}, tuple, "Arg0 := BLS12_381.g2_mul(Arg1, Arg2) - Scalar multiplication for a G2-value (Arg2), and an Fr-value"}
|
||||
|
||||
, { 'BLS12_381_GT_INV', 16#8d, false, true, true, ?GAS(100), [a,a], bls12_381_gt_inv, {tuple}, tuple, "Arg0 := BLS12_381.gt_inv(Arg1) - Invert a GT-value"}
|
||||
, { 'BLS12_381_GT_ADD', 16#8e, false, true, true, ?GAS(100), [a,a,a], bls12_381_gt_add, {tuple, tuple}, tuple, "Arg0 := BLS12_381.gt_add(Arg1, Arg2) - Add two GT-values"}
|
||||
, { 'BLS12_381_GT_MUL', 16#8f, false, true, true, ?GAS(100), [a,a,a], bls12_381_gt_mul, {tuple, tuple}, tuple, "Arg0 := BLS12_381.gt_mul(Arg1, Arg2) - Multiply two GT-values"}
|
||||
, { 'BLS12_381_GT_POW', 16#90, false, true, true, ?GAS(2000), [a,a,a], bls12_381_gt_pow, {tuple, tuple}, tuple, "Arg0 := BLS12_381.gt_pow(Arg1, Arg2) - Scalar exponentiation for a GT-value (Arg2), and an Fr-value"}
|
||||
, { 'BLS12_381_GT_IS_ONE', 16#91, false, true, true, ?GAS(30), [a,a], bls12_381_gt_is_one, {tuple}, bool, "Arg0 := BLS12_381.gt_is_one(Arg1) - Check if a GT value is \"one\""}
|
||||
, { 'BLS12_381_PAIRING', 16#92, false, true, true, ?GAS(12000), [a,a,a], bls12_381_pairing, {tuple, tuple}, tuple, "Arg0 := BLS12_381.pairing(Arg1, Arg2) - Find the pairing of a G1-value (Arg1) and a G2-value (Arg2)"}
|
||||
, { 'BLS12_381_MILLER_LOOP', 16#93, false, true, true, ?GAS(5000), [a,a,a], bls12_381_miller_loop, {tuple, tuple}, tuple, "Arg0 := BLS12_381.miller_loop(Arg1, Arg2) - Do the Miller-loop step of pairing for a G1-value (Arg1) and a G2-value (Arg2)"}
|
||||
, { 'BLS12_381_FINAL_EXP', 16#94, false, true, true, ?GAS(7000), [a,a], bls12_381_final_exp, {tuple}, tuple, "Arg0 := BLS12_381.final_exp(Arg1) - Do the final exponentiation in pairing"}
|
||||
|
||||
, { 'BLS12_381_INT_TO_FR', 16#95, false, true, true, ?GAS(30), [a,a], bls12_381_int_to_fr, {tuple}, tuple, "Arg0 := to_montgomery(Arg1) - Convert (Big)integer to montgomery representation (32 bytes)"}
|
||||
, { 'BLS12_381_INT_TO_FP', 16#96, false, true, true, ?GAS(30), [a,a], bls12_381_int_to_fp, {tuple}, tuple, "Arg0 := to_montgomery(Arg1) - Convert (Big)integer to montgomery representation (48 bytes)"}
|
||||
, { 'BLS12_381_FR_TO_INT', 16#97, false, true, true, ?GAS(30), [a,a], bls12_381_fr_to_int, {tuple}, tuple, "Arg0 := from_montgomery(Arg1) - Convert montgomery representation (32 bytes) to integer"}
|
||||
, { 'BLS12_381_FP_TO_INT', 16#98, false, true, true, ?GAS(30), [a,a], bls12_381_fp_to_int, {tuple}, tuple, "Arg0 := from_montgomery(Arg1) - Convert montgomery representation (48 bytes) to integer"}
|
||||
|
||||
, { 'AENS_LOOKUP', 16#99, false, false, true, ?GAS(2000), [a,a], aens_lookup, {string}, variant, "Lookup the name of Arg0. Returns option(AENS.name)"}
|
||||
|
||||
%% Intentional gap (was oracles)
|
||||
|
||||
, { 'AUTH_TX', 16#9b, false, true, true, ?GAS(100 ), [a], auth_tx, {}, variant, "If in GA authentication context return Some(Tx) otherwise None."}
|
||||
|
||||
, { 'STR_TO_LIST', 16#9c, false, true, true, ?GAS(100), [a,a], str_to_list, {string}, list, "Arg0 := string converted to list of characters"}
|
||||
, { 'STR_FROM_LIST', 16#9d, false, true, true, ?GAS(100), [a,a], str_from_list, {list}, string, "Arg0 := string converted from list of characters"}
|
||||
, { 'STR_TO_UPPER', 16#9e, false, true, true, ?GAS(100), [a,a], str_to_upper, {string}, string, "Arg0 := to_upper(string)"}
|
||||
, { 'STR_TO_LOWER', 16#9f, false, true, true, ?GAS(100), [a,a], str_to_lower, {string}, string, "Arg0 := to_lower(string)"}
|
||||
, { 'CHAR_TO_INT', 16#a0, false, true, true, ?GAS(10), [a,a], char_to_int, {char}, int, "Arg0 := integer representation of UTF-8 character"}
|
||||
, { 'CHAR_FROM_INT', 16#a1, false, true, true, ?GAS(10), [a,a], char_from_int, {int}, variant, "Arg0 := Some(UTF-8 character) from integer if valid, None if not valid."}
|
||||
|
||||
, { 'CALL_PGR', 16#a2, true, false, true, ?GAS(100), [a,is,a,a,a,a,a], call_pgr, {contract, string, typerep, typerep, integer, integer, bool}, variant, "Potentially protected remote call. Arg5 is protected flag, otherwise as CALL_GR."}
|
||||
|
||||
, { 'CREATE', 16#a3, true, false, true, ?GAS(10000), [a,a,a], create, {contract_bytearray, typerep, integer}, contract, "Deploys a contract with a bytecode Arg1 and value Arg3. The `init` arguments should be placed on the stack and match the type in Arg2. Writes contract address to the top of the accumulator stack. If an account on the resulting address did exist before the call, the `payable` flag will be updated."}
|
||||
, { 'CLONE', 16#a4, true, false, true, ?GAS(5000), [a,a,a,a], clone, {contract, typerep, integer, bool}, any, "Clones the contract under Arg1 and deploys it with value of Arg3. The `init` arguments should be placed on the stack and match the type in Arg2. Writes contract (or `None` on fail when protected) to the top of the accumulator stack. Does not copy the existing contract's store – it will be initialized by a fresh call to the `init` function. If an account on the resulting address did exist before the call, the `payable` flag will be updated."}
|
||||
, { 'CLONE_G', 16#a5, true, false, true, ?GAS(5000), [a,a,a,a,a], clone_g, {contract, typerep, integer, integer, bool}, any, "Like `CLONE` but additionally limits the gas of the `init` call by Arg3"}
|
||||
, { 'BYTECODE_HASH', 16#a6, false, true, true, ?GAS(100), [a,a], bytecode_hash, {contract}, variant, "Arg0 := hash of the deserialized contract's bytecode under address given in Arg1 (or `None` on fail). Fails on AEVM contracts and contracts deployed before Iris."}
|
||||
|
||||
, { 'FEE', 16#a7, false, true, true, ?GAS(10), [a], fee, {}, integer, "Arg0 := The fee for the current call tx."}
|
||||
|
||||
, { 'ADDRESS_TO_BYTES', 16#a8, false, true, true, ?GAS(10), [a, a], addr_to_bytes, {address}, bytes, "Arg0 := the byte representation of the address"}
|
||||
, { 'POSEIDON', 16#a9, false, true, true, ?GAS(6000), [a, a, a], poseidon, {integer, integer}, integer, "Arg0 := the Poseidon hash of Arg1 and Arg2 - all integers in the BLS12-381 scalar field"}
|
||||
, { 'MULMOD', 16#aa, false, true, true, ?GAS(10), [a, a, a, a], mulmod, {integer, integer, integer}, integer, "Arg0 := (Arg1 * Arg2) mod Arg3"}
|
||||
, { 'BAND', 16#ab, false, true, true, ?GAS(10), [a, a, a], bin_and, {integer, integer}, integer, "Arg0 := Arg1 & Arg2"}
|
||||
, { 'BOR', 16#ac, false, true, true, ?GAS(10), [a, a, a], bin_or, {integer, integer}, integer, "Arg0 := Arg1 | Arg2"}
|
||||
, { 'BXOR', 16#ad, false, true, true, ?GAS(10), [a, a, a], bin_xor, {integer, integer}, integer, "Arg0 := Arg1 ^ Arg2"}
|
||||
, { 'BNOT', 16#ae, false, true, true, ?GAS(10), [a, a], bin_not, {integer}, integer, "Arg0 := ~Arg1"}
|
||||
, { 'BSL', 16#af, false, true, true, ?GAS(10), [a, a, a], bin_sl, {integer, integer}, integer, "Arg0 := Arg1 << Arg2"}
|
||||
, { 'BSR', 16#b0, false, true, true, ?GAS(10), [a, a, a], bin_sr, {integer, integer}, integer, "Arg0 := Arg1 >> Arg2"}
|
||||
, { 'BYTES_SPLIT_ANY', 16#b1, false, true, true, ?GAS(10), [a, a, a], bytes_split_any, {bytes, integer}, variant, "Arg0 := bytes_split_any(Arg1, Arg2), where a positive Arg2 is the length of the first chunk, and a negative Arg2 is the length of the second chunk. Returns None if byte array is not long enough."}
|
||||
, { 'BYTES_SIZE', 16#b2, false, true, true, ?GAS(10), [a, a], bytes_size, {bytes}, integer, "Arg0 := bytes_size(Arg1), returns the number of bytes in the byte array."}
|
||||
, { 'BYTES_TO_FIXED_SIZE', 16#b3, false, true, true, ?GAS(10), [a, a, a], bytes_to_fixed_size, {bytes, integer}, variant, "Arg0 := bytes_to_fixed_size(Arg1, Arg2), returns Some(Arg1') if byte_size(Arg1) == Arg2, None otherwise. The type of Arg1' is bytes(Arg2) but the value is unchanged"}
|
||||
, { 'INT_TO_BYTES', 16#b4, false, true, true, ?GAS(10), [a, a, a], int_to_bytes, {integer, integer}, bytes, "Arg0 := turn integer Arg1 into a byte array (big endian) length Arg2 (truncating if not fit)."}
|
||||
, { 'STR_TO_BYTES', 16#b5, false, true, true, ?GAS(10), [a, a], str_to_bytes, {integer}, bytes, "Arg0 := turn string Arg1 into the corresponding byte array."}
|
||||
, { 'NETWORK_ID', 16#b6, false, true, true, ?GAS(10), [a], network_id, {}, string, "Arg0 := The network_id of the chain."}
|
||||
|
||||
, { 'DBG_LOC', 16#f0, false, true, true, ?GAS(0), [a, a], dbg_loc, {string, integer}, none, "Debug Op: Execution location. Args = {file_name, line_num}" }
|
||||
, { 'DBG_DEF', 16#f1, false, true, true, ?GAS(0), [a, a], dbg_def, {string, any}, none, "Debug Op: Define a variable. Args = {var_name, register}" }
|
||||
, { 'DBG_UNDEF', 16#f2, false, true, true, ?GAS(0), [a, a], dbg_undef, {string, any}, none, "Debug Op: Undefine a variable. Args = {var_name, register}" }
|
||||
, { 'DBG_CONTRACT', 16#f3, false, true, true, ?GAS(0), [a], dbg_contract, {string}, none, "Debug Op: Name the current contract. Args: {contract_name}"}
|
||||
|
||||
, { 'DEACTIVATE', 16#fa, false, true, true, ?GAS(10), [], deactivate, {}, none, "Mark the current contract for deactivation."}
|
||||
, { 'ABORT', 16#fb, true, true, true, ?GAS(10), [a], abort, {string}, none, "Abort execution (dont use all gas) with error message in Arg0."}
|
||||
, { 'EXIT', 16#fc, true, true, true, ?GAS(10), [a], exit, {string}, none, "Abort execution (use upp all gas) with error message in Arg0."}
|
||||
, { 'NOP', 16#fd, false, true, true, ?GAS(1), [], nop, {}, none, "The no op. does nothing."}
|
||||
%% FUNCTION 16#fe "Function declaration and entrypoint."
|
||||
%% EXTEND 16#ff "Reserved for future extensions beyond one byte opcodes."
|
||||
].
|
||||
|
||||
|
||||
generate_header_file(Filename, Ops) ->
|
||||
{ok, File} = file:open(Filename, [write]),
|
||||
Defines = lists:flatten([gen_defines(Op) || Op <- Ops]),
|
||||
io:format(File, "~s", [prelude("Provides opcode defines.\n")]),
|
||||
io:format(File, "%% FATE opcodes\n~s", [Defines]),
|
||||
io:format(File, "~s",
|
||||
["-define('FUNCTION' , 16#fe).\n"
|
||||
"-define('EXTEND' , 16#ff).\n\n"]),
|
||||
file:close(File).
|
||||
|
||||
generate_opcodes_ops(Modulename, HrlFile, SrcDir, Ops) ->
|
||||
Filename = SrcDir ++ atom_to_list(Modulename) ++ ".erl",
|
||||
|
||||
{ok, File} = file:open(Filename, [write]),
|
||||
Mnemonic = lists:flatten([gen_mnemonic(Op) || Op <- Ops]),
|
||||
ToOp = lists:flatten([gen_m_to_op(Op) || Op <- Ops]),
|
||||
Args = lists:flatten([gen_args(Op) || Op <- Ops]),
|
||||
EndBB = lists:flatten([gen_bb(Op) || Op <- Ops]),
|
||||
InAuth = lists:flatten([gen_in_auth(Op) || Op <- Ops]),
|
||||
Offchain = lists:flatten([gen_allowed_offchain(Op) || Op <- Ops]),
|
||||
GasCost = lists:flatten([gen_gas_cost(Op) || Op <- Ops]),
|
||||
|
||||
io:format(File, "~s", [prelude("Provides opcode primitives.\n")]),
|
||||
io:format(File, "~s", [ops_exports(Modulename, HrlFile,
|
||||
["args/1\n"
|
||||
" , end_bb/1\n"
|
||||
" , in_auth/1\n"
|
||||
" , allowed_offchain/1\n"
|
||||
" , mnemonic/1\n"
|
||||
" , m_to_op/1\n"
|
||||
" , gas_cost/1\n"
|
||||
])]),
|
||||
|
||||
io:format(File, "%% FATE mnemonics\n~s", [Mnemonic]),
|
||||
io:format(File, "mnemonic(Op) -> exit({bad_opcode, Op}).\n\n", []),
|
||||
|
||||
io:format(File, "%% FATE opcodes\n~s", [ToOp]),
|
||||
io:format(File, "m_to_op(M) -> exit({bad_mnemonic, M}).\n\n", []),
|
||||
|
||||
io:format(File, "%% FATE numbers of args to op.\n~s", [Args]),
|
||||
io:format(File, "args(Op) -> exit({bad_opcode, Op}).\n\n", []),
|
||||
|
||||
io:format(File, "%% Does FATE Op end a Basic Block?\n~s", [EndBB]),
|
||||
io:format(File, "end_bb(_) -> false.\n\n", []),
|
||||
|
||||
io:format(File, "%% Is FATE Op allowed in GA Authentication context?\n~s", [InAuth]),
|
||||
io:format(File, "in_auth(_) -> false.\n\n", []),
|
||||
|
||||
io:format(File, "%% Is FATE Op allowed in a state channel offchain context?\n~s", [Offchain]),
|
||||
io:format(File, "allowed_offchain(_) -> false.\n\n", []),
|
||||
|
||||
io:format(File, "%% Base cost of operation\n~s", [GasCost]),
|
||||
io:format(File, "gas_cost(Op) -> exit({bad_opcode, Op}).\n\n", []),
|
||||
|
||||
file:close(File).
|
||||
|
||||
generate_code_ops(Modulename, SrcDir, Ops) ->
|
||||
Filename = SrcDir ++ atom_to_list(Modulename) ++ ".erl",
|
||||
|
||||
{ok, File} = file:open(Filename, [write]),
|
||||
Types = lists:flatten([gen_type(Op) || Op <- Ops]),
|
||||
TypeExports = lists:flatten([gen_type_exports(Op) || Op <- Ops]),
|
||||
[#{type_name := FirstType} | RestOfOps] = Ops,
|
||||
FateTypes = lists:flatten([gen_fate_code_type(Op) || Op <- RestOfOps]),
|
||||
ConstructorExports = lists:flatten([gen_constructor_exports(Op) || Op <- Ops]),
|
||||
Constructors = lists:flatten([gen_constructors(Op) || Op <- Ops]),
|
||||
|
||||
io:format(File, "~s", [prelude(" Provide constructor functuions for "
|
||||
"Fate instructions.\n%%% Provide types"
|
||||
" and documentation for Fate "
|
||||
"instructions.\n")]),
|
||||
io:format(File, "-module(~w).\n\n", [Modulename]),
|
||||
io:format(File, "-include_lib(\"gmbytecode/include/gmb_fate_data.hrl\").\n\n"
|
||||
"-define(i(__X__), {immediate, __X__ }).\n\n"
|
||||
"-type fate_arg_immediate(T) :: {immediate, T}.\n"
|
||||
"-type fate_arg_var() :: {var, integer()}.\n"
|
||||
"-type fate_arg_arg() :: {arg, integer()}.\n"
|
||||
"-type fate_arg_stack() :: {stack, 0}.\n"
|
||||
"-type fate_arg() :: fate_arg_immediate()\n"
|
||||
" | fate_arg_var()\n"
|
||||
" | fate_arg_arg()\n"
|
||||
" | fate_arg_stack().\n\n"
|
||||
"-type fate_arg_immediate() :: {immediate, gmb_fate_data:fate_type()}.\n"
|
||||
, []),
|
||||
io:format(File, "~s", [Types]),
|
||||
io:format(File, "-type fate_code() :: ~s\n~s .\n\n",
|
||||
[FirstType, FateTypes]),
|
||||
io:format(File, "-export_type([ fate_code/0\n~s ]).\n\n", [TypeExports]),
|
||||
io:format(File, "-export([ foo/0\n~s ]).\n\n", [ConstructorExports]),
|
||||
io:format(File, "~s\n", [Constructors]),
|
||||
|
||||
io:format(File, "foo() -> \"A temp hack.\".\n", []),
|
||||
|
||||
file:close(File).
|
||||
|
||||
gen_type(#{type_name := TypeName, type := Type}) ->
|
||||
lists:flatten(io_lib:format("-type ~-29s :: ~s.\n",
|
||||
[TypeName, Type])).
|
||||
|
||||
gen_fate_code_type(#{type_name := TypeName}) ->
|
||||
lists:flatten(io_lib:format(" | ~s\n", [TypeName])).
|
||||
|
||||
gen_type_exports(#{type_name := TypeName}) ->
|
||||
lists:flatten(io_lib:format(" , ~s/0\n", [TypeName--"()"])).
|
||||
|
||||
gen_constructor_exports(#{constructor_type := Function}) ->
|
||||
lists:flatten(io_lib:format(" , ~s\n", [Function])).
|
||||
|
||||
gen_constructors(#{constructor := Function, format := [],
|
||||
type_name := Type, opname := Name}) ->
|
||||
lists:flatten(io_lib:format("-spec ~s() -> ~s.\n"
|
||||
"~s() ->\n"
|
||||
" ~w.\n\n",
|
||||
[Function, Type, Function, Name]));
|
||||
gen_constructors(#{constructor := Function, format := ArgSpec,
|
||||
type_name := Type, opname := Name}) ->
|
||||
ArgTypeSpecs = gen_arg_type_specs(ArgSpec),
|
||||
Args = gen_arg_names(0, ArgSpec),
|
||||
UseArgs = gen_arg_uses(0, ArgSpec),
|
||||
lists:flatten(io_lib:format("-spec ~s(~s) -> ~s.\n"
|
||||
"~s(~s) ->\n"
|
||||
" {~w, ~s}.\n\n",
|
||||
[Function, ArgTypeSpecs, Type,
|
||||
Function, Args, Name, UseArgs])).
|
||||
|
||||
gen_arg_type_specs([]) -> [];
|
||||
gen_arg_type_specs([a]) -> "fate_arg()";
|
||||
gen_arg_type_specs([is]) -> "gmb_fate_data:fate_string()";
|
||||
gen_arg_type_specs([ii]) -> "gmb_fate_data:fate_integer()";
|
||||
gen_arg_type_specs([li]) -> "[gmb_fate_data:fate_integer()]";
|
||||
gen_arg_type_specs([t]) -> "gmb_fate_data:fate_type_type()";
|
||||
gen_arg_type_specs([a | Args]) -> "fate_arg(), " ++ gen_arg_type_specs(Args);
|
||||
gen_arg_type_specs([is | Args]) -> "gmb_fate_data:fate_string(), " ++ gen_arg_type_specs(Args);
|
||||
gen_arg_type_specs([ii | Args]) -> "gmb_fate_data:fate_integer(), " ++ gen_arg_type_specs(Args);
|
||||
gen_arg_type_specs([li | Args]) -> "[gmb_fate_data:fate_integer()], " ++ gen_arg_type_specs(Args);
|
||||
gen_arg_type_specs([t | Args]) -> "gmb_fate_data:fate_type_type(), " ++ gen_arg_type_specs(Args).
|
||||
|
||||
|
||||
gen_arg_names(_, []) ->
|
||||
[];
|
||||
gen_arg_names(N, [_]) -> io_lib:format("Arg~w", [N]);
|
||||
gen_arg_names(N, [_|Args]) ->
|
||||
io_lib:format("Arg~w, ", [N]) ++ gen_arg_names(N+1, Args).
|
||||
|
||||
gen_arg_uses(_, []) ->
|
||||
[];
|
||||
gen_arg_uses(N, [a]) -> io_lib:format("Arg~w", [N]);
|
||||
gen_arg_uses(N, [is]) -> io_lib:format("{immediate, Arg~w}", [N]);
|
||||
gen_arg_uses(N, [ii]) -> io_lib:format("{immediate, Arg~w}", [N]);
|
||||
gen_arg_uses(N, [li]) -> io_lib:format("{immediate, Arg~w}", [N]);
|
||||
gen_arg_uses(N, [t]) -> io_lib:format("Arg~w", [N]);
|
||||
gen_arg_uses(N, [a | Args]) ->
|
||||
io_lib:format("Arg~w, ", [N]) ++ gen_arg_uses(N+1, Args);
|
||||
gen_arg_uses(N, [is | Args]) ->
|
||||
io_lib:format("{immediate, Arg~w}, ", [N]) ++ gen_arg_uses(N+1, Args);
|
||||
gen_arg_uses(N, [ii | Args]) ->
|
||||
io_lib:format("{immediate, Arg~w}, ", [N]) ++ gen_arg_uses(N+1, Args);
|
||||
gen_arg_uses(N, [li | Args]) ->
|
||||
io_lib:format("[{immediate, I} || I <- Arg~w], ", [N]) ++ gen_arg_uses(N+1, Args);
|
||||
gen_arg_uses(N, [t | Args]) ->
|
||||
io_lib:format("Arg~w, ", [N]) ++ gen_arg_uses(N+1, Args).
|
||||
|
||||
|
||||
ops_exports(Module, HrlFile, Exports) ->
|
||||
lists:flatten(io_lib:format(
|
||||
"-module(~w).\n\n"
|
||||
"-export([ ~s ]).\n\n"
|
||||
"-include_lib(\"gmbytecode/" ++ HrlFile ++"\").\n\n"
|
||||
"%%====================================================================\n"
|
||||
"%% API\n"
|
||||
"%%====================================================================\n",
|
||||
[Module, Exports])).
|
||||
|
||||
gen_mnemonic(#{opname := Name, macro := Macro}) ->
|
||||
lists:flatten(io_lib:format("mnemonic(~24s) -> ~24w ;\n",
|
||||
[Macro, Name])).
|
||||
|
||||
gen_m_to_op(#{opname := Name, macro := Macro}) ->
|
||||
lists:flatten(io_lib:format("m_to_op(~24w) -> ~24s ;\n",
|
||||
[Name, Macro])).
|
||||
|
||||
gen_args(#{macro := Macro, arity := Arity}) ->
|
||||
lists:flatten(io_lib:format("args(~24s) -> ~2w ;\n",
|
||||
[Macro, Arity])).
|
||||
|
||||
gen_bb(#{macro := Macro, end_bb := EndBB}) ->
|
||||
lists:flatten(io_lib:format("end_bb(~24s) -> ~w ;\n",
|
||||
[Macro, EndBB])).
|
||||
|
||||
gen_in_auth(#{macro := Macro, in_auth := InAuth}) ->
|
||||
lists:flatten(io_lib:format("in_auth(~24s) -> ~w ;\n",
|
||||
[Macro, InAuth])).
|
||||
|
||||
gen_allowed_offchain(#{macro := Macro, offchain := Offchain}) ->
|
||||
lists:flatten(io_lib:format("allowed_offchain(~24s) -> ~w ;\n",
|
||||
[Macro, Offchain])).
|
||||
|
||||
gen_gas_cost(#{macro := Macro, gas := Gas}) ->
|
||||
lists:flatten(io_lib:format("gas_cost(~24s) -> ~w ;\n",
|
||||
[Macro, Gas])).
|
||||
|
||||
prelude(Doc) ->
|
||||
"%%%-------------------------------------------------------------------\n"
|
||||
"%%% @copyright (C) 2019, Aeternity Anstalt\n"
|
||||
"%%%\n"
|
||||
"%%% === === N O T E : This file is generated do not edit. === ===\n"
|
||||
"%%%\n"
|
||||
"%%% Source is in gmb_fate_generate_ops.erl\n"
|
||||
"%%% @doc\n"
|
||||
"%%% "++Doc++
|
||||
"%%% @end\n"
|
||||
"%%%-------------------------------------------------------------------\n\n".
|
||||
|
||||
|
||||
gen_defines(#{opname := Name, opcode := OpCode}) ->
|
||||
lists:flatten(io_lib:format("-define(~-29w, 16#~2.16.0b).\n", [Name, OpCode])).
|
||||
|
||||
gen([]) ->
|
||||
[];
|
||||
gen([{OpName, OpCode, EndBB, InAuth, AllowedOffchain, Gas, FateFormat, Constructor, ArgTypes, ResType, Doc} | Rest]) ->
|
||||
Arity = length(FateFormat),
|
||||
Name = atom_to_list(OpName),
|
||||
LowerName = string:to_lower(Name),
|
||||
TypeName = "fate_" ++ LowerName ++ "()",
|
||||
Macro = "?" ++ Name,
|
||||
Type = case FateFormat of
|
||||
[] -> io_lib:format("~w", [OpName]);
|
||||
Args ->
|
||||
io_lib:format("{~w, ~s}", [OpName, expand_types(Args)])
|
||||
end,
|
||||
ConstructorType = atom_to_list(Constructor) ++ "/" ++ io_lib:format("~w", [Arity]),
|
||||
|
||||
[#{ opname => OpName
|
||||
, opcode => OpCode
|
||||
, arity => Arity
|
||||
, end_bb => EndBB
|
||||
, in_auth => InAuth
|
||||
, offchain => AllowedOffchain
|
||||
, format => FateFormat
|
||||
, macro => Macro
|
||||
, type_name => TypeName
|
||||
, doc => Doc
|
||||
, gas => Gas
|
||||
, type => Type
|
||||
, constructor => Constructor
|
||||
, constructor_type => ConstructorType
|
||||
, arg_types => ArgTypes
|
||||
, res_type => ResType
|
||||
}| gen(Rest)].
|
||||
|
||||
|
||||
expand_types([]) -> "";
|
||||
expand_types([T]) -> expand_type(T);
|
||||
expand_types([T|Ts]) ->expand_type(T) ++ ", " ++ expand_types(Ts).
|
||||
|
||||
expand_type(a) -> "fate_arg()";
|
||||
expand_type(is) -> "fate_arg_immediate(gmb_fate_data:fate_string())";
|
||||
expand_type(ii) -> "fate_arg_immediate(gmb_fate_data:fate_integer())";
|
||||
expand_type(li) -> "fate_arg_immediate([gmb_fate_data:fate_integer()])";
|
||||
expand_type(t) -> "gmb_fate_data:fate_type_type()".
|
||||
|
||||
generate_scanner(TemplateFile, Outfile, Path, Ops) ->
|
||||
{ok, Template} = file:read_file(filename:join(Path,TemplateFile)),
|
||||
Tokens = lists:flatten([gen_token(Op) || Op <- Ops]),
|
||||
NewFile = insert_tokens_in_template(Template, Tokens),
|
||||
file:write_file(filename:join(Path, Outfile), NewFile).
|
||||
|
||||
gen_token(#{opname := OpName}) ->
|
||||
Name = atom_to_list(OpName),
|
||||
io_lib:format("~-28s: {token, {mnemonic, TokenLine, ~w}}.\n",
|
||||
[Name, OpName]).
|
||||
|
||||
insert_tokens_in_template(<<"%% ###REPLACEWITHOPTOKENS###", Rest/binary >>, Tokens) ->
|
||||
[Tokens, Rest];
|
||||
insert_tokens_in_template(<<"%%% ###REPLACEWITHNOTE###", Rest/binary >>, Tokens) ->
|
||||
[
|
||||
"%%%\n"
|
||||
"%%% === === N O T E : This file is generated do not edit. === ===\n"
|
||||
"%%%\n"
|
||||
"%%% Source is in gmb_fate_generate_ops.erl\n"
|
||||
"%%% and gmb_fate_asm_scan.template"
|
||||
| insert_tokens_in_template(Rest, Tokens)];
|
||||
insert_tokens_in_template(<<B,Rest/binary>>, Tokens) ->
|
||||
[B|insert_tokens_in_template(Rest, Tokens)].
|
||||
|
||||
gen_asm_pp(Module, Path, Ops) ->
|
||||
Filename = filename:join(Path, atom_to_list(Module)) ++ ".erl",
|
||||
{ok, File} = file:open(Filename, [write]),
|
||||
Formats = lists:flatten([gen_format(Op)++"\n" || Op <- Ops]),
|
||||
|
||||
io:format(File, "~s", [prelude(" Provide pretty printing functuions for "
|
||||
"Fate instructions.\n")]),
|
||||
io:format(File, "-module(~w).\n\n", [Module]),
|
||||
io:format(File,
|
||||
"-export([format_op/2]).\n\n"
|
||||
"format_arg(li, {immediate, LI}) ->\n"
|
||||
" gmb_fate_data:format(LI);\n"
|
||||
"format_arg(_, {immediate, I}) ->\n"
|
||||
" gmb_fate_data:format(I);\n"
|
||||
"format_arg(a, {arg, N}) -> io_lib:format(\"arg~~p\", [N]);\n"
|
||||
"format_arg(a, {var, N}) when N < 0 -> io_lib:format(\"store~~p\", [-N]);\n"
|
||||
"format_arg(a, {var, N}) -> io_lib:format(\"var~~p\", [N]);\n"
|
||||
"format_arg(a, {stack, 0}) -> \"a\".\n\n"
|
||||
"lookup(Name, Symbols) ->\n"
|
||||
" maps:get(Name, Symbols, io_lib:format(\"~~p\",[Name])).\n\n"
|
||||
"~s"
|
||||
, [Formats]),
|
||||
|
||||
io:format(File, "format_op(Op, _Symbols) -> io_lib:format(\";; Bad Op: ~~w\\n\", [Op]).\n", []),
|
||||
file:close(File).
|
||||
|
||||
gen_format(#{opname := Name}) when (Name =:= 'CALL_R') ->
|
||||
io_lib:format("format_op({~w, {immediate, Contract}, {immediate, Function}, ArgType, RetType, Value}, Symbols) ->\n"
|
||||
" [\"~s \", lookup(Contract, Symbols), \".\", "
|
||||
"lookup(Function, Symbols), \" \", "
|
||||
"format_arg(a, ArgType), \" \", "
|
||||
"format_arg(a, RetType), \" \", "
|
||||
"format_arg(a, Value)];\n"
|
||||
"format_op({~w, Contract, {immediate, Function}, ArgType, RetType, Value}, Symbols) ->\n"
|
||||
"[\"~s \", format_arg(a, Contract), \".\", "
|
||||
"lookup(Function, Symbols), \" \", "
|
||||
"format_arg(a, ArgType), \" \", "
|
||||
"format_arg(a, RetType), \" \", "
|
||||
"format_arg(a, Value)];\n",
|
||||
[Name, atom_to_list(Name), Name, atom_to_list(Name)]);
|
||||
gen_format(#{opname := Name}) when (Name =:= 'CALL_GR') ->
|
||||
io_lib:format("format_op({~w, {immediate, Contract}, {immediate, Function}, ArgType, RetType, Value, Gas}, Symbols) ->\n"
|
||||
" [\"~s \", lookup(Contract, Symbols), \".\", "
|
||||
"lookup(Function, Symbols), \" \", "
|
||||
"format_arg(a, ArgType), \" \", "
|
||||
"format_arg(a, RetType), \" \", "
|
||||
"format_arg(a, Value), \" \", "
|
||||
"format_arg(a, Gas)];\n"
|
||||
"format_op({~w, Contract, {immediate, Function}, ArgType, RetType, Value, Gas}, Symbols) ->\n"
|
||||
"[\"~s \", format_arg(a, Contract), \".\", "
|
||||
"lookup(Function, Symbols), \" \", "
|
||||
"format_arg(a, ArgType), \" \", "
|
||||
"format_arg(a, RetType), \" \", "
|
||||
"format_arg(a, Value), \" \", "
|
||||
"format_arg(a, Gas)];\n",
|
||||
[Name, atom_to_list(Name), Name, atom_to_list(Name)]);
|
||||
gen_format(#{opname := Name, format := []}) ->
|
||||
io_lib:format("format_op(~w, _) -> [\"~s\"];", [Name, atom_to_list(Name)]);
|
||||
gen_format(#{opname := Name, format := Args}) ->
|
||||
NameAsString = atom_to_list(Name),
|
||||
case Args of
|
||||
[T0] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0)];",
|
||||
[Name, NameAsString, T0]);
|
||||
[T0, T1] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0, Arg1}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0), "
|
||||
"\" \", format_arg(~w, Arg1)];",
|
||||
[Name, NameAsString, T0, T1]);
|
||||
[T0, T1, T2] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0, Arg1, Arg2}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0), "
|
||||
"\" \", format_arg(~w, Arg1),"
|
||||
"\" \", format_arg(~w, Arg2)];",
|
||||
[Name, NameAsString, T0, T1, T2]);
|
||||
[T0, T1, T2, T3] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0, Arg1, Arg2, Arg3}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0), "
|
||||
"\" \", format_arg(~w, Arg1),"
|
||||
"\" \", format_arg(~w, Arg2),"
|
||||
"\" \", format_arg(~w, Arg3)];",
|
||||
[Name, NameAsString, T0, T1, T2, T3]);
|
||||
[T0, T1, T2, T3, T4] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0, Arg1, Arg2, Arg3, Arg4}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0), "
|
||||
"\" \", format_arg(~w, Arg1),"
|
||||
"\" \", format_arg(~w, Arg2),"
|
||||
"\" \", format_arg(~w, Arg3),"
|
||||
"\" \", format_arg(~w, Arg4)];",
|
||||
[Name, NameAsString, T0, T1, T2, T3, T4]);
|
||||
[T0, T1, T2, T3, T4, T5] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0), "
|
||||
"\" \", format_arg(~w, Arg1),"
|
||||
"\" \", format_arg(~w, Arg2),"
|
||||
"\" \", format_arg(~w, Arg3),"
|
||||
"\" \", format_arg(~w, Arg4),"
|
||||
"\" \", format_arg(~w, Arg5)];",
|
||||
[Name, NameAsString, T0, T1, T2, T3, T4, T5]);
|
||||
[T0, T1, T2, T3, T4, T5, T6] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0), "
|
||||
"\" \", format_arg(~w, Arg1),"
|
||||
"\" \", format_arg(~w, Arg2),"
|
||||
"\" \", format_arg(~w, Arg3),"
|
||||
"\" \", format_arg(~w, Arg4),"
|
||||
"\" \", format_arg(~w, Arg5),"
|
||||
"\" \", format_arg(~w, Arg6)];",
|
||||
[Name, NameAsString, T0, T1, T2, T3, T4, T5, T6]);
|
||||
[T0, T1, T2, T3, T4, T5, T6, T7] ->
|
||||
io_lib:format(
|
||||
"format_op({~w, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7}, _) ->\n"
|
||||
" [\"~s \", format_arg(~w, Arg0), "
|
||||
"\" \", format_arg(~w, Arg1),"
|
||||
"\" \", format_arg(~w, Arg2),"
|
||||
"\" \", format_arg(~w, Arg3),"
|
||||
"\" \", format_arg(~w, Arg4),"
|
||||
"\" \", format_arg(~w, Arg5),"
|
||||
"\" \", format_arg(~w, Arg6),"
|
||||
"\" \", format_arg(~w, Arg7)];",
|
||||
[Name, NameAsString, T0, T1, T2, T3, T4, T5, T6, T7])
|
||||
end.
|
||||
|
||||
test_asm_generator(Filename) ->
|
||||
{ok, File} = file:open(Filename, [write]),
|
||||
Instructions = lists:flatten([gen_instruction(Op)++"\n" || Op <- get_ops()]),
|
||||
io:format(File,
|
||||
";; CONTRACT all_instructions\n\n"
|
||||
";; Dont expect this contract to typecheck or run.\n"
|
||||
";; Just used to check assembler rountrip of all instruction.\n\n"
|
||||
"FUNCTION foo () : {tuple, []}\n"
|
||||
"~s"
|
||||
, [Instructions]),
|
||||
io:format(File, " RETURNR ()\n", []),
|
||||
file:close(File).
|
||||
|
||||
|
||||
gen_instruction(#{opname := Name, format := []}) ->
|
||||
io_lib:format(" ~s\n", [Name]);
|
||||
gen_instruction(#{opname := Name, format := ArgTypes}) ->
|
||||
Args = lists:flatten(lists:join(" ", [gen_arg(A) || A <- ArgTypes])),
|
||||
I = io_lib:format(" ~s ~s\n", [Name, Args]),
|
||||
I.
|
||||
|
||||
%% This should be done with a Quick Check generator...
|
||||
gen_arg(a) -> any_arg();
|
||||
gen_arg(is) -> "foo";
|
||||
gen_arg(ii) -> gen_int();
|
||||
gen_arg(li) -> "[1, 2, 3]";
|
||||
gen_arg(t) -> "integer".
|
||||
|
||||
any_arg() ->
|
||||
element(rand:uniform(5), {"a", stack_arg(), var_arg(), arg_arg(), imm_arg()}).
|
||||
stack_arg() -> "a".
|
||||
arg_arg() -> "arg" ++ integer_to_list(rand:uniform(256)-1).
|
||||
var_arg() -> "var" ++ integer_to_list(rand:uniform(256)-1).
|
||||
imm_arg() ->
|
||||
case rand:uniform(15) of
|
||||
1 -> gen_int();
|
||||
2 -> gen_int();
|
||||
3 -> gen_int();
|
||||
4 -> gen_int();
|
||||
5 -> gen_int();
|
||||
6 -> gen_int();
|
||||
7 -> gen_int();
|
||||
8 -> gen_address();
|
||||
9 -> gen_boolean();
|
||||
10 -> gen_string();
|
||||
11 -> gen_map();
|
||||
12 -> gen_list();
|
||||
13 -> gen_bits();
|
||||
14 -> gen_tuple();
|
||||
15 -> gen_variant()
|
||||
end.
|
||||
|
||||
gen_key() ->
|
||||
case rand:uniform(15) of
|
||||
1 -> gen_int();
|
||||
2 -> gen_int();
|
||||
3 -> gen_int();
|
||||
4 -> gen_int();
|
||||
5 -> gen_int();
|
||||
6 -> gen_int();
|
||||
7 -> gen_int();
|
||||
8 -> gen_address();
|
||||
9 -> gen_boolean();
|
||||
10 -> gen_string();
|
||||
11 -> gen_string();
|
||||
12 -> gen_list();
|
||||
13 -> gen_bits();
|
||||
14 -> gen_tuple();
|
||||
15 -> gen_variant()
|
||||
end.
|
||||
|
||||
gen_boolean() ->
|
||||
element(rand:uniform(2), {"true", "false"}).
|
||||
|
||||
gen_int() ->
|
||||
element(rand:uniform(4),
|
||||
{ integer_to_list(rand:uniform(round(math:pow(10,40))))
|
||||
, integer_to_list(rand:uniform(10))
|
||||
, integer_to_list(rand:uniform(100))
|
||||
, io_lib:format("0x~.16b",[rand:uniform(round(math:pow(10,10)))])}).
|
||||
|
||||
gen_address() -> "#nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv".
|
||||
gen_string() -> "\"foo\"".
|
||||
gen_map() -> "{ " ++ gen_key() ++ " => " ++ imm_arg() ++ "}".
|
||||
gen_list() ->
|
||||
case rand:uniform(4) of
|
||||
1 -> "[]";
|
||||
2 -> "[" ++ lists:join(", ", gen_list_elements()) ++ " ]";
|
||||
3 -> "[ " ++ imm_arg() ++ " ]";
|
||||
4 -> "[ " ++ imm_arg() ++ ", " ++ imm_arg() ++ " ]"
|
||||
end.
|
||||
|
||||
%% Not type correct.
|
||||
gen_list_elements() ->
|
||||
case rand:uniform(3) of
|
||||
1 -> [imm_arg() | gen_list_elements()];
|
||||
2 -> [];
|
||||
3 -> [imm_arg()]
|
||||
end.
|
||||
|
||||
gen_bits() ->
|
||||
element(rand:uniform(3),
|
||||
{"<>"
|
||||
,"!<>"
|
||||
, "101010"}).
|
||||
|
||||
gen_tuple() ->
|
||||
case rand:uniform(3) of
|
||||
1 -> "()";
|
||||
2 -> "(42)";
|
||||
3 -> "(" ++ imm_arg() ++ ")"
|
||||
end.
|
||||
|
||||
gen_variant() ->
|
||||
case rand:uniform(3) of
|
||||
1 -> "(| 5 | 2 | (1, \"foo\", ()) |)";
|
||||
2 -> "(| 2 | 1 | ( " ++ imm_arg() ++ " ) |)";
|
||||
3 -> "(| 2 | 0 | ( " ++ imm_arg() ++ ", " ++ imm_arg() ++ " ) |)"
|
||||
end.
|
||||
|
@ -1,220 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Functions for manipulating FATE maps. In particular for mediating
|
||||
%%% between plain map values (represented by Erlang maps) and maps that are
|
||||
%%% fully or partially saved in the contract store.
|
||||
%%% @end
|
||||
%%% -------------------------------------------------------------------
|
||||
-module(gmb_fate_maps).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-include("gmb_fate_data.hrl").
|
||||
|
||||
-export([ allocate_store_maps/2
|
||||
, has_store_maps/1
|
||||
, unfold_store_maps/2
|
||||
, refcount/1
|
||||
, refcount_zero/0
|
||||
, refcount_diff/2
|
||||
, refcount_union/1
|
||||
, refcount_union/2
|
||||
, no_used_ids/0 ]).
|
||||
|
||||
-export_type([used_ids/0, maps/0, refcount/0]).
|
||||
|
||||
%% Size in bytes of serialization of a map for which we turn it into a store
|
||||
%% map. It's not worth turning small maps into store maps.
|
||||
%% Under consensus!
|
||||
-ifdef(TEST).
|
||||
-define(STORE_MAP_THRESHOLD, 0).
|
||||
-else.
|
||||
-define(STORE_MAP_THRESHOLD, 100).
|
||||
-endif.
|
||||
|
||||
-type fate_value() :: gmb_fate_data:fate_type().
|
||||
-type fate_value_or_tombstone() :: fate_value() | ?FATE_MAP_TOMBSTONE.
|
||||
-type id() :: integer().
|
||||
-type used_ids() :: list(id()).
|
||||
-type maps() :: #{ id() => gmb_fate_data:fate_map() | gmb_fate_data:fate_store_map() }.
|
||||
|
||||
%% -- Allocating store maps --------------------------------------------------
|
||||
|
||||
-spec allocate_store_maps(used_ids(), [fate_value_or_tombstone()]) -> {[fate_value_or_tombstone()], maps()}.
|
||||
allocate_store_maps(Used, Vals) ->
|
||||
{_Used, Vals1, Maps} = allocate_store_maps_l(Used, Vals, #{}),
|
||||
{Vals1, Maps}.
|
||||
|
||||
allocate_store_maps(Used, ?FATE_MAP_TOMBSTONE = Val, Maps) -> {Used, Val, Maps};
|
||||
allocate_store_maps(Used, ?FATE_TRUE = Val, Maps) -> {Used, Val, Maps};
|
||||
allocate_store_maps(Used, ?FATE_FALSE = Val, Maps) -> {Used, Val, Maps};
|
||||
allocate_store_maps(Used, ?FATE_UNIT = Val, Maps) -> {Used, Val, Maps};
|
||||
allocate_store_maps(Used, ?FATE_BITS(_) = Val, Maps) -> {Used, Val, Maps};
|
||||
allocate_store_maps(Used, ?FATE_BYTES(_) = Val, Maps) -> {Used, Val, Maps};
|
||||
allocate_store_maps(Used, ?FATE_ADDRESS(_) = Val, Maps) -> {Used, Val, Maps};
|
||||
allocate_store_maps(Used, ?FATE_CONTRACT(_) = Val, Maps) -> {Used, Val, Maps};
|
||||
allocate_store_maps(Used, ?FATE_ORACLE(_) = Val, Maps) -> {Used, Val, Maps};
|
||||
allocate_store_maps(Used, ?FATE_ORACLE_Q(_) = Val, Maps) -> {Used, Val, Maps};
|
||||
allocate_store_maps(Used, ?FATE_CHANNEL(_) = Val, Maps) -> {Used, Val, Maps};
|
||||
allocate_store_maps(Used, ?FATE_TYPEREP(_) = Val, Maps) -> {Used, Val, Maps};
|
||||
allocate_store_maps(Used, Val, Maps) when ?IS_FATE_INTEGER(Val) -> {Used, Val, Maps};
|
||||
allocate_store_maps(Used, Val, Maps) when ?IS_FATE_STRING(Val) -> {Used, Val, Maps};
|
||||
allocate_store_maps(Used, ?FATE_TUPLE(Val), Maps) ->
|
||||
{Used1, Vals, Maps1} = allocate_store_maps_l(Used, tuple_to_list(Val), Maps),
|
||||
{Used1, ?FATE_TUPLE(list_to_tuple(Vals)), Maps1};
|
||||
allocate_store_maps(Used, Val, Maps) when ?IS_FATE_LIST(Val) ->
|
||||
{Used1, Vals, Maps1} = allocate_store_maps_l(Used, ?FATE_LIST_VALUE(Val), Maps),
|
||||
{Used1, ?MAKE_FATE_LIST(Vals), Maps1};
|
||||
allocate_store_maps(Used, ?FATE_VARIANT(Arities, Tag, Vals), Maps) ->
|
||||
{Used1, Vals1, Maps1} = allocate_store_maps_l(Used, tuple_to_list(Vals), Maps),
|
||||
{Used1, ?FATE_VARIANT(Arities, Tag, list_to_tuple(Vals1)), Maps1};
|
||||
allocate_store_maps(Used, Val, Maps) when ?IS_FATE_MAP(Val) ->
|
||||
{Used1, KVs, Maps1} = allocate_store_maps_m(Used, ?FATE_MAP_VALUE(Val), Maps),
|
||||
Val1 = ?MAKE_FATE_MAP(KVs),
|
||||
case byte_size(gmb_fate_encoding:serialize(Val1)) < ?STORE_MAP_THRESHOLD of
|
||||
true -> {Used1, Val1, Maps1};
|
||||
false ->
|
||||
{Id, Used2} = next_id(Used1),
|
||||
{Used2, ?FATE_STORE_MAP(#{}, Id), Maps1#{Id => Val1}}
|
||||
end;
|
||||
allocate_store_maps(Used, ?FATE_STORE_MAP(Cache, _Id) = Val, Maps) when Cache =:= #{} ->
|
||||
{Used, Val, Maps};
|
||||
allocate_store_maps(Used, ?FATE_STORE_MAP(Cache, Id), Maps) ->
|
||||
{NewId, Used1} = next_id(Used),
|
||||
{Used2, Cache1, Maps1} = allocate_store_maps_m(Used1, Cache, Maps),
|
||||
{Used2, ?FATE_STORE_MAP(#{}, NewId), Maps1#{NewId => ?FATE_STORE_MAP(Cache1, Id)}}.
|
||||
|
||||
allocate_store_maps_l(Used, [], Maps) -> {Used, [], Maps};
|
||||
allocate_store_maps_l(Used, [H | T], Maps) ->
|
||||
{Used1, H1, Maps1} = allocate_store_maps(Used, H, Maps),
|
||||
{Used2, T1, Maps2} = allocate_store_maps(Used1, T, Maps1),
|
||||
{Used2, [H1 | T1], Maps2}.
|
||||
|
||||
allocate_store_maps_m(Used, Val, Maps) ->
|
||||
maps:fold(fun(K, V, {Us, M, Ms}) ->
|
||||
{Us1, V1, Ms1} = allocate_store_maps(Us, V, Ms),
|
||||
{Us1, M#{ K => V1 }, Ms1}
|
||||
end, {Used, #{}, Maps}, Val).
|
||||
|
||||
%% -- Unfolding store maps ---------------------------------------------------
|
||||
|
||||
-type unfold_fun() :: fun((id()) -> gmb_fate_data:fate_map()).
|
||||
|
||||
-spec unfold_store_maps(unfold_fun(), fate_value_or_tombstone()) -> fate_value_or_tombstone().
|
||||
unfold_store_maps(_Unfold, ?FATE_MAP_TOMBSTONE = Val) -> Val;
|
||||
unfold_store_maps(_Unfold, ?FATE_TRUE = Val) -> Val;
|
||||
unfold_store_maps(_Unfold, ?FATE_FALSE = Val) -> Val;
|
||||
unfold_store_maps(_Unfold, ?FATE_UNIT = Val) -> Val;
|
||||
unfold_store_maps(_Unfold, ?FATE_BITS(_) = Val) -> Val;
|
||||
unfold_store_maps(_Unfold, ?FATE_BYTES(_) = Val) -> Val;
|
||||
unfold_store_maps(_Unfold, ?FATE_ADDRESS(_) = Val) -> Val;
|
||||
unfold_store_maps(_Unfold, ?FATE_CONTRACT(_) = Val) -> Val;
|
||||
unfold_store_maps(_Unfold, ?FATE_ORACLE(_) = Val) -> Val;
|
||||
unfold_store_maps(_Unfold, ?FATE_ORACLE_Q(_) = Val) -> Val;
|
||||
unfold_store_maps(_Unfold, ?FATE_CHANNEL(_) = Val) -> Val;
|
||||
unfold_store_maps(_Unfold, ?FATE_TYPEREP(_) = Val) -> Val;
|
||||
unfold_store_maps(_Unfold, Val) when ?IS_FATE_INTEGER(Val) -> Val;
|
||||
unfold_store_maps(_Unfold, Val) when ?IS_FATE_STRING(Val) -> Val;
|
||||
unfold_store_maps(Unfold, ?FATE_TUPLE(Val)) ->
|
||||
Vals = unfold_store_maps_l(Unfold, tuple_to_list(Val)),
|
||||
?FATE_TUPLE(list_to_tuple(Vals));
|
||||
unfold_store_maps(Unfold, Val) when ?IS_FATE_LIST(Val) ->
|
||||
?MAKE_FATE_LIST(unfold_store_maps_l(Unfold, ?FATE_LIST_VALUE(Val)));
|
||||
unfold_store_maps(Unfold, ?FATE_VARIANT(Arities, Tag, Vals)) ->
|
||||
Vals1 = unfold_store_maps_l(Unfold, tuple_to_list(Vals)),
|
||||
?FATE_VARIANT(Arities, Tag, list_to_tuple(Vals1));
|
||||
unfold_store_maps(Unfold, Val) when ?IS_FATE_MAP(Val) ->
|
||||
?MAKE_FATE_MAP(unfold_store_maps_m(Unfold, ?FATE_MAP_VALUE(Val)));
|
||||
unfold_store_maps(Unfold, ?FATE_STORE_MAP(Cache, Id)) ->
|
||||
StoreMap = Unfold(Id),
|
||||
maps:fold(fun write_cache/3, unfold_store_maps(Unfold, StoreMap),
|
||||
unfold_store_maps_m(Unfold, Cache)).
|
||||
|
||||
unfold_store_maps_l(Unfold, Vals) ->
|
||||
[ unfold_store_maps(Unfold, Val) || Val <- Vals ].
|
||||
|
||||
unfold_store_maps_m(Unfold, Val) ->
|
||||
maps:map(fun(_, V) -> unfold_store_maps(Unfold, V) end, Val).
|
||||
|
||||
write_cache(Key, ?FATE_MAP_TOMBSTONE, Map) ->
|
||||
maps:remove(Key, Map);
|
||||
write_cache(Key, Val, Map) ->
|
||||
Map#{ Key => Val }.
|
||||
|
||||
%% -- Reference counting -----------------------------------------------------
|
||||
|
||||
-type refcount() :: #{id() => integer()}.
|
||||
|
||||
-spec refcount_zero() -> refcount().
|
||||
refcount_zero() -> #{}.
|
||||
|
||||
-spec refcount_diff(refcount(), refcount()) -> refcount().
|
||||
refcount_diff(New, Old) ->
|
||||
maps:fold(fun(K, N, C) -> maps:update_with(K, fun(M) -> M - N end, -N, C) end,
|
||||
New, Old).
|
||||
|
||||
-spec refcount_union([refcount()]) -> refcount().
|
||||
refcount_union(Counts) -> lists:foldl(fun refcount_union/2, #{}, Counts).
|
||||
|
||||
-spec refcount_union(refcount(), refcount()) -> refcount().
|
||||
refcount_union(A, B) ->
|
||||
maps:fold(fun(K, N, C) -> maps:update_with(K, fun(M) -> M + N end, N, C) end,
|
||||
B, A).
|
||||
|
||||
-spec has_store_maps(fate_value()) -> boolean().
|
||||
has_store_maps(Val) ->
|
||||
refcount_zero() /= refcount(Val).
|
||||
|
||||
-spec refcount(fate_value()) -> refcount().
|
||||
refcount(Val) -> refcount(Val, #{}).
|
||||
|
||||
-spec refcount(fate_value_or_tombstone(), refcount()) -> refcount().
|
||||
refcount(?FATE_MAP_TOMBSTONE, Count) -> Count;
|
||||
refcount(?FATE_TRUE, Count) -> Count;
|
||||
refcount(?FATE_FALSE, Count) -> Count;
|
||||
refcount(?FATE_UNIT, Count) -> Count;
|
||||
refcount(?FATE_BITS(_), Count) -> Count;
|
||||
refcount(?FATE_BYTES(_), Count) -> Count;
|
||||
refcount(?FATE_ADDRESS(_), Count) -> Count;
|
||||
refcount(?FATE_CONTRACT(_), Count) -> Count;
|
||||
refcount(?FATE_ORACLE(_), Count) -> Count;
|
||||
refcount(?FATE_ORACLE_Q(_), Count) -> Count;
|
||||
refcount(?FATE_CHANNEL(_), Count) -> Count;
|
||||
refcount(?FATE_TYPEREP(_), Count) -> Count;
|
||||
refcount(Val, Count) when ?IS_FATE_INTEGER(Val) -> Count;
|
||||
refcount(Val, Count) when ?IS_FATE_STRING(Val) -> Count;
|
||||
refcount(?FATE_TUPLE(Val), Count) ->
|
||||
refcount_l(tuple_to_list(Val), Count);
|
||||
refcount(Val, Count) when ?IS_FATE_LIST(Val) ->
|
||||
refcount_l(?FATE_LIST_VALUE(Val), Count);
|
||||
refcount(?FATE_VARIANT(_Arities, _Tag, Vals), Count) ->
|
||||
refcount_l(tuple_to_list(Vals), Count);
|
||||
refcount(Val, Count) when ?IS_FATE_MAP(Val) ->
|
||||
refcount_m(?FATE_MAP_VALUE(Val), Count);
|
||||
refcount(?FATE_STORE_MAP(Cache, Id), Count) ->
|
||||
refcount_m(Cache, maps:update_with(Id, fun(N) -> N + 1 end, 1, Count)).
|
||||
|
||||
refcount_l(Vals, Count) ->
|
||||
lists:foldl(fun refcount/2, Count, Vals).
|
||||
|
||||
refcount_m(Val, Count) ->
|
||||
%% No maps in map keys
|
||||
maps:fold(fun(_, ?FATE_MAP_TOMBSTONE, C) -> C;
|
||||
(_, V, C) -> refcount(V, C) end, Count, Val).
|
||||
|
||||
%% -- Map id allocation ------------------------------------------------------
|
||||
|
||||
-spec no_used_ids() -> used_ids().
|
||||
no_used_ids() -> [].
|
||||
|
||||
-spec next_id(used_ids()) -> {id(), used_ids()}.
|
||||
next_id(UsedIds) ->
|
||||
next_id(UsedIds, 0, []).
|
||||
|
||||
next_id(Used, J, Acc) when Used == []; J < hd(Used) ->
|
||||
{J, lists:reverse(Acc) ++ [J | Used]};
|
||||
next_id([I | Used], I, Acc) ->
|
||||
next_id(Used, I + 1, [I | Acc]);
|
||||
next_id([I | Used], J, Acc) when J > I ->
|
||||
next_id(Used, J, [I | Acc]).
|
332
src/gmb_heap.erl
332
src/gmb_heap.erl
@ -1,332 +0,0 @@
|
||||
-module(gmb_heap).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-export([ to_binary/1
|
||||
, to_binary/2
|
||||
, from_heap/3
|
||||
, from_binary/2
|
||||
, from_binary/3
|
||||
, maps_with_next_id/1
|
||||
, set_next_id/2
|
||||
, heap_fragment/3
|
||||
, heap_value/3
|
||||
, heap_value/4
|
||||
, heap_value_pointer/1
|
||||
, heap_value_maps/1
|
||||
, heap_value_offset/1
|
||||
, heap_value_heap/1
|
||||
, heap_value_byte_size/1
|
||||
, heap_fragment_maps/1
|
||||
, heap_fragment_offset/1
|
||||
, heap_fragment_heap/1
|
||||
]).
|
||||
|
||||
-export_type([binary_value/0, heap_value/0, offset/0, heap_fragment/0]).
|
||||
|
||||
-include_lib("gmbytecode/include/gmb_typerep_def.hrl").
|
||||
-include_lib("gmbytecode/include/gmb_heap.hrl").
|
||||
|
||||
-type word() :: non_neg_integer().
|
||||
-type pointer() :: word().
|
||||
-opaque heap_fragment() :: #heap{}.
|
||||
-type offset() :: non_neg_integer().
|
||||
-type binary_value() :: binary().
|
||||
-type heap_value() :: {pointer(), heap_fragment()}.
|
||||
|
||||
|
||||
-spec maps_with_next_id(heap_fragment()) -> #maps{}.
|
||||
%% Create just a maps value, don't keep rest of Heap
|
||||
maps_with_next_id(#heap{maps = #maps{next_id = N}}) ->
|
||||
#maps{ next_id = N }.
|
||||
|
||||
-spec set_next_id(heap_fragment(), non_neg_integer()) -> heap_fragment().
|
||||
set_next_id(Heap, N) ->
|
||||
Heap#heap{ maps = Heap#heap.maps#maps{ next_id = N } }.
|
||||
|
||||
%% -- data type heap_fragment
|
||||
|
||||
-spec heap_fragment(binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_fragment().
|
||||
heap_fragment(Heap) ->
|
||||
heap_fragment(#maps{ next_id = 0 }, 0, Heap).
|
||||
|
||||
-spec heap_fragment(#maps{}, offset(),
|
||||
binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_fragment().
|
||||
heap_fragment(Maps, Offset, Heap) ->
|
||||
#heap{maps = Maps, offset = Offset, heap = Heap}.
|
||||
|
||||
-spec heap_fragment_maps(heap_fragment()) -> #maps{}.
|
||||
heap_fragment_maps(#heap{maps = Maps}) ->
|
||||
Maps.
|
||||
|
||||
-spec heap_fragment_offset(heap_fragment()) -> offset().
|
||||
heap_fragment_offset(#heap{offset = Offs}) ->
|
||||
Offs.
|
||||
|
||||
-spec heap_fragment_heap(heap_fragment()) -> binary() | #{non_neg_integer() => non_neg_integer()}.
|
||||
heap_fragment_heap(#heap{heap = Heap}) ->
|
||||
Heap.
|
||||
|
||||
|
||||
%% -- data type heap_value
|
||||
|
||||
-spec heap_value(#maps{}, pointer(),
|
||||
binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_value().
|
||||
heap_value(Maps, Ptr, Heap) ->
|
||||
heap_value(Maps, Ptr, Heap, 0).
|
||||
|
||||
-spec heap_value(#maps{}, pointer(),
|
||||
binary() | #{non_neg_integer() => non_neg_integer()}, offset()) -> heap_value().
|
||||
heap_value(Maps, Ptr, Heap, Offs) ->
|
||||
{Ptr, heap_fragment(Maps, Offs, Heap)}.
|
||||
|
||||
-spec heap_value_pointer(heap_value()) -> pointer().
|
||||
heap_value_pointer({Ptr, _}) -> Ptr.
|
||||
|
||||
-spec heap_value_maps(heap_value()) -> #maps{}.
|
||||
heap_value_maps({_, Heap}) -> Heap#heap.maps.
|
||||
|
||||
-spec heap_value_offset(heap_value()) -> offset().
|
||||
heap_value_offset({_, Heap}) -> Heap#heap.offset.
|
||||
|
||||
-spec heap_value_heap(heap_value()) ->
|
||||
binary() | #{non_neg_integer() => non_neg_integer()}.
|
||||
heap_value_heap({_, Heap}) -> Heap#heap.heap.
|
||||
|
||||
%% -- Byte size of a heap value ----------------------------------------------
|
||||
|
||||
-spec heap_value_byte_size(heap_value()) -> non_neg_integer().
|
||||
heap_value_byte_size({_, Heap}) ->
|
||||
Value = Heap#heap.heap,
|
||||
Maps = Heap#heap.maps,
|
||||
ValueSize =
|
||||
if is_binary(Value) -> byte_size(Value);
|
||||
true -> 0 end,
|
||||
MapsSize =
|
||||
lists:sum([ pmap_size(Map) || Map <- maps:values(Maps#maps.maps) ]),
|
||||
ValueSize + MapsSize.
|
||||
|
||||
pmap_size(#pmap{data = stored}) -> 0;
|
||||
pmap_size(#pmap{data = Data}) when is_map(Data) ->
|
||||
lists:sum([ byte_size(Key) + byte_size(Val)
|
||||
|| {Key, Val} <- maps:to_list(Data),
|
||||
Val /= tombstone ]).
|
||||
|
||||
%% -- Value to binary --------------------------------------------------------
|
||||
|
||||
-spec to_binary(gmb_aevm_data:data()) -> gmb_aevm_data:heap().
|
||||
%% Encode the data as a heap where the first word is the value (for unboxed
|
||||
%% types) or a pointer to the value (for boxed types).
|
||||
to_binary(Data) ->
|
||||
to_binary(Data, 0).
|
||||
|
||||
to_binary(Data, BaseAddress) ->
|
||||
{Address, Memory} = to_binary1(Data, BaseAddress + 32),
|
||||
R = <<Address:256, Memory/binary>>,
|
||||
R.
|
||||
|
||||
|
||||
%% Allocate the data in memory, from the given address. Return a pair
|
||||
%% of memory contents from that address and the value representing the
|
||||
%% data.
|
||||
to_binary1(Data,_Address) when is_integer(Data) ->
|
||||
{Data,<<>>};
|
||||
to_binary1(Data, Address) when is_binary(Data) ->
|
||||
%% a string
|
||||
Words = gmb_memory:binary_to_words(Data),
|
||||
{Address,<<(size(Data)):256, << <<W:256>> || W <- Words>>/binary>>};
|
||||
to_binary1({contract_bytearray, FateCode}, Address) when is_binary(FateCode) ->
|
||||
Words = gmb_memory:binary_to_words(FateCode),
|
||||
{Address,<<(size(FateCode)):256, << <<W:256>> || W <- Words>>/binary>>};
|
||||
to_binary1(none, Address) -> to_binary1({variant, 0, []}, Address);
|
||||
to_binary1({some, Value}, Address) -> to_binary1({variant, 1, [Value]}, Address);
|
||||
to_binary1(word, Address) -> to_binary1({?TYPEREP_WORD_TAG}, Address);
|
||||
to_binary1(string, Address) -> to_binary1({?TYPEREP_STRING_TAG}, Address);
|
||||
to_binary1(typerep, Address) -> to_binary1({?TYPEREP_TYPEREP_TAG}, Address);
|
||||
to_binary1(contract_bytearray, Address) -> to_binary1({?TYPEREP_CONTRACT_BYTEARRAY_TAG}, Address);
|
||||
to_binary1(function, Address) -> to_binary1({?TYPEREP_FUN_TAG}, Address);
|
||||
to_binary1({list, T}, Address) -> to_binary1({?TYPEREP_LIST_TAG, T}, Address);
|
||||
to_binary1({option, T}, Address) -> to_binary1({variant, [[], [T]]}, Address);
|
||||
to_binary1({tuple, Ts}, Address) -> to_binary1({?TYPEREP_TUPLE_TAG, Ts}, Address);
|
||||
to_binary1({variant, Cons}, Address) -> to_binary1({?TYPEREP_VARIANT_TAG, Cons}, Address);
|
||||
to_binary1({map, K, V}, Address) -> to_binary1({?TYPEREP_MAP_TAG, K, V}, Address);
|
||||
to_binary1({variant, Tag, Args}, Address) ->
|
||||
to_binary1(list_to_tuple([Tag | Args]), Address);
|
||||
to_binary1(Map, Address) when is_map(Map) ->
|
||||
Size = maps:size(Map),
|
||||
%% Sort according to binary ordering
|
||||
KVs = lists:sort([ {to_binary(K), to_binary(V)} || {K, V} <- maps:to_list(Map) ]),
|
||||
{Address, <<Size:256, << <<(byte_size(K)):256, K/binary,
|
||||
(byte_size(V)):256, V/binary>> || {K, V} <- KVs >>/binary >>};
|
||||
to_binary1({}, _Address) ->
|
||||
{0, <<>>};
|
||||
to_binary1(Data, Address) when is_tuple(Data) ->
|
||||
{Elems,Memory} = to_binaries(tuple_to_list(Data),Address+32*size(Data)),
|
||||
ElemsBin = << <<W:256>> || W <- Elems>>,
|
||||
{Address,<< ElemsBin/binary, Memory/binary >>};
|
||||
to_binary1([],_Address) ->
|
||||
<<Nil:256>> = <<(-1):256>>,
|
||||
{Nil,<<>>};
|
||||
to_binary1([H|T],Address) ->
|
||||
to_binary1({H,T},Address).
|
||||
|
||||
|
||||
to_binaries([],_Address) ->
|
||||
{[],<<>>};
|
||||
to_binaries([H|T],Address) ->
|
||||
{HRep,HMem} = to_binary1(H,Address),
|
||||
{TRep,TMem} = to_binaries(T,Address+size(HMem)),
|
||||
{[HRep|TRep],<<HMem/binary, TMem/binary>>}.
|
||||
|
||||
%% Interpret a return value (a binary) using a type rep.
|
||||
|
||||
-spec from_heap(Type :: ?Type(), Heap :: binary(), Ptr :: integer()) ->
|
||||
{ok, term()} | {error, term()}.
|
||||
from_heap(Type, Heap, Ptr) ->
|
||||
try {ok, from_binary(#{}, Type, Heap, Ptr)}
|
||||
catch _:Err ->
|
||||
%% io:format("** Error: from_heap failed with ~p\n ~p\n", [Err, erlang:get_stacktrace()]),
|
||||
{error, Err}
|
||||
end.
|
||||
|
||||
%% Base address is the address of the first word of the given heap.
|
||||
-spec from_binary(T :: ?Type(),
|
||||
Heap :: binary(),
|
||||
BaseAddr :: non_neg_integer()) ->
|
||||
{ok, term()} | {error, term()}.
|
||||
from_binary(T, Heap = <<V:256, _/binary>>, BaseAddr) ->
|
||||
from_heap(T, <<0:BaseAddr/unit:8, Heap/binary>>, V);
|
||||
from_binary(_, Bin, _BaseAddr) ->
|
||||
{error, {binary_too_short, Bin}}.
|
||||
|
||||
-spec from_binary(?Type(), binary()) -> {ok, term()} | {error, term()}.
|
||||
from_binary(T, Heap) ->
|
||||
from_binary(T, Heap, 0).
|
||||
|
||||
from_binary(_, word, _, V) ->
|
||||
V;
|
||||
from_binary(_, signed_word, _, V) ->
|
||||
<<N:256/signed>> = <<V:256>>,
|
||||
N;
|
||||
from_binary(_, bool, _, V) ->
|
||||
case V of
|
||||
0 -> false;
|
||||
1 -> true
|
||||
end;
|
||||
from_binary(_, string, Heap, V) ->
|
||||
StringSize = heap_word(Heap,V),
|
||||
BitAddr = 8*(V+32),
|
||||
<<_:BitAddr,Bytes:StringSize/binary,_/binary>> = Heap,
|
||||
Bytes;
|
||||
from_binary(_, {tuple, []}, _, _) ->
|
||||
{};
|
||||
from_binary(Visited, {tuple,Cpts}, Heap, V) ->
|
||||
check_circular_refs(Visited, V),
|
||||
NewVisited = Visited#{V => true},
|
||||
ElementNums = lists:seq(0, length(Cpts)-1),
|
||||
TypesAndPointers = lists:zip(Cpts, ElementNums),
|
||||
ElementAddress = fun(Index) -> V + 32 * Index end,
|
||||
Element = fun(Index) ->
|
||||
heap_word(Heap, ElementAddress(Index))
|
||||
end,
|
||||
Convert = fun(Type, Index) ->
|
||||
from_binary(NewVisited, Type, Heap, Element(Index))
|
||||
end,
|
||||
Elements = [Convert(T, I) || {T,I} <- TypesAndPointers],
|
||||
list_to_tuple(Elements);
|
||||
from_binary(Visited, {list, Elem}, Heap, V) ->
|
||||
<<Nil:256>> = <<(-1):256>>,
|
||||
if V==Nil ->
|
||||
[];
|
||||
true ->
|
||||
{H,T} = from_binary(Visited, {tuple,[Elem,{list,Elem}]},Heap,V),
|
||||
[H|T]
|
||||
end;
|
||||
from_binary(Visited, {option, A}, Heap, V) ->
|
||||
from_binary(Visited, {variant_t, [{none, []}, {some, [A]}]}, Heap, V);
|
||||
from_binary(Visited, {variant, Cons}, Heap, V) ->
|
||||
Tag = heap_word(Heap, V),
|
||||
Args = lists:nth(Tag + 1, Cons),
|
||||
Visited1 = Visited#{V => true},
|
||||
{variant, Tag, tuple_to_list(from_binary(Visited1, {tuple, Args}, Heap, V + 32))};
|
||||
from_binary(Visited, {variant_t, TCons}, Heap, V) -> %% Tagged variants
|
||||
{Tags, Cons} = lists:unzip(TCons),
|
||||
{variant, I, Args} = from_binary(Visited, {variant, Cons}, Heap, V),
|
||||
Tag = lists:nth(I + 1, Tags),
|
||||
case Args of
|
||||
[] -> Tag;
|
||||
_ -> list_to_tuple([Tag | Args])
|
||||
end;
|
||||
from_binary(_Visited, {map, A, B}, Heap, Ptr) ->
|
||||
%% FORMAT: [Size] [KeySize] Key [ValSize] Val .. [KeySize] Key [ValSize] Val
|
||||
Size = heap_word(Heap, Ptr),
|
||||
map_binary_to_value(A, B, Size, Heap, Ptr + 32);
|
||||
from_binary(Visited, typerep, Heap, V) ->
|
||||
check_circular_refs(Visited, V),
|
||||
Tag = heap_word(Heap, V),
|
||||
Arg1 = fun(T, I) -> from_binary(Visited#{V => true}, T, Heap, heap_word(Heap, V + 32 * I)) end,
|
||||
Arg = fun(T) -> Arg1(T, 1) end,
|
||||
case Tag of
|
||||
?TYPEREP_WORD_TAG -> word;
|
||||
?TYPEREP_STRING_TAG -> string;
|
||||
?TYPEREP_TYPEREP_TAG -> typerep;
|
||||
?TYPEREP_LIST_TAG -> {list, Arg(typerep)};
|
||||
?TYPEREP_TUPLE_TAG -> {tuple, Arg({list, typerep})};
|
||||
?TYPEREP_VARIANT_TAG -> {variant, Arg({list, {list, typerep}})};
|
||||
?TYPEREP_MAP_TAG -> {map, Arg(typerep), Arg1(typerep, 2)};
|
||||
?TYPEREP_FUN_TAG -> function;
|
||||
?TYPEREP_CONTRACT_BYTEARRAY_TAG -> contract_bytearray
|
||||
end;
|
||||
from_binary(_, contract_bytearray, Heap, V) ->
|
||||
FateCodeSize = heap_word(Heap, V),
|
||||
BitAddr = 8*(V+32),
|
||||
<<_:BitAddr,Bytes:FateCodeSize/binary,_/binary>> = Heap,
|
||||
{contract_bytearray, Bytes}.
|
||||
|
||||
map_binary_to_value(KeyType, ValType, N, Bin, Ptr) ->
|
||||
%% Avoid looping on bogus sizes
|
||||
MaxN = byte_size(Bin) div 64,
|
||||
Heap = heap_fragment(Bin),
|
||||
map_from_binary({value, KeyType, ValType}, min(N, MaxN), Heap, Ptr, #{}).
|
||||
|
||||
map_from_binary(_, 0, _, _, Map) -> Map;
|
||||
map_from_binary({value, KeyType, ValType} = Output, I, Heap, Ptr, Map) ->
|
||||
KeySize = get_word(Heap, Ptr),
|
||||
KeyPtr = Ptr + 32,
|
||||
KeyBin = get_chunk(Heap, KeyPtr, KeySize),
|
||||
ValSize = get_word(Heap, KeyPtr + KeySize),
|
||||
ValPtr = KeyPtr + KeySize + 32,
|
||||
ValBin = get_chunk(Heap, ValPtr, ValSize),
|
||||
%% Keys and values are self contained binaries
|
||||
{ok, Key} = from_binary(KeyType, KeyBin),
|
||||
{ok, Val} = from_binary(ValType, ValBin),
|
||||
map_from_binary(Output, I - 1, Heap, ValPtr + ValSize, Map#{Key => Val}).
|
||||
|
||||
check_circular_refs(Visited, V) ->
|
||||
case maps:is_key(V, Visited) of
|
||||
true -> exit(circular_references);
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
heap_word(Heap, Addr) when is_binary(Heap) ->
|
||||
BitSize = 8*Addr,
|
||||
<<_:BitSize,W:256,_/binary>> = Heap,
|
||||
W;
|
||||
heap_word(Heap, Addr) when is_map(Heap) ->
|
||||
0 = Addr rem 32, %% Check that it's word aligned.
|
||||
maps:get(Addr, Heap, 0).
|
||||
|
||||
get_word(#heap{offset = Offs, heap = Mem}, Addr) when Addr >= Offs ->
|
||||
get_word(Mem, Addr - Offs);
|
||||
get_word(Mem, Addr) when is_binary(Mem) ->
|
||||
<<_:Addr/unit:8, Word:256, _/binary>> = Mem,
|
||||
Word.
|
||||
|
||||
get_chunk(#heap{offset = Offs, heap = Mem}, Addr, Bytes) when Addr >= Offs ->
|
||||
get_chunk(Mem, Addr - Offs, Bytes);
|
||||
get_chunk(Mem, Addr, Bytes) when is_binary(Mem) ->
|
||||
<<_:Addr/unit:8, Chunk:Bytes/binary, _/binary>> = Mem,
|
||||
Chunk.
|
||||
|
||||
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Memory speifics that compiler and VM need to agree upon
|
||||
%%% @end
|
||||
%%% Updated : 22 Jan 2025
|
||||
%%% Created : 19 Dec 2018
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(gmb_memory).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-export([binary_to_words/1]).
|
||||
|
||||
binary_to_words(<<>>) ->
|
||||
[];
|
||||
binary_to_words(<<N:256,Bin/binary>>) ->
|
||||
[N|binary_to_words(Bin)];
|
||||
binary_to_words(Bin) ->
|
||||
binary_to_words(<<Bin/binary,0>>).
|
||||
|
@ -1,13 +1,12 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc Basic tests for Fate data
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(gmb_data_test).
|
||||
-module(aeb_data_test).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
format_integer_test() ->
|
||||
"0" = gmb_fate_data:format(0).
|
||||
"0" = aeb_fate_data:format(0).
|
@ -1,20 +1,19 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc Basic tests for Fate serialization
|
||||
%%%
|
||||
%%% To run:
|
||||
%%% TEST=gmb_fate_asm_test rebar3 eunit
|
||||
%%% TEST=aeb_fate_asm_test rebar3 eunit
|
||||
%%%
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(gmb_fate_asm_test).
|
||||
-module(aeb_fate_asm_test).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
asm_path() ->
|
||||
filename:join(code:lib_dir(gmbytecode, test), "asm_code").
|
||||
filename:join(code:lib_dir(aebytecode, test), "asm_code").
|
||||
|
||||
|
||||
file_path(File) ->
|
||||
@ -22,11 +21,16 @@ file_path(File) ->
|
||||
|
||||
read_file(File) ->
|
||||
FilePath = file_path(File),
|
||||
Asm = gmb_fate_asm:read_file(FilePath),
|
||||
Asm = aeb_fate_asm:read_file(FilePath),
|
||||
Asm.
|
||||
|
||||
assemble(Asm) ->
|
||||
gmb_fate_asm:asm_to_bytecode(Asm, []).
|
||||
{Env, BC} = aeb_fate_asm:asm_to_bytecode(Asm, []),
|
||||
{Env, BC}.
|
||||
|
||||
disassemble(BC) ->
|
||||
aeb_fate_asm:bytecode_to_fate_code(BC, []).
|
||||
|
||||
|
||||
asm_disasm_idenity_test() ->
|
||||
check_roundtrip(identity).
|
||||
@ -48,19 +52,18 @@ sources() ->
|
||||
, "tuple"
|
||||
, "mapofmap"
|
||||
, "immediates"
|
||||
, "names"
|
||||
, "meta"
|
||||
, "all_instructions"
|
||||
].
|
||||
|
||||
check_roundtrip(File) ->
|
||||
AssemblerCode = read_file(File),
|
||||
{_Env, ByteCode} = assemble(AssemblerCode),
|
||||
FateCode = gmb_fate_code:deserialize(ByteCode),
|
||||
DissasmCode = gmb_fate_asm:to_asm(FateCode),
|
||||
FateCode = disassemble(ByteCode),
|
||||
DissasmCode = aeb_fate_asm:to_asm(FateCode),
|
||||
io:format("~s~n", [AssemblerCode]),
|
||||
io:format("~s~n", [DissasmCode]),
|
||||
{_Env2, ByteCode2} = assemble(DissasmCode),
|
||||
ByteCode3 = gmb_fate_code:serialize(FateCode),
|
||||
Code1 = gmb_fate_asm:strip(ByteCode),
|
||||
Code2 = gmb_fate_asm:strip(ByteCode2),
|
||||
Code3 = gmb_fate_asm:strip(ByteCode3),
|
||||
?assertEqual(Code1, Code2),
|
||||
?assertEqual(Code1, Code3).
|
||||
Code1 = aeb_fate_asm:strip(ByteCode),
|
||||
Code2 = aeb_fate_asm:strip(ByteCode2),
|
||||
io:format("~s~n", [aeb_fate_asm:to_asm(disassemble(ByteCode2))]),
|
||||
?assertEqual(Code1, Code2).
|
83
test/aeb_serialize_test.erl
Normal file
83
test/aeb_serialize_test.erl
Normal file
@ -0,0 +1,83 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc Basic tests for Fate serialization
|
||||
%%%
|
||||
%%% To run:
|
||||
%%% TEST=aeb_serialize_test rebar3 eunit
|
||||
%%%
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeb_serialize_test).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
serialize_integer_test() ->
|
||||
<<0>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(0)),
|
||||
<<2>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(1)),
|
||||
<<126>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(63)),
|
||||
<<111, 0>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(64)),
|
||||
<<111,130,255,255>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(65535 + 64)),
|
||||
<<111,184,129,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,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,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,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,0,0,0,0>> =
|
||||
aeb_fate_encoding:serialize(aeb_fate_data:make_integer(1 bsl 1024 + 64)).
|
||||
|
||||
serialize_deserialize_test_() ->
|
||||
[{lists:flatten(io_lib:format("~p", [X])),
|
||||
fun() ->
|
||||
?assertEqual(X,
|
||||
aeb_fate_encoding:deserialize(aeb_fate_encoding:serialize(X)))
|
||||
end}
|
||||
|| X <- sources()].
|
||||
|
||||
make_int_list(N) -> [aeb_fate_data:make_integer(I) || I <- lists:seq(1, N)].
|
||||
|
||||
sources() ->
|
||||
FortyTwo = aeb_fate_data:make_integer(42),
|
||||
Unit = aeb_fate_data:make_unit(),
|
||||
True = aeb_fate_data:make_boolean(true),
|
||||
False = aeb_fate_data:make_boolean(false),
|
||||
Nil = aeb_fate_data:make_list([]),
|
||||
EmptyString = aeb_fate_data:make_string(""),
|
||||
EmptyMap = aeb_fate_data:make_map(#{}),
|
||||
[aeb_fate_data:make_integer(0),
|
||||
aeb_fate_data:make_integer(1),
|
||||
True, False, Unit, Nil, EmptyString, EmptyMap,
|
||||
aeb_fate_data:make_list([True]),
|
||||
aeb_fate_data:make_address(
|
||||
<<0,1,2,3,4,5,6,7,8,9,
|
||||
0,1,2,3,4,5,6,7,8,9,
|
||||
0,1,2,3,4,5,6,7,8,9,
|
||||
1,2>>),
|
||||
aeb_fate_data:make_string(<<"Hello">>),
|
||||
aeb_fate_data:make_string(
|
||||
<<"0123456789012345678901234567890123456789"
|
||||
"0123456789012345678901234567890123456789"
|
||||
"0123456789012345678901234567890123456789"
|
||||
"0123456789012345678901234567890123456789">>), %% Magic concat 80 char string.
|
||||
aeb_fate_data:make_tuple({True, FortyTwo}),
|
||||
aeb_fate_data:make_tuple(list_to_tuple(make_int_list(65))),
|
||||
aeb_fate_data:make_tuple(list_to_tuple(make_int_list(16))),
|
||||
aeb_fate_data:make_map(#{ aeb_fate_data:make_integer(1) => True, aeb_fate_data:make_integer(2) => False}),
|
||||
aeb_fate_data:make_map(#{ aeb_fate_data:make_string(<<"foo">>) => aeb_fate_data:make_tuple({FortyTwo, True})}),
|
||||
aeb_fate_data:make_list(make_int_list(3)),
|
||||
aeb_fate_data:make_integer(-65),
|
||||
aeb_fate_data:make_integer(65),
|
||||
aeb_fate_data:make_integer(-32432847932847928374983),
|
||||
aeb_fate_data:make_bits(0),
|
||||
aeb_fate_data:make_bits(1),
|
||||
aeb_fate_data:make_bits(-1),
|
||||
aeb_fate_data:make_list(make_int_list(65)),
|
||||
aeb_fate_data:make_variant(2, 0, {FortyTwo}),
|
||||
aeb_fate_data:make_variant(2, 1, {}),
|
||||
aeb_fate_data:make_list([aeb_fate_data:make_variant(3, 0, {})]),
|
||||
aeb_fate_data:make_variant(255, 254, {}),
|
||||
aeb_fate_data:make_variant(5, 3, {aeb_fate_data:make_boolean(true),
|
||||
aeb_fate_data:make_list(make_int_list(3)),
|
||||
aeb_fate_data:make_string(<<"foo">>)})
|
||||
|
||||
].
|
@ -1,4 +1,4 @@
|
||||
-module(gmbytecode_SUITE).
|
||||
-module(aebytecode_SUITE).
|
||||
|
||||
%% common_test exports
|
||||
-export([ all/0 ]).
|
||||
@ -12,8 +12,8 @@ all() ->
|
||||
[ roundtrip_identy ].
|
||||
|
||||
roundtrip_identy(_Cfg) ->
|
||||
CodeDir = code:lib_dir(gmbytecode, test),
|
||||
FileName = filename:join(CodeDir, "asm_code/identity.gmsm"),
|
||||
Code = gmb_asm:file(FileName, []),
|
||||
ct:log("Code ~p:~n~s~n", [FileName, gmb_disassemble:format(Code, fun io:format/2)]),
|
||||
CodeDir = code:lib_dir(aebytecode, test),
|
||||
FileName = filename:join(CodeDir, "asm_code/identity.aesm"),
|
||||
Code = aeb_asm:file(FileName, []),
|
||||
ct:log("Code ~p:~n~s~n", [FileName, aeb_disassemble:format(Code, fun io:format/2)]),
|
||||
ok.
|
233
test/asm_code/all_instructions.fate
Normal file
233
test/asm_code/all_instructions.fate
Normal file
@ -0,0 +1,233 @@
|
||||
;; CONTRACT all_instructions
|
||||
|
||||
;; Dont expect this contract to typecheck or run.
|
||||
;; Just used to check assembler rountrip of all instruction.
|
||||
|
||||
FUNCTION foo () : {tuple, []}
|
||||
RETURN
|
||||
|
||||
RETURNR a13
|
||||
|
||||
CALL foo
|
||||
|
||||
CALL_R arg125 foo
|
||||
|
||||
CALL_T foo
|
||||
|
||||
CALL_TR arg245 foo
|
||||
|
||||
JUMP 5514251025295783441695716053282666408426
|
||||
|
||||
JUMPIF arg196 0x12c651665
|
||||
|
||||
SWITCH_V2 a27 63 33
|
||||
|
||||
SWITCH_V3 var4 0x1d61723dd 79 7
|
||||
|
||||
SWITCH_VN #nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv [1, 2, 3]
|
||||
|
||||
PUSH var80
|
||||
|
||||
DUPA
|
||||
|
||||
DUP a
|
||||
|
||||
POP a107
|
||||
|
||||
STORE arg183 var225
|
||||
|
||||
INCA
|
||||
|
||||
INC a25
|
||||
|
||||
DECA
|
||||
|
||||
DEC a
|
||||
|
||||
ADD a217 a a
|
||||
|
||||
SUB arg35 arg165 var74
|
||||
|
||||
MUL 44 35 "foo"
|
||||
|
||||
DIV 263838340369912686645632650718169038811 a24 a
|
||||
|
||||
MOD var113 arg80 arg207
|
||||
|
||||
POW a176 a a123
|
||||
|
||||
LT a 78 var81
|
||||
|
||||
GT arg19 4729414120208894485838100532547810615352 var175
|
||||
|
||||
EQ 85 a arg164
|
||||
|
||||
ELT a161 arg226 a168
|
||||
|
||||
EGT a131 1 var250
|
||||
|
||||
NEQ a85 a a83
|
||||
|
||||
AND var255 0x294a24f6 var189
|
||||
|
||||
OR (| 2 | 0 | ( (), (42) ) |) arg168 var107
|
||||
|
||||
NOT arg124 a
|
||||
|
||||
TUPLE 5019186157739257888756115213149493826410
|
||||
|
||||
ELEMENT integer arg148 var25 a219
|
||||
|
||||
MAP_EMPTY a135
|
||||
|
||||
MAP_LOOKUP a82 a a143
|
||||
|
||||
MAP_LOOKUPD var112 arg35 a163 var112
|
||||
|
||||
MAP_UPDATE false a0 a56 a
|
||||
|
||||
MAP_DELETE arg180 a var1
|
||||
|
||||
MAP_MEMBER a { true => 4} 94
|
||||
|
||||
MAP_FROM_LIST () a159
|
||||
|
||||
NIL arg91
|
||||
|
||||
IS_NIL a121 var6
|
||||
|
||||
CONS arg185 "foo" a114
|
||||
|
||||
HD a150 var124
|
||||
|
||||
TL arg223 a
|
||||
|
||||
LENGTH var216 a143
|
||||
|
||||
STR_EQ { 203961992615221001243597889146034217896 => 0x1f53a1843} 281217554184165828643225535776787296845 a177
|
||||
|
||||
STR_JOIN a a 7144184027126178769820155907121270843348
|
||||
|
||||
INT_TO_STR var238 a
|
||||
|
||||
ADDR_TO_STR a arg216
|
||||
|
||||
STR_REVERSE a174 #nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
|
||||
|
||||
INT_TO_ADDR arg127 var207
|
||||
|
||||
VARIANT a a 0x1f7b72200 a
|
||||
|
||||
VARIANT_TEST a26 arg217 a
|
||||
|
||||
VARIANT_ELEMENT a86 arg103 arg108
|
||||
|
||||
BITS_NONEA
|
||||
|
||||
BITS_NONE a
|
||||
|
||||
BITS_ALLA
|
||||
|
||||
BITS_ALL a164
|
||||
|
||||
BITS_ALL_N a221 arg135
|
||||
|
||||
BITS_SET arg150 a48 { 0x1a715e2a6 => 3}
|
||||
|
||||
BITS_CLEAR arg98 a arg164
|
||||
|
||||
BITS_TEST a a242 (| 5 | 2 | (1, "foo", ()) |)
|
||||
|
||||
BITS_SUM a244 a71
|
||||
|
||||
BITS_OR var20 var186 a
|
||||
|
||||
BITS_AND a187 4 arg203
|
||||
|
||||
BITS_DIFF var200 arg247 var20
|
||||
|
||||
ADDRESS a237
|
||||
|
||||
BALANCE a231
|
||||
|
||||
ORIGIN arg216
|
||||
|
||||
CALLER a27
|
||||
|
||||
GASPRICE arg119
|
||||
|
||||
BLOCKHASH arg110
|
||||
|
||||
BENEFICIARY var163
|
||||
|
||||
TIMESTAMP a
|
||||
|
||||
GENERATION 242795038229506961431398379342231049652
|
||||
|
||||
MICROBLOCK arg43
|
||||
|
||||
DIFFICULTY var24
|
||||
|
||||
GASLIMIT arg220
|
||||
|
||||
GAS var35
|
||||
|
||||
LOG0 a a85
|
||||
|
||||
LOG1 arg94 arg86 arg208
|
||||
|
||||
LOG2 a113 (| 5 | 2 | (1, "foo", ()) |) arg238 var108
|
||||
|
||||
LOG3 arg255 arg15 arg211 var139 arg44
|
||||
|
||||
LOG4 #nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv a247 a 9 a38 a
|
||||
|
||||
DEACTIVATE
|
||||
|
||||
SPEND #nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv var136
|
||||
|
||||
ORACLE_REGISTER arg29 48 ((| 5 | 2 | (1, "foo", ()) |)) arg65 { <> => false} <>
|
||||
|
||||
ORACLE_QUERY
|
||||
|
||||
ORACLE_RESPOND
|
||||
|
||||
ORACLE_EXTEND
|
||||
|
||||
ORACLE_GET_ANSWER
|
||||
|
||||
ORACLE_GET_QUESTION
|
||||
|
||||
ORACLE_QUERY_FEE
|
||||
|
||||
AENS_RESOLVE
|
||||
|
||||
AENS_PRECLAIM
|
||||
|
||||
AENS_CLAIM
|
||||
|
||||
AENS_UPDATE
|
||||
|
||||
AENS_TRANSFER
|
||||
|
||||
AENS_REVOKE
|
||||
|
||||
ECVERIFY
|
||||
|
||||
SHA3
|
||||
|
||||
SHA256
|
||||
|
||||
BLAKE2B
|
||||
|
||||
DUMMY7ARG a a 7607708484837907159893701471377343595877 (| 2 | 0 | ( [], [ 45, { 1 => 3441201581501946066216994494994943246334} ] ) |) a0 var56 "foo"
|
||||
|
||||
DUMMY8ARG 3673679924816289365509492271980889822579 a69 arg242 var237 a175 arg106 () var255
|
||||
|
||||
ABORT a
|
||||
|
||||
EXIT var120
|
||||
|
||||
NOP
|
||||
|
||||
RETURNR ()
|
@ -62,13 +62,13 @@ id_local: JUMPDEST
|
||||
JUMP
|
||||
|
||||
;; Test the code from the shell
|
||||
;; aevm_eeevm:eval(aevm_eeevm_state:init(#{ exec => #{ code => list_to_binary(gmb_asm:file("apps/gmsophia/test/contracts/identity.aesm", [])), address => 0, caller => 0, data => <<0:256, 42:256>>, gas => 1000000, gasPrice => 1, origin => 0, value => 0 }, env => #{currentCoinbase => 0, currentDifficulty => 0, currentGasLimit => 10000, currentNumber => 0, currentTimestamp => 0}, pre => #{}}, #{})).
|
||||
;; aevm_eeevm:eval(aevm_eeevm_state:init(#{ exec => #{ code => list_to_binary(aeb_asm:file("apps/aesophia/test/contracts/identity.aesm", [])), address => 0, caller => 0, data => <<0:256, 42:256>>, gas => 1000000, gasPrice => 1, origin => 0, value => 0 }, env => #{currentCoinbase => 0, currentDifficulty => 0, currentGasLimit => 10000, currentNumber => 0, currentTimestamp => 0}, pre => #{}}, #{})).
|
||||
|
||||
;; Test the code from the shell with tracing.
|
||||
;; aevm_eeevm:eval(aevm_eeevm_state:init(#{ exec => #{ code => gmb_asm:file("apps/gmsophia/test/contracts/identity.aesm", []), address => 0, caller => 0, data => <<0:256, 42:256>>, gas => 1000000, gasPrice => 1, origin => 0, value => 0 }, env => #{currentCoinbase => 0, currentDifficulty => 0, currentGasLimit => 10000, currentNumber => 0, currentTimestamp => 0}, pre => #{}}, #{ trace => true})).
|
||||
;; aevm_eeevm:eval(aevm_eeevm_state:init(#{ exec => #{ code => aeb_asm:file("apps/aesophia/test/contracts/identity.aesm", []), address => 0, caller => 0, data => <<0:256, 42:256>>, gas => 1000000, gasPrice => 1, origin => 0, value => 0 }, env => #{currentCoinbase => 0, currentDifficulty => 0, currentGasLimit => 10000, currentNumber => 0, currentTimestamp => 0}, pre => #{}}, #{ trace => true})).
|
||||
|
||||
|
||||
;; Test the code from the shell with tracing.
|
||||
;; aevm_eeevm:eval(aevm_eeevm_state:init(#{ exec => #{ code => gmb_asm:file("apps/gmsophia/test/contracts/identity.aesm", [pp_tokens, pp_opcodes, pp_patched_code, pp_hex_string]), address => 0, caller => 0, data => <<0:256, 42:256>>, gas => 1000000, gasPrice => 1, origin => 0, value => 0}, env => #{currentCoinbase => 0, currentDifficulty => 0, currentGasLimit => 10000, currentNumber => 0, currentTimestamp => 0}, pre => #{}}, #{ trace => true})).
|
||||
;; aevm_eeevm:eval(aevm_eeevm_state:init(#{ exec => #{ code => aeb_asm:file("apps/aesophia/test/contracts/identity.aesm", [pp_tokens, pp_opcodes, pp_patched_code, pp_hex_string]), address => 0, caller => 0, data => <<0:256, 42:256>>, gas => 1000000, gasPrice => 1, origin => 0, value => 0}, env => #{currentCoinbase => 0, currentDifficulty => 0, currentGasLimit => 10000, currentNumber => 0, currentTimestamp => 0}, pre => #{}}, #{ trace => true})).
|
||||
|
||||
;; aec_conductor:stop_mining().
|
||||
|
@ -5,4 +5,4 @@ FUNCTION id(integer) -> integer
|
||||
;; Test the code from the shell
|
||||
;; _build/default/rel/aessembler/bin/aessembler console
|
||||
|
||||
;; gmb_gmfa:file("../../../../test/asm_code/identity.fate", []).
|
||||
;; aeb_aefa:file("../../../../test/asm_code/identity.fate", []).
|
||||
|
@ -66,18 +66,12 @@ FUNCTION tuple() : {tuple, [integer, boolean, string, {tuple, [integer, integer]
|
||||
|
||||
|
||||
FUNCTION address() : address
|
||||
RETURNR @ak_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
|
||||
|
||||
FUNCTION contract() : contract
|
||||
RETURNR @ct_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
|
||||
|
||||
FUNCTION channel() : channel
|
||||
RETURNR @ch_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
|
||||
RETURNR #deadbeef
|
||||
|
||||
;; Option(integer) = NONE | SOME(integer)
|
||||
FUNCTION variant_none() : {variant, [{tuple, []}, {tuple, [integer]}]}
|
||||
RETURNR (| [0,1] | 0 | () |)
|
||||
RETURNR (| 2 | 0 | () |)
|
||||
|
||||
;; Option(integer) = NONE | SOME(integer)
|
||||
FUNCTION variant_some() : {variant, [{tuple, []}, {tuple, [integer]}]}
|
||||
RETURNR (| [0,1] | 1 | (42) |)
|
||||
RETURNR (| 2 | 1 | (42) |)
|
||||
|
@ -2,7 +2,7 @@
|
||||
FUNCTION call(integer):integer
|
||||
STORE var1 arg0
|
||||
PUSH 0
|
||||
CALL "write"
|
||||
CALL write
|
||||
PUSH var1
|
||||
RETURN
|
||||
|
||||
|
@ -1,12 +0,0 @@
|
||||
;; CONTRACT meta
|
||||
|
||||
FUNCTION meta() : boolean
|
||||
CREATE @cb_+PJGA6A4Fz4T2LHV5knITCldR3rqO7HrXO2zhOAR9JWNbhf8Q8C4xbhx/gx8JckANwAXfQBVACAAAP4vhlvZADcABwECgv5E1kQfADcBBzcACwAWMBReAHMAFjBvJFMAFjBvggOoFAAUABQSggABAz/+tIwWhAA3AAdTAAD+1jB5kAQ3AAcLAAD+6MRetgA3AQc3ABoGggABAz+4TS8GEQx8JclFY2FsbGVyX2lzX2NyZWF0b3IRL4Zb2Q1nZXQRRNZEHxFpbml0EbSMFoQdYmFsYW5jZRHWMHmQFXZhbHVlEejEXrYNc2V0gi8AhTQuMy4wAUqQ8s4= a 2137
|
||||
|
||||
CLONE a arg0 2137 false
|
||||
CLONE_G a arg0 2137 10000 false
|
||||
|
||||
BYTECODE_HASH a a
|
||||
BYTECODE_HASH a a
|
||||
EQ a a a
|
||||
RETURNR a
|
@ -1,21 +0,0 @@
|
||||
;; CONTRACT names
|
||||
|
||||
FUNCTION preclaim(address, {bytes, 32}) : {tuple, []}
|
||||
AENS_PRECLAIM #AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== arg0 arg1
|
||||
RETURNR {}
|
||||
|
||||
FUNCTION claim(address, string, integer, integer) : {tuple, []}
|
||||
AENS_CLAIM #AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== arg0 arg1 arg2 arg3
|
||||
RETURNR {}
|
||||
|
||||
FUNCTION transfer(address, address, {bytes, 32}) : {tuple, []}
|
||||
AENS_TRANSFER #AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== arg0 arg1 arg2
|
||||
RETURNR {}
|
||||
|
||||
FUNCTION revoke(address, {bytes, 32}) : {tuple, []}
|
||||
AENS_REVOKE #AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== arg0 arg1
|
||||
RETURNR {}
|
||||
|
||||
FUNCTION resolve(string, string) : {variant, [{tuple, []}, {tuple, [address]}]}
|
||||
AENS_RESOLVE a arg0 arg1 'address
|
||||
RETURN
|
@ -19,23 +19,27 @@ FUNCTION inc(integer) -> integer
|
||||
|
||||
FUNCTION call(integer) -> integer
|
||||
INCA
|
||||
CALL "inc"
|
||||
CALL inc
|
||||
INCA
|
||||
RETURN
|
||||
|
||||
|
||||
FUNCTION tailcall(integer) -> integer
|
||||
INCA
|
||||
CALL_T "inc"
|
||||
CALL_T inc
|
||||
|
||||
;; FUNCTION remote_call(integer) : integer
|
||||
;; PUSH arg0
|
||||
;; CALL_R remote.add_five {tuple, [integer]} integer 0 ;; typereps don't parse
|
||||
;; INCA
|
||||
;; RETURN
|
||||
FUNCTION remote_call(integer) : integer
|
||||
PUSH arg0
|
||||
CALL_R remote.add_five
|
||||
INCA
|
||||
RETURN
|
||||
|
||||
FUNCTION remote_tailcall(integer) : integer
|
||||
PUSH arg0
|
||||
CALL_TR remote add_five
|
||||
|
||||
;; Test the code from the shell
|
||||
;; _build/default/rel/aessembler/bin/aessembler console
|
||||
|
||||
;; gmb_gmfa:file("../../../../test/asm_code/test.fate", []).
|
||||
;; f(Asm), f(Env), f(BC), Asm = gmfa_asm:read_file("../../../../test/asm_code/test.fate"), {Env, BC} = gmfa_asm:asm_to_bytecode(Asm, []), gmfa_asm:bytecode_to_fate_code(BC, []).
|
||||
;; aeb_aefa:file("../../../../test/asm_code/test.fate", []).
|
||||
;; f(Asm), f(Env), f(BC), Asm = aefa_asm:read_file("../../../../test/asm_code/test.fate"), {Env, BC} = aefa_asm:asm_to_bytecode(Asm, []), aefa_asm:bytecode_to_fate_code(BC, []).
|
@ -1,13 +1,13 @@
|
||||
FUNCTION make_0tuple():{tuple, []}
|
||||
;; BB : 0
|
||||
TUPLE a 0
|
||||
TUPLE 0
|
||||
RETURN
|
||||
|
||||
FUNCTION make_2tuple(integer, integer):{tuple, [integer, integer]}
|
||||
;; BB : 0
|
||||
PUSH arg0
|
||||
PUSH arg1
|
||||
TUPLE a 2
|
||||
TUPLE 2
|
||||
RETURN
|
||||
|
||||
FUNCTION make_5tuple(integer, integer, integer, integer, integer):
|
||||
@ -18,18 +18,18 @@ FUNCTION make_5tuple(integer, integer, integer, integer, integer):
|
||||
PUSH arg2
|
||||
PUSH arg3
|
||||
PUSH arg4
|
||||
TUPLE a 5
|
||||
TUPLE 5
|
||||
RETURN
|
||||
|
||||
FUNCTION element1(integer, integer): integer
|
||||
;; BB : 0
|
||||
PUSH arg0
|
||||
PUSH arg1
|
||||
TUPLE a 2
|
||||
ELEMENT a 1 a
|
||||
TUPLE 2
|
||||
ELEMENT integer a 1 a
|
||||
RETURN
|
||||
|
||||
FUNCTION element({tuple, [integer, integer]}, integer): integer
|
||||
;; BB : 0
|
||||
ELEMENT a arg1 arg0
|
||||
ELEMENT integer a arg1 arg0
|
||||
RETURN
|
||||
|
@ -1,98 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc Basic tests for Fate serialization
|
||||
%%%
|
||||
%%% To run:
|
||||
%%% TEST=gmb_serialize_test rebar3 eunit
|
||||
%%%
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(gmb_serialize_test).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
serialize_integer_test() ->
|
||||
<<0>> = gmb_fate_encoding:serialize(gmb_fate_data:make_integer(0)),
|
||||
<<2>> = gmb_fate_encoding:serialize(gmb_fate_data:make_integer(1)),
|
||||
<<126>> = gmb_fate_encoding:serialize(gmb_fate_data:make_integer(63)),
|
||||
<<111, 0>> = gmb_fate_encoding:serialize(gmb_fate_data:make_integer(64)),
|
||||
<<111,130,255,255>> = gmb_fate_encoding:serialize(gmb_fate_data:make_integer(65535 + 64)),
|
||||
<<111,184,129,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,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,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,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,0,0,0,0>> =
|
||||
gmb_fate_encoding:serialize(gmb_fate_data:make_integer(1 bsl 1024 + 64)).
|
||||
|
||||
serialize_deserialize_test_() ->
|
||||
[{lists:flatten(io_lib:format("~p", [X])),
|
||||
fun() ->
|
||||
?assertEqual(X,
|
||||
gmb_fate_encoding:deserialize(gmb_fate_encoding:serialize(X)))
|
||||
end}
|
||||
|| X <- sources()].
|
||||
|
||||
make_int_list(N) -> [gmb_fate_data:make_integer(I) || I <- lists:seq(1, N)].
|
||||
|
||||
sources() ->
|
||||
FortyTwo = gmb_fate_data:make_integer(42),
|
||||
Unit = gmb_fate_data:make_unit(),
|
||||
True = gmb_fate_data:make_boolean(true),
|
||||
False = gmb_fate_data:make_boolean(false),
|
||||
Nil = gmb_fate_data:make_list([]),
|
||||
EmptyString = gmb_fate_data:make_string(""),
|
||||
EmptyMap = gmb_fate_data:make_map(#{}),
|
||||
[gmb_fate_data:make_integer(0),
|
||||
gmb_fate_data:make_integer(1),
|
||||
True, False, Unit, Nil, EmptyString, EmptyMap,
|
||||
gmb_fate_data:make_hash(<<1,2,3,4,5>>),
|
||||
gmb_fate_data:make_signature(<<1,2,3,4,5>>),
|
||||
gmb_fate_data:make_contract(<<1,2,3,4,5>>),
|
||||
gmb_fate_data:make_channel(<<1,2,3,4,5>>),
|
||||
gmb_fate_data:make_list([True]),
|
||||
gmb_fate_data:make_address(
|
||||
<<0,1,2,3,4,5,6,7,8,9,
|
||||
0,1,2,3,4,5,6,7,8,9,
|
||||
0,1,2,3,4,5,6,7,8,9,
|
||||
1,2>>),
|
||||
gmb_fate_data:make_string(<<"Hello">>),
|
||||
gmb_fate_data:make_string(
|
||||
<<"0123456789012345678901234567890123456789"
|
||||
"0123456789012345678901234567890123456789"
|
||||
"0123456789012345678901234567890123456789"
|
||||
"0123456789012345678901234567890123456789">>), %% Magic concat 80 char string.
|
||||
gmb_fate_data:make_tuple({True, FortyTwo}),
|
||||
gmb_fate_data:make_tuple(list_to_tuple(make_int_list(65))),
|
||||
gmb_fate_data:make_tuple(list_to_tuple(make_int_list(16))),
|
||||
gmb_fate_data:make_map(#{ gmb_fate_data:make_integer(1) => True, gmb_fate_data:make_integer(2) => False}),
|
||||
gmb_fate_data:make_map(#{ gmb_fate_data:make_string(<<"foo">>) => gmb_fate_data:make_tuple({FortyTwo, True})}),
|
||||
gmb_fate_data:make_list(make_int_list(3)),
|
||||
gmb_fate_data:make_integer(-65),
|
||||
gmb_fate_data:make_integer(65),
|
||||
gmb_fate_data:make_integer(-32432847932847928374983),
|
||||
gmb_fate_data:make_bits(0),
|
||||
gmb_fate_data:make_bits(1),
|
||||
gmb_fate_data:make_bits(-1),
|
||||
gmb_fate_data:make_list(make_int_list(65)),
|
||||
gmb_fate_data:make_variant([1,2,3], 0, {FortyTwo}),
|
||||
gmb_fate_data:make_variant([2,0], 1, {}),
|
||||
gmb_fate_data:make_list([gmb_fate_data:make_variant([0,0,0], 0, {})]),
|
||||
gmb_fate_data:make_variant([0|| _<-lists:seq(1,255)], 254, {}),
|
||||
gmb_fate_data:make_variant([0,1,2,3,4,5],
|
||||
3, {gmb_fate_data:make_boolean(true),
|
||||
gmb_fate_data:make_list(make_int_list(3)),
|
||||
gmb_fate_data:make_string(<<"foo">>)}),
|
||||
%% contract C =
|
||||
%% type state = int
|
||||
%% entrypoint init() = 2137
|
||||
|
||||
%% cb_+FFGA6Af6sHTrctrcNGwEa8MPei7iEHIjnxcsBzlA5IK0Yn11sCllP5E1kQfADcANwAaDoJvgggZAQM/jC8BEUTWRB8RaW5pdIIvAIU0LjMuMAD7u
|
||||
gmb_fate_data:make_contract_bytearray(
|
||||
<<248,81,70,3,160,31,234,193,211,173,203,107,112,209,176,17,175,12,61,232,187,
|
||||
136,65,200,142,124,92,176,28,229,3,146,10,209,137,245,214,192,165,148,254,68,
|
||||
214,68,31,0,55,0,55,0,26,14,130,111,130,8,25,1,3,63,140,47,1,17,68,214,68,31,
|
||||
17,105,110,105,116,130,47,0,133,52,46,51,46,48,0>>)
|
||||
].
|
19
zomp.meta
19
zomp.meta
@ -1,19 +0,0 @@
|
||||
{name,"Gajumaru Bytecode"}.
|
||||
{type,lib}.
|
||||
{modules,[]}.
|
||||
{prefix,none}.
|
||||
{desc,"A library and stand alone assembler for Gajumaru bytecode. This version supports AEVM bytecode and FATE bytecode."}.
|
||||
{author,[]}.
|
||||
{package_id,{"otpr","gmbytecode",{3,4,1}}}.
|
||||
{deps,[{"otpr","gmserialization",{0,1,2}},
|
||||
{"otpr","eblake2",{1,0,0}},
|
||||
{"otpr","getopt",{1,0,2}}]}.
|
||||
{key_name,none}.
|
||||
{a_email,[]}.
|
||||
{c_email,[]}.
|
||||
{copyright,[]}.
|
||||
{file_exts,[]}.
|
||||
{license,skip}.
|
||||
{repo_url,"https://git.qpq.swiss/QPQ-AG/gmbytecode"}.
|
||||
{tags,["gajumaru","blockchain","fate","bytecode","crypto","gm"]}.
|
||||
{ws_url,[]}.
|
20
zomp_prep
20
zomp_prep
@ -1,20 +0,0 @@
|
||||
#! /bin/bash
|
||||
|
||||
# This is a small pre-packaging source generation and include correction script that should be
|
||||
# run before packaging this project for use with ZX/Zomp.
|
||||
|
||||
rm -rf _build
|
||||
rm -f src/gmb_fate_opcodes.erl src/gmb_fate_ops.erl include/gmb_fate_opcodes.hrl src/gmb_fate_asm_scan.xrl src/gmb_fate_pp.erl
|
||||
make sources
|
||||
cd src
|
||||
for f in $(ls --ignore=gmb_fate_generate_ops.erl | grep erl)
|
||||
do
|
||||
echo "Updating includes in: $f"
|
||||
sed -i 's/gmbytecode\/include\///g' "$f"
|
||||
sed -i 's/\.\.\/include\///g' "$f"
|
||||
sed -i 's/include_lib/include/g' "$f"
|
||||
done
|
||||
cd ..
|
||||
rm -f ebin/*.beam
|
||||
rm -f rebar*
|
||||
rm -rf quickcheck
|
Loading…
x
Reference in New Issue
Block a user