Compare commits
301 Commits
Author | SHA1 | Date | |
---|---|---|---|
cc4fd04019 | |||
97cea33be8 | |||
1cdcb9150b | |||
![]() |
b47b2fe23c | ||
![]() |
5e38d6e829 | ||
![]() |
009e036192 | ||
![]() |
37808419a8 | ||
![]() |
f4c3782888 | ||
![]() |
1688f85f2b | ||
![]() |
b38349274f | ||
![]() |
b8d593e351 | ||
![]() |
0f7529b26a | ||
![]() |
2a0a397afa | ||
![]() |
093bcd6204 | ||
![]() |
6601ad2d38 | ||
![]() |
578ebe2a8a | ||
![]() |
8269dbd71e | ||
![]() |
08cc0a9fcd | ||
![]() |
84f20ab683 | ||
![]() |
7497345928 | ||
![]() |
822a269f75 | ||
![]() |
0699f35b03 | ||
![]() |
3829e29a63 | ||
![]() |
52e9d30f76 | ||
![]() |
da7f00ae5d | ||
![]() |
326fca709f | ||
![]() |
e860e217a0 | ||
![]() |
bc48b5d62f | ||
![]() |
7b9c1b856b | ||
![]() |
05dfd7ffc7 | ||
![]() |
7d4f3fed10 | ||
![]() |
4c0906398e | ||
![]() |
1bb24a485b | ||
![]() |
abd12b599f | ||
![]() |
e4d46bd17a | ||
![]() |
142502179c | ||
![]() |
0318c60caf | ||
![]() |
eb8810a783 | ||
![]() |
69110b2ee8 | ||
![]() |
b0263319e3 | ||
![]() |
951db9f384 | ||
![]() |
fe11ee31ab | ||
![]() |
0c010cfd0d | ||
![]() |
d16f9a9579 | ||
![]() |
f53f61e554 | ||
![]() |
84e246d9da | ||
![]() |
1498e1cdc2 | ||
![]() |
41b80b5d44 | ||
![]() |
fd9f15789c | ||
![]() |
8d447e803b | ||
![]() |
83616392e1 | ||
![]() |
135a27c992 | ||
![]() |
b30f8a2bfd | ||
![]() |
9d81358daf | ||
![]() |
5493bece71 | ||
![]() |
c90bb0c6db | ||
![]() |
7f0d3090d4 | ||
![]() |
e5164330b0 | ||
![]() |
e5bdf583f9 | ||
![]() |
719bf26a38 | ||
![]() |
5ef5d455b4 | ||
![]() |
36559842ae | ||
![]() |
94d0b984e9 | ||
![]() |
167bfcc0e6 | ||
![]() |
2f11143e5a | ||
![]() |
ff5a4c7dd5 | ||
![]() |
370c7387a9 | ||
![]() |
8a9c9dec95 | ||
![]() |
4565188c6a | ||
![]() |
059d114a06 | ||
![]() |
e4b09d7c5c | ||
![]() |
b040dccdef | ||
![]() |
211ee74df2 | ||
![]() |
38f4f0ecd7 | ||
![]() |
281e9b210c | ||
![]() |
3e7a8e4a69 | ||
![]() |
ea5548be6c | ||
![]() |
48cfbd03b0 | ||
![]() |
04878c5ed9 | ||
![]() |
4f4d6d30cd | ||
![]() |
176df87bb0 | ||
![]() |
2239bfb6f6 | ||
![]() |
872766260b | ||
![]() |
f184abeb87 | ||
![]() |
a66dc0a97f | ||
![]() |
17c9656f5c | ||
![]() |
3106ca1306 | ||
![]() |
3565719c7a | ||
![]() |
b036531dc2 | ||
![]() |
7f0593fbf2 | ||
![]() |
a533fd5fcb | ||
![]() |
02a3462cf4 | ||
![]() |
69912db2b6 | ||
![]() |
5e16b85ae2 | ||
![]() |
d272e821b2 | ||
![]() |
72b2a581d5 | ||
![]() |
72d61471e0 | ||
![]() |
e21abb875e | ||
![]() |
e7f2be7ce8 | ||
![]() |
c6475fe1c2 | ||
![]() |
4e4c20c387 | ||
![]() |
1d5e5be252 | ||
![]() |
6efa4a0cb8 | ||
![]() |
59b7b786ac | ||
![]() |
f31887c2ed | ||
![]() |
d794566363 | ||
![]() |
850a5e2c35 | ||
![]() |
c270c794c3 | ||
![]() |
10cc127883 | ||
![]() |
50df849709 | ||
![]() |
dfa9b80a3c | ||
![]() |
befa1e3ff9 | ||
![]() |
efb4afeafa | ||
![]() |
e75336486e | ||
![]() |
fdd660a219 | ||
![]() |
3954bd22da | ||
![]() |
13211887a3 | ||
![]() |
834ab298d1 | ||
![]() |
52781060b2 | ||
![]() |
3721fde7e8 | ||
![]() |
23ee7e0ca4 | ||
![]() |
af6224cb3b | ||
![]() |
197dfd5da1 | ||
![]() |
087ec31698 | ||
![]() |
c49140fd5d | ||
![]() |
414c45fbf7 | ||
![]() |
f5a9be67d9 | ||
![]() |
15095a74ab | ||
![]() |
5aee70b8ff | ||
![]() |
44ec31d958 | ||
![]() |
8fde1e5e24 | ||
![]() |
54dcf364e5 | ||
![]() |
7c6a80fef7 | ||
![]() |
c0bc71b0b7 | ||
![]() |
c30bfd7b1c | ||
![]() |
e184028261 | ||
![]() |
986a7e6734 | ||
![]() |
2a9035d5ef | ||
![]() |
8a50d20a67 | ||
![]() |
0b0cc38444 | ||
![]() |
17c2a93e72 | ||
![]() |
e62cedb22c | ||
![]() |
76ae61b66c | ||
![]() |
edea526f38 | ||
![]() |
487e087287 | ||
![]() |
c63ac888dd | ||
![]() |
4d12b124f3 | ||
![]() |
35ce283736 | ||
![]() |
677712b0b8 | ||
![]() |
5171b800cc | ||
![]() |
bf05e14661 | ||
![]() |
e3a00905de | ||
![]() |
59af12bf34 | ||
![]() |
f7f0dfde51 | ||
![]() |
33a1d5f4fb | ||
![]() |
eeaf646a86 | ||
![]() |
7fdc7a6cee | ||
![]() |
f13ba67a2c | ||
![]() |
f421c1e361 | ||
![]() |
f91c8fabdd | ||
![]() |
9dfc5f4f1d | ||
![]() |
1fda6912da | ||
![]() |
cb83224c60 | ||
![]() |
9840b22546 | ||
![]() |
e3f843fd91 | ||
![]() |
803ebc0854 | ||
![]() |
7e96e3baef | ||
![]() |
768e0d4fbb | ||
![]() |
f92e23c955 | ||
![]() |
e321882b98 | ||
![]() |
b45509962e | ||
![]() |
c1fb3a47c7 | ||
![]() |
a0c3a990ed | ||
![]() |
506f9ca72e | ||
![]() |
7dd9c29cc0 | ||
![]() |
242700e084 | ||
![]() |
29b5ee3e68 | ||
![]() |
896290ad3b | ||
![]() |
876e8504c8 | ||
![]() |
53a055b90a | ||
![]() |
409d761b18 | ||
![]() |
f15315adb7 | ||
![]() |
b8b316aae0 | ||
![]() |
985e5358c9 | ||
![]() |
ffebc13d08 | ||
![]() |
3ff4df42ff | ||
![]() |
d6fbc73450 | ||
![]() |
3d6ac9df92 | ||
![]() |
e8390e52d1 | ||
![]() |
58daf1bb5c | ||
![]() |
cb8e2b07a4 | ||
![]() |
46f9d34447 | ||
![]() |
942c7fb069 | ||
![]() |
53130fc638 | ||
![]() |
8bf19dc060 | ||
![]() |
a5bfdf63d5 | ||
![]() |
41860b041e | ||
![]() |
25ef7e7fe3 | ||
![]() |
bcc409f302 | ||
![]() |
42719e7000 | ||
![]() |
0d6322c0aa | ||
![]() |
f7a4c40c50 | ||
![]() |
ec0af8046a | ||
![]() |
73c80e1168 | ||
![]() |
45ff418699 | ||
![]() |
032277ae8b | ||
![]() |
0bb4ac0fea | ||
![]() |
4a90e3b2b4 | ||
![]() |
8b7fefc8a9 | ||
![]() |
6f59ef7a7c | ||
![]() |
302c1c211d | ||
![]() |
74791cfe52 | ||
![]() |
453f68fa39 | ||
![]() |
aa9d2bf893 | ||
![]() |
23b98f7d65 | ||
![]() |
5d7bd73bcb | ||
![]() |
34b9684b6b | ||
![]() |
ccbb0ed6c7 | ||
![]() |
f1298870e5 | ||
![]() |
9cfd369c5d | ||
![]() |
f115feb16d | ||
![]() |
241a96ebaa | ||
![]() |
880cf573aa | ||
![]() |
89f5ebc84b | ||
![]() |
e98298cce4 | ||
![]() |
0d1899b32a | ||
![]() |
3e0e289f2f | ||
![]() |
11a8997ac7 | ||
![]() |
2f4e1888c2 | ||
![]() |
a9389e4e69 | ||
![]() |
2d3cede235 | ||
![]() |
08a09b065b | ||
![]() |
5fd076f043 | ||
![]() |
2555868990 | ||
![]() |
7eafbc22ae | ||
![]() |
3ed0fcbe05 | ||
![]() |
b6019eb81b | ||
![]() |
6eab9a32c9 | ||
![]() |
91fc56c322 | ||
![]() |
1887486d36 | ||
![]() |
bf6741eac4 | ||
![]() |
491489ca7d | ||
![]() |
91c4ab5bea | ||
![]() |
163e805f55 | ||
![]() |
d69375e72b | ||
![]() |
2b3603e86f | ||
![]() |
31d188ce81 | ||
![]() |
dfca41c98d | ||
![]() |
1526ad3bf0 | ||
![]() |
4e325ff203 | ||
![]() |
e8253b0970 | ||
![]() |
230e2187ce | ||
![]() |
a6a2686d53 | ||
![]() |
56cf62b487 | ||
![]() |
15628c9a05 | ||
![]() |
431b311ae5 | ||
![]() |
28f6c42647 | ||
![]() |
734c8e8e40 | ||
![]() |
436ba457e9 | ||
![]() |
0261b76314 | ||
![]() |
e1e2236a26 | ||
![]() |
04d64dfe9b | ||
![]() |
afcc6fd31a | ||
![]() |
6466ddb866 | ||
![]() |
75f3eeffa7 | ||
![]() |
a670d1ca6c | ||
![]() |
96c0fab3ab | ||
![]() |
90659342c0 | ||
![]() |
f88fe008bf | ||
![]() |
f5f007e74d | ||
![]() |
0c01215b6a | ||
![]() |
fa64bbb56c | ||
![]() |
fe7e3a638a | ||
![]() |
d2c70509dd | ||
![]() |
15ee836ddb | ||
![]() |
68e6a2163e | ||
![]() |
9041423906 | ||
![]() |
913abb6c7b | ||
![]() |
2d599df0ea | ||
![]() |
54aace97af | ||
![]() |
7e7f061b34 | ||
![]() |
333bf53537 | ||
![]() |
0528ee1229 | ||
![]() |
8425eb80c5 | ||
![]() |
c00c4a5ac3 | ||
![]() |
662b611e6d | ||
![]() |
9abeb21eee | ||
![]() |
34ae94e3e7 | ||
![]() |
d4da5e69ad | ||
![]() |
417a34ecd1 | ||
![]() |
b35ccb8eb6 | ||
![]() |
04571f757a | ||
![]() |
9763a1a6f5 | ||
![]() |
6c60f1e37f | ||
![]() |
23695330ef | ||
![]() |
43652e0843 | ||
![]() |
6f67da1292 | ||
![]() |
20c8fbabc9 | ||
![]() |
fccc570bee | ||
![]() |
8fc929b1ee | ||
![]() |
01ae99f7e8 |
@ -1,37 +0,0 @@
|
||||
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: rebar3 dialyzer
|
||||
- run:
|
||||
name: Eunit
|
||||
command: rebar3 eunit
|
||||
- run:
|
||||
name: Common Tests
|
||||
command: rebar3 ct
|
||||
- save_cache:
|
||||
key: dialyzer-cache-v1-{{ .Branch }}-{{ .Revision }}
|
||||
paths:
|
||||
- _build/default/rebar3_20.3.8_plt
|
||||
- store_artifacts:
|
||||
path: _build/test/logs
|
15
.gitea/workflows/test.yaml
Normal file
15
.gitea/workflows/test.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
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
|
20
.gitignore
vendored
20
.gitignore
vendored
@ -8,7 +8,21 @@ ebin/*.beam
|
||||
rel/example_project
|
||||
.concrete/DEV_MODE
|
||||
.rebar
|
||||
aeb_asm_scan.erl
|
||||
aeb_fate_asm_scan.erl
|
||||
gmb_asm_scan.erl
|
||||
gmb_fate_asm_scan.erl
|
||||
gmb_fate_asm_scan.xrl
|
||||
_build/
|
||||
aefateasm
|
||||
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
|
||||
|
1
Emakefile
Normal file
1
Emakefile
Normal file
@ -0,0 +1 @@
|
||||
{"src/*", [debug_info, {i, "include/"}, {outdir, "ebin/"}]}.
|
1
LICENSE
1
LICENSE
@ -1,5 +1,6 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2025, QPQ AG
|
||||
Copyright (c) 2017, aeternity developers
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
|
27
Makefile
27
Makefile
@ -1,20 +1,37 @@
|
||||
|
||||
|
||||
REBAR ?= rebar3
|
||||
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
|
||||
|
||||
all: local
|
||||
|
||||
local:
|
||||
sources: $(GENERATED_SRC)
|
||||
|
||||
local: $(GENERATED_SRC)
|
||||
@$(REBAR) as local release
|
||||
|
||||
console:
|
||||
console: local
|
||||
@$(REBAR) as local shell
|
||||
|
||||
clean:
|
||||
@$(REBAR) clean
|
||||
rm -f $(GENERATED_SRC)
|
||||
rm -f ebin/*.beam
|
||||
rm -rf _build
|
||||
|
||||
dialyzer: local
|
||||
@$(REBAR) as local dialyzer
|
||||
|
||||
distclean: clean
|
||||
@rm -rf _build/
|
||||
|
||||
eunit: local
|
||||
@$(REBAR) as local eunit
|
||||
|
||||
test: local
|
||||
@$(REBAR) as local eunit
|
||||
|
||||
ebin/%.beam: src/%.erl
|
||||
erlc +debug_info -o $(dir $@) $<
|
||||
|
||||
$(GENERATED_SRC): $(GENERATOR_DEPS)
|
||||
erl -pa ebin/ -noshell -s gmb_fate_generate_ops gen_and_halt src/ include/
|
||||
|
138
README.md
138
README.md
@ -1,9 +1,135 @@
|
||||
aebytecode
|
||||
=====
|
||||
# gmbytecode
|
||||
A library and stand alone assembler for Gajumaru bytecode.
|
||||
|
||||
An OTP library
|
||||
This version supports AEVM bytecode and FATE bytecode.
|
||||
|
||||
Build
|
||||
-----
|
||||
## Build
|
||||
|
||||
$ make
|
||||
|
||||
## Fate Code
|
||||
|
||||
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 three 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.
|
||||
|
||||
### Fate Assembler Code
|
||||
|
||||
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 stack postions is either a (for stack 0)
|
||||
or start with stack followed by an integer
|
||||
`stack1`
|
||||
`a`
|
||||
|
||||
Immediate values can be of 10 types:
|
||||
|
||||
1. Integers as decimals: {Digits} or -{Digits}
|
||||
`42`
|
||||
`-2374683271468723648732648736498712634876147`
|
||||
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`
|
||||
|
||||
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 ) |)
|
||||
`(| [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=`
|
||||
|
||||
|
||||
Where
|
||||
|
||||
Digits: [0123456789]
|
||||
|
||||
Hexdigits: [0123456789abcdef]
|
||||
|
||||
base58char: [123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]
|
||||
|
||||
base64char: [ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxy0123456789+/=]
|
||||
|
||||
Characters: any printable ascii character 0..255 (except " no quoting yet)
|
||||
|
||||
Key: any value except for a map
|
||||
|
||||
Bits: 01 or space
|
||||
|
||||
Elements: Nothing or Value , Elements
|
||||
|
||||
Size: Digits (0 < Size < 256)
|
||||
|
||||
Tag: Digits (0 =< Tag < Size)
|
||||
|
||||
$ rebar3 compile
|
||||
|
16
ebin/gmbytecode.app
Normal file
16
ebin/gmbytecode.app
Normal file
@ -0,0 +1,16 @@
|
||||
{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,[]}]}.
|
@ -1,55 +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_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).
|
@ -1,121 +0,0 @@
|
||||
|
||||
%% FATE opcodes
|
||||
-define('NOP' , 16#00).
|
||||
-define('RETURN' , 16#01).
|
||||
-define('CALL' , 16#02).
|
||||
-define('CALL_R' , 16#03).
|
||||
-define('CALL_T' , 16#04).
|
||||
-define('CALL_TR' , 16#05).
|
||||
-define('JUMP' , 16#06).
|
||||
-define('JUMPIF' , 16#07).
|
||||
-define('SWITCH' , 16#08).
|
||||
-define('PUSH' , 16#09).
|
||||
-define('DUP' , 16#0a).
|
||||
-define('POP' , 16#0b).
|
||||
-define('STORE' , 16#10).
|
||||
-define('ADD' , 16#11).
|
||||
-define('MUL' , 16#12).
|
||||
-define('SUB' , 16#13).
|
||||
-define('DIV' , 16#14).
|
||||
-define('MOD' , 16#15).
|
||||
-define('POW' , 16#16).
|
||||
-define('LT' , 16#17).
|
||||
-define('GT' , 16#18).
|
||||
-define('EQ' , 16#19).
|
||||
-define('ELT' , 16#1a).
|
||||
-define('EGT' , 16#1b).
|
||||
-define('NEQ' , 16#1c).
|
||||
-define('AND' , 16#1d).
|
||||
-define('OR' , 16#1e).
|
||||
-define('NOT' , 16#1f).
|
||||
-define('TUPLE' , 16#20).
|
||||
-define('ELEMENT' , 16#21).
|
||||
-define('MAP_EMPTY' , 16#22).
|
||||
-define('MAP_LOOKUP' , 16#23).
|
||||
-define('MAP_UPDATE' , 16#24).
|
||||
-define('MAP_DELETE' , 16#25).
|
||||
-define('MAP_MEMBER' , 16#26).
|
||||
-define('MAP_FROM_LIST' , 16#27).
|
||||
-define('NIL' , 16#28).
|
||||
-define('IS_NIL' , 16#29).
|
||||
-define('CONS' , 16#2a).
|
||||
-define('HD' , 16#2b).
|
||||
-define('TL' , 16#2c).
|
||||
-define('LENGTH' , 16#2d).
|
||||
-define('STR_EQ' , 16#2e).
|
||||
-define('STR_JOIN' , 16#2f).
|
||||
-define('ADDR_TO_STR' , 16#30).
|
||||
-define('STR_REVERSE' , 16#31).
|
||||
-define('INT_TO_ADDR' , 16#32).
|
||||
-define('VARIANT' , 16#33).
|
||||
-define('VARIANT_TEST' , 16#34).
|
||||
-define('VARIANT_ELEMENT', 16#35).
|
||||
-define('BITS_NONE' , 16#36).
|
||||
-define('BITS_ALL' , 16#37).
|
||||
-define('BITS_SET' , 16#38).
|
||||
-define('BITS_CLEAR' , 16#39).
|
||||
-define('BITS_TEST' , 16#3a).
|
||||
-define('BITS_SUM' , 16#3b).
|
||||
-define('BITS_OR' , 16#3c).
|
||||
-define('BITS_AND' , 16#3d).
|
||||
-define('BITS_DIFF' , 16#3e).
|
||||
-define('ADDRESS' , 16#3f).
|
||||
-define('BALANCE' , 16#40).
|
||||
-define('ORIGIN' , 16#41).
|
||||
-define('CALLER' , 16#42).
|
||||
-define('GASPRICE' , 16#43).
|
||||
-define('BLOCKHASH' , 16#44).
|
||||
-define('BENEFICIARY' , 16#45).
|
||||
-define('TIMESTAMP' , 16#46).
|
||||
-define('NUMBER' , 16#47).
|
||||
-define('DIFFICULTY' , 16#48).
|
||||
-define('GASLIMIT' , 16#49).
|
||||
-define('GAS' , 16#4a).
|
||||
-define('LOG0' , 16#4b).
|
||||
-define('LOG1' , 16#4c).
|
||||
-define('LOG2' , 16#4d).
|
||||
-define('LOG3' , 16#4e).
|
||||
-define('LOG4' , 16#4f).
|
||||
-define('ABORT' , 16#50).
|
||||
-define('EXIT' , 16#51).
|
||||
-define('DEACTIVATE' , 16#52).
|
||||
-define('INC' , 16#53).
|
||||
-define('DEC' , 16#54).
|
||||
-define('INT_TO_STR' , 16#55).
|
||||
-define('SPEND' , 16#56).
|
||||
-define('ORACLE_REGISTER', 16#57).
|
||||
-define('ORACLE_QUERY' , 16#58).
|
||||
-define('ORACLE_RESPOND' , 16#59).
|
||||
-define('ORACLE_EXTEND' , 16#5a).
|
||||
-define('ORACLE_GET_ANSWER', 16#5b).
|
||||
-define('ORACLE_GET_QUESTION', 16#5c).
|
||||
-define('ORACLE_QUERY_FEE', 16#5d).
|
||||
-define('AENS_RESOLVE' , 16#5e).
|
||||
-define('AENS_PRECLAIM' , 16#5f).
|
||||
-define('AENS_CLAIM' , 16#60).
|
||||
-define('AENS_UPDATE' , 16#61).
|
||||
-define('AENS_TRANSFER' , 16#62).
|
||||
-define('AENS_REVOKE' , 16#63).
|
||||
-define('ECVERIFY' , 16#64).
|
||||
-define('SHA3' , 16#65).
|
||||
-define('SHA256' , 16#66).
|
||||
-define('BLAKE2B' , 16#67).
|
||||
-define('RETURNR' , 16#68).
|
||||
-define('MAP_LOOKUPD' , 16#69).
|
||||
-define('SWITCH_V2' , 16#6a).
|
||||
-define('SWITCH_V3' , 16#6b).
|
||||
-define('SWITCH_VN' , 16#6c).
|
||||
-define('BITS_ALL_N' , 16#6d).
|
||||
-define('BITS_NONEA' , 16#6e).
|
||||
-define('BITS_ALLA' , 16#6f).
|
||||
-define('DUPA' , 16#70).
|
||||
-define('INCA' , 16#71).
|
||||
-define('DECA' , 16#72).
|
||||
-define('POPA' , 16#73).
|
||||
|
||||
|
||||
-define('FUNCTION' , 16#fe).
|
||||
-define('EXTEND' , 16#ff).
|
||||
|
||||
-define( COMMENT(X), {comment, X}).
|
||||
|
99
include/gmb_fate_data.hrl
Normal file
99
include/gmb_fate_data.hrl
Normal file
@ -0,0 +1,99 @@
|
||||
-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}).
|
15
include/gmb_heap.hrl
Normal file
15
include/gmb_heap.hrl
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
-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()} }).
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
-define( 'SHA3', 16#20).
|
||||
|
||||
-define( 'CREATOR', 16#2f).
|
||||
-define( 'ADDRESS', 16#30).
|
||||
-define( 'BALANCE', 16#31).
|
||||
-define( 'ORIGIN', 16#32).
|
||||
@ -165,6 +166,8 @@
|
||||
-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).
|
||||
@ -183,9 +186,20 @@
|
||||
-define(PRIM_CALL_MAP_TOLIST, 305).
|
||||
|
||||
-define(PRIM_CALL_IN_CRYPTO_RANGE(__TTYPE__), (((__TTYPE__) > 399) andalso ((__TTYPE__) < 500))).
|
||||
-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).
|
||||
-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).
|
12
include/gmb_typerep_def.hrl
Normal file
12
include/gmb_typerep_def.hrl
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
-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).
|
27
quickcheck/gmb_fate_code_tests.erl
Normal file
27
quickcheck/gmb_fate_code_tests.erl
Normal file
@ -0,0 +1,27 @@
|
||||
%%% @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)
|
||||
]}.
|
27
quickcheck/gmb_fate_data_tests.erl
Normal file
27
quickcheck/gmb_fate_data_tests.erl
Normal file
@ -0,0 +1,27 @@
|
||||
%%% @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)
|
||||
]}.
|
27
quickcheck/gmb_fate_encoding_tests.erl
Normal file
27
quickcheck/gmb_fate_encoding_tests.erl
Normal file
@ -0,0 +1,27 @@
|
||||
%%% @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)
|
||||
]}.
|
167
quickcheck/gmfate_code_eqc.erl
Normal file
167
quickcheck/gmfate_code_eqc.erl
Normal file
@ -0,0 +1,167 @@
|
||||
%%% @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))).
|
211
quickcheck/gmfate_eqc.erl
Normal file
211
quickcheck/gmfate_eqc.erl
Normal file
@ -0,0 +1,211 @@
|
||||
%%% @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.
|
56
quickcheck/gmfate_type_eqc.erl
Normal file
56
quickcheck/gmfate_type_eqc.erl
Normal file
@ -0,0 +1,56 @@
|
||||
%%% @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)))).
|
||||
|
38
rebar.config
38
rebar.config
@ -1,14 +1,24 @@
|
||||
%% -*- mode: erlang; indent-tabs-mode: nil -*-
|
||||
|
||||
{minimum_otp_vsn, "20.1"}.
|
||||
|
||||
{erl_opts, [debug_info]}.
|
||||
|
||||
{deps, [ {getopt, "1.0.1"}
|
||||
{deps, [ {eblake2, "1.0.0"}
|
||||
, {gmserialization, {git, "https://git.qpq.swiss/QPQ-AG/gmserialization.git",
|
||||
{ref, "9d2ecc00d32ea295309563e54a81636ecb597e96"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
]}.
|
||||
|
||||
{escript_incl_apps, [aebytecode, getopt]}.
|
||||
{escript_main_app, aebytecode}.
|
||||
{escript_name, aefateasm}.
|
||||
{escript_incl_apps, [gmbytecode, eblake2, gmserialization, getopt]}.
|
||||
{escript_main_app, gmbytecode}.
|
||||
{escript_name, gmfateasm}.
|
||||
{escript_emu_args, "%%!"}.
|
||||
|
||||
{pre_hooks,
|
||||
[{"(linux|darwin|solaris|win32)", compile, "make sources"},
|
||||
{"(freebsd)", compile, "gmake sources"}]}.
|
||||
|
||||
{provider_hooks, [{post, [{compile, escriptize}]}]}.
|
||||
|
||||
|
||||
@ -19,8 +29,8 @@
|
||||
]}.
|
||||
|
||||
|
||||
{relx, [{release, {aessembler, "0.0.1"},
|
||||
[aebytecode, getopt]},
|
||||
{relx, [{release, {gmbytecode, "3.4.1"},
|
||||
[gmbytecode, eblake2, getopt]},
|
||||
|
||||
{dev_mode, true},
|
||||
{include_erts, false},
|
||||
@ -28,16 +38,22 @@
|
||||
{extended_start_script, true}]}.
|
||||
|
||||
{profiles, [{binary, [
|
||||
{deps, [ {getopt, "1.0.1"}
|
||||
{deps, [ {eblake2, "1.0.0"}
|
||||
, {gmserialization, {git, "https://git.qpq.swiss/QPQ-AG/gmserialization.git",
|
||||
{ref, "9d2ecc00d32ea295309563e54a81636ecb597e96"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
]},
|
||||
|
||||
{post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)",
|
||||
escriptize,
|
||||
"cp \"$REBAR_BUILD_DIR/bin/aefateasm\" ./aefateasm"},
|
||||
"cp \"$REBAR_BUILD_DIR/bin/gmfateasm\" ./gmfateasm"},
|
||||
{"win32",
|
||||
escriptize,
|
||||
"robocopy \"%REBAR_BUILD_DIR%/bin/\" ./ aefateasm* "
|
||||
"robocopy \"%REBAR_BUILD_DIR%/bin/\" ./ gmfateasm* "
|
||||
"/njs /njh /nfl /ndl & exit /b 0"} % silence things
|
||||
]}
|
||||
]}]}.
|
||||
|
||||
]},
|
||||
{eqc, [{erl_opts, [{parse_transform, eqc_cover}, {d, 'EQC'}]},
|
||||
{extra_src_dirs, ["quickcheck"]} %% May not be called eqc!
|
||||
]}
|
||||
]}.
|
||||
|
23
rebar.lock
23
rebar.lock
@ -1,10 +1,23 @@
|
||||
{"1.1.0",
|
||||
[{<<"enacl">>,
|
||||
{git,"https://github.com/aeternity/enacl.git",
|
||||
{ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}},
|
||||
{"1.2.0",
|
||||
[{<<"gmserialization">>,
|
||||
{git,"https://git.qpq.swiss/QPQ-AG/gmserialization.git",
|
||||
{ref,"9d2ecc00d32ea295309563e54a81636ecb597e96"}},
|
||||
0},
|
||||
{<<"base58">>,
|
||||
{git,"https://git.qpq.swiss/QPQ-AG/erl-base58.git",
|
||||
{ref,"e6aa62eeae3d4388311401f06e4b939bf4e94b9c"}},
|
||||
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,[
|
||||
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
|
||||
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
|
||||
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]},
|
||||
{pkg_hash_ext,[
|
||||
{<<"eblake2">>, <<"3C4D300A91845B25D501929A26AC2E6F7157480846FAB2347A4C11AE52E08A99">>},
|
||||
{<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>}]}
|
||||
].
|
||||
|
@ -1,148 +0,0 @@
|
||||
%%%=============================================================================
|
||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% BLAKE2b implementation in Erlang - for details see: https://blake2.net
|
||||
%%% @end
|
||||
%%%=============================================================================
|
||||
|
||||
-module(aeb_blake2).
|
||||
|
||||
-export([ blake2b/2
|
||||
, blake2b/3
|
||||
]).
|
||||
|
||||
-define(MAX_64BIT, 16#ffffffffffffffff).
|
||||
|
||||
-spec blake2b(HashLen :: integer(), Msg :: binary()) -> {ok, binary()}.
|
||||
blake2b(HashLen, Msg) ->
|
||||
blake2b(HashLen, Msg, <<>>).
|
||||
|
||||
-spec blake2b(HashLen :: integer(), Msg :: binary(), Key :: binary()) -> {ok, binary()}.
|
||||
blake2b(HashLen, Msg0, Key) ->
|
||||
%% If message should be keyed, prepend message with padded key.
|
||||
Msg = <<(pad(128, Key))/binary, Msg0/binary>>,
|
||||
|
||||
%% Set up the initial state
|
||||
Init = (16#01010000 + (byte_size(Key) bsl 8) + HashLen),
|
||||
<<H0:64, H1_7/binary>> = blake_iv(),
|
||||
H = <<(H0 bxor Init):64, H1_7/binary>>,
|
||||
|
||||
%% Perform the compression - message will be chopped into 128-byte chunks.
|
||||
State = blake2b_compress(H, Msg, 0),
|
||||
|
||||
%% Just return the requested part of the hash
|
||||
{ok, binary_part(to_little_endian(State), {0, HashLen})}.
|
||||
|
||||
blake2b_compress(H, <<Chunk:(128*8), Rest/binary>>, BCompr) when Rest /= <<>> ->
|
||||
H1 = blake2b_compress(H, <<Chunk:(128*8)>>, BCompr + 128, false),
|
||||
blake2b_compress(H1, Rest, BCompr + 128);
|
||||
blake2b_compress(H, SmallChunk, BCompr) ->
|
||||
Size = byte_size(SmallChunk),
|
||||
FillSize = (128 - Size) * 8,
|
||||
blake2b_compress(H, <<SmallChunk/binary, 0:FillSize>>, BCompr + Size, true).
|
||||
|
||||
blake2b_compress(H, Chunk0, BCompr, Last) ->
|
||||
Chunk = to_big_endian(Chunk0),
|
||||
<<V0_11:(12*64), V12:64, V13:64, V14:64, V15:64>> = <<H/binary, (blake_iv())/binary>>,
|
||||
V12_ = V12 bxor (BCompr band ?MAX_64BIT),
|
||||
V13_ = V13 bxor ((BCompr bsr 64) band ?MAX_64BIT),
|
||||
V14_ = case Last of
|
||||
false -> V14;
|
||||
true -> V14 bxor ?MAX_64BIT
|
||||
end,
|
||||
V = <<V0_11:(12*64), V12_:64, V13_:64, V14_:64, V15:64>>,
|
||||
|
||||
<<VLow:(8*64), VHigh:(8*64)>> =
|
||||
lists:foldl(fun(Round, Vx) -> blake2b_mix(Round, Chunk, Vx) end, V, lists:seq(0, 11)),
|
||||
|
||||
<<HInt:(8*64)>> = H,
|
||||
<<((HInt bxor VLow) bxor VHigh):(8*64)>>.
|
||||
|
||||
blake2b_mix(Rnd, Chunk, V) ->
|
||||
<<V0:64, V1:64, V2:64, V3:64, V4:64, V5:64, V6:64, V7:64, V8:64,
|
||||
V9:64, V10:64, V11:64, V12:64, V13:64, V14:64, V15:64>> = V,
|
||||
<<M0:64, M1:64, M2:64, M3:64, M4:64, M5:64, M6:64, M7:64, M8:64,
|
||||
M9:64, M10:64, M11:64, M12:64, M13:64, M14:64, M15:64>> = Chunk,
|
||||
Ms = {M0, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15},
|
||||
M = fun(Ix) -> element(Ix+1, Ms) end,
|
||||
|
||||
[S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15] = sigma(Rnd rem 10),
|
||||
|
||||
{Vx0, Vx4, Vx8, Vx12} = blake2b_mix(V0, V4, V8, V12, M(S0), M(S1)),
|
||||
{Vx1, Vx5, Vx9, Vx13} = blake2b_mix(V1, V5, V9, V13, M(S2), M(S3)),
|
||||
{Vx2, Vx6, Vx10, Vx14} = blake2b_mix(V2, V6, V10, V14, M(S4), M(S5)),
|
||||
{Vx3, Vx7, Vx11, Vx15} = blake2b_mix(V3, V7, V11, V15, M(S6), M(S7)),
|
||||
|
||||
{Vy0, Vy5, Vy10, Vy15} = blake2b_mix(Vx0, Vx5, Vx10, Vx15, M(S8), M(S9)),
|
||||
{Vy1, Vy6, Vy11, Vy12} = blake2b_mix(Vx1, Vx6, Vx11, Vx12, M(S10), M(S11)),
|
||||
{Vy2, Vy7, Vy8, Vy13} = blake2b_mix(Vx2, Vx7, Vx8, Vx13, M(S12), M(S13)),
|
||||
{Vy3, Vy4, Vy9, Vy14} = blake2b_mix(Vx3, Vx4, Vx9, Vx14, M(S14), M(S15)),
|
||||
|
||||
<<Vy0:64, Vy1:64, Vy2:64, Vy3:64, Vy4:64, Vy5:64, Vy6:64, Vy7:64, Vy8:64,
|
||||
Vy9:64, Vy10:64, Vy11:64, Vy12:64, Vy13:64, Vy14:64, Vy15:64>>.
|
||||
|
||||
blake2b_mix(Va, Vb, Vc, Vd, X, Y) ->
|
||||
Va1 = (Va + Vb + X) band ?MAX_64BIT,
|
||||
Vd1 = rotr64(32, Vd bxor Va1),
|
||||
|
||||
Vc1 = (Vc + Vd1) band ?MAX_64BIT,
|
||||
Vb1 = rotr64(24, Vb bxor Vc1),
|
||||
|
||||
Va2 = (Va1 + Vb1 + Y) band ?MAX_64BIT,
|
||||
Vd2 = rotr64(16, Va2 bxor Vd1),
|
||||
|
||||
Vc2 = (Vc1 + Vd2) band ?MAX_64BIT,
|
||||
Vb2 = rotr64(63, Vb1 bxor Vc2),
|
||||
|
||||
{Va2, Vb2, Vc2, Vd2}.
|
||||
|
||||
blake_iv() ->
|
||||
IV0 = 16#6A09E667F3BCC908,
|
||||
IV1 = 16#BB67AE8584CAA73B,
|
||||
IV2 = 16#3C6EF372FE94F82B,
|
||||
IV3 = 16#A54FF53A5F1D36F1,
|
||||
IV4 = 16#510E527FADE682D1,
|
||||
IV5 = 16#9B05688C2B3E6C1F,
|
||||
IV6 = 16#1F83D9ABFB41BD6B,
|
||||
IV7 = 16#5BE0CD19137E2179,
|
||||
<<IV0:64, IV1:64, IV2:64, IV3:64, IV4:64, IV5:64, IV6:64, IV7:64>>.
|
||||
|
||||
sigma(N) ->
|
||||
{_, Row} = lists:keyfind(N, 1, sigma()), Row.
|
||||
|
||||
sigma() ->
|
||||
[{0, [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]},
|
||||
{1, [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3]},
|
||||
{2, [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4]},
|
||||
{3, [ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8]},
|
||||
{4, [ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13]},
|
||||
{5, [ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9]},
|
||||
{6, [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11]},
|
||||
{7, [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10]},
|
||||
{8, [ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5]},
|
||||
{9, [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]}].
|
||||
|
||||
rotr64(N, I64) ->
|
||||
<<I64rot:64>> = rotr641(N, <<I64:64>>),
|
||||
I64rot.
|
||||
|
||||
rotr641(16, <<X:(64-16), Y:16>>) -> <<Y:16, X:(64-16)>>;
|
||||
rotr641(24, <<X:(64-24), Y:24>>) -> <<Y:24, X:(64-24)>>;
|
||||
rotr641(32, <<X:(64-32), Y:32>>) -> <<Y:32, X:(64-32)>>;
|
||||
rotr641(63, <<X:(64-63), Y:63>>) -> <<Y:63, X:(64-63)>>.
|
||||
|
||||
pad(N, Bin) ->
|
||||
case (N - (byte_size(Bin) rem N)) rem N of
|
||||
0 -> Bin;
|
||||
Pad -> <<Bin/binary, 0:(Pad *8)>>
|
||||
end.
|
||||
|
||||
to_big_endian(Bin) -> to_big_endian(Bin, <<>>).
|
||||
to_big_endian(<<>>, Acc) -> Acc;
|
||||
to_big_endian(<<UInt64:1/little-unsigned-integer-unit:64, Rest/binary>>, Acc) ->
|
||||
to_big_endian(Rest, <<Acc/binary, UInt64:1/big-unsigned-integer-unit:64>>).
|
||||
|
||||
to_little_endian(Bin) -> to_little_endian(Bin, <<>>).
|
||||
to_little_endian(<<>>, Acc) -> Acc;
|
||||
to_little_endian(<<UInt64:1/big-unsigned-integer-unit:64, Rest/binary>>, Acc) ->
|
||||
to_little_endian(Rest, <<Acc/binary, UInt64:1/little-unsigned-integer-unit:64>>).
|
@ -1,882 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||
%%% @doc Assembler for Fate machine code.
|
||||
%%%
|
||||
%%% 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
|
||||
%%% arg0
|
||||
%%% References to variables/registers start with var
|
||||
%%% var0
|
||||
%%% Immediates can be of 9 types:
|
||||
%%% 1. Integers
|
||||
%%% 42
|
||||
%%% -2374683271468723648732648736498712634876147
|
||||
%%% 2. Hexadecimal integers starting with 0x
|
||||
%%% 0x0deadbeef0
|
||||
%%% 3. addresses, a 256-bit hash strings starting with #
|
||||
%%% followed by up to 64 hex chars
|
||||
%%% #00000deadbeef
|
||||
%%% 4. Boolean
|
||||
%%% true
|
||||
%%% false
|
||||
%%% 5. Strings
|
||||
%%% "Hello"
|
||||
%%% 6. Empty map
|
||||
%%% {}
|
||||
%%% 7. Lists
|
||||
%%% []
|
||||
%%% [1, 2]
|
||||
%%% 8. Bit field
|
||||
%%% <000>
|
||||
%%% <1010>
|
||||
%%% <>
|
||||
%%% !<>
|
||||
%%% 9. Tuples
|
||||
%%% ()
|
||||
%%% (1, "foo")
|
||||
%%% @end
|
||||
%%% Created : 21 Dec 2017
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeb_fate_asm).
|
||||
|
||||
-export([ assemble_file/3
|
||||
, asm_to_bytecode/2
|
||||
, bytecode_to_fate_code/2
|
||||
, function_call/1
|
||||
, pp/1
|
||||
, read_file/1
|
||||
, strip/1
|
||||
, to_asm/1
|
||||
, to_hexstring/1
|
||||
]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_fate_opcodes.hrl").
|
||||
-include_lib("aebytecode/include/aeb_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, _} = aeb_fate_asm_scan:scan(String),
|
||||
parse_function_call(Tokens).
|
||||
|
||||
parse_function_call([{id,_,Name}, {'(',_}| Rest]) ->
|
||||
{Args, []} = to_args(Rest),
|
||||
aeb_fate_encoding:serialize(
|
||||
{tuple, {mk_hash(Name), {tuple, list_to_tuple(Args)}}}).
|
||||
|
||||
|
||||
to_args([{')', _}]) -> {[], []};
|
||||
to_args(Tokens) ->
|
||||
case to_data(Tokens) of
|
||||
{Arg, [{',', _} | Rest]} ->
|
||||
{More, Rest2} = to_args(Rest),
|
||||
{[Arg|More], Rest2};
|
||||
{Arg, [{')', _} | Rest]} ->
|
||||
{[Arg], Rest}
|
||||
end.
|
||||
|
||||
to_data([{int,_line, Int}|Rest]) ->
|
||||
{Int, Rest};
|
||||
to_data([{boolean,_line, Bool}|Rest]) ->
|
||||
{Bool, Rest};
|
||||
to_data([{hash,_line, Hash}|Rest]) ->
|
||||
{Hash, Rest}.
|
||||
|
||||
pp(FateCode) ->
|
||||
Listing = to_asm(FateCode),
|
||||
io_lib:format("~ts~n",[Listing]).
|
||||
|
||||
|
||||
to_asm(#{ functions := Functions
|
||||
, symbols := Symbols
|
||||
, annotations := 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, {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_arg({immediate, I}) ->
|
||||
aeb_fate_data:format(I);
|
||||
format_arg({arg, N}) -> io_lib:format("arg~p", [N]);
|
||||
format_arg({var, N}) -> io_lib:format("var~p", [N]);
|
||||
format_arg({stack, 0}) -> "a";
|
||||
format_arg({stack, N}) -> io_lib:format("a~p", [N]).
|
||||
|
||||
|
||||
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) ->
|
||||
[" ",
|
||||
format_op(Op, Symbols),
|
||||
"\n",
|
||||
format_code(Rest, Symbols)].
|
||||
|
||||
format_op('RETURN', _) -> "RETURN";
|
||||
format_op({'RETURNR', Arg}, _) -> ["RETURNR ", format_arg(Arg)];
|
||||
format_op({'CALL', {immediate, Function}}, Symbols) ->
|
||||
["CALL ", lookup(Function, Symbols)];
|
||||
format_op({'CALL_T', {immediate, Function}}, Symbols) ->
|
||||
["CALL_T ", lookup(Function, Symbols)];
|
||||
format_op({'CALL_R', {immediate, Contract}, {immediate, Function}}, Symbols) ->
|
||||
["CALL_R ", lookup(Contract, Symbols), "." , lookup(Function, Symbols)];
|
||||
format_op({'CALL_R', Contract, {immediate, Function}}, Symbols) ->
|
||||
["CALL_R ", format_arg(Contract), "." , lookup(Function, Symbols)];
|
||||
format_op({'CALL_TR', {immediate, Contract}, {immediate, Function}}, Symbols) ->
|
||||
["CALL_TR ", lookup(Contract, Symbols), "." , lookup(Function, Symbols)];
|
||||
format_op({'CALL_TR', Contract, {immediate, Function}}, Symbols) ->
|
||||
["CALL_TR ", format_arg(Contract), "." , lookup(Function, Symbols)];
|
||||
format_op({'JUMP', {immediate, BB}}, _) ->
|
||||
["JUMP ", io_lib:format("~p", [BB])];
|
||||
format_op({'JUMPIF', Arg, {immediate, BB}}, _) ->
|
||||
["JUMPIF ", format_arg(Arg), " ", io_lib:format("~p", [BB])];
|
||||
format_op({'SWITCH_V2', Variant, {immediate, BB1}, {immediate, BB2}}, _) ->
|
||||
["SWITCH_V2 ", format_arg(Variant), " ", BB1, " ", BB2];
|
||||
format_op({'SWITCH_V3', Variant, {immediate, BB1}, {immediate, BB2}, {immediate, BB3}}, _) ->
|
||||
["SWITCH_V2 ", format_arg(Variant), " ", BB1, " ", BB2, " ", BB3];
|
||||
format_op({'SWITCH_VN', Variant, BBs}, _) ->
|
||||
["SWITCH_VN ", format_arg(Variant), [[" ", BB] || {immedate, BB} <- BBs]];
|
||||
format_op({'PUSH', Arg0}, _) ->
|
||||
["PUSH ", format_arg(Arg0)];
|
||||
format_op('INCA', _) -> "INCA";
|
||||
format_op({'INC', Name}, _) -> ["INC ", format_arg(Name)];
|
||||
format_op({'DEC', Name}, _) -> ["DEC ", format_arg(Name)];
|
||||
format_op('DECA', _) -> "DECA";
|
||||
format_op({'ADD', Dest, Left, Right}, _) ->
|
||||
["ADD ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)];
|
||||
format_op({'SUB', Dest, Left, Right}, _) ->
|
||||
["SUB ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)];
|
||||
format_op({'MUL', Dest, Left, Right}, _) ->
|
||||
["MUL ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)];
|
||||
format_op({'DIV', Dest, Left, Right}, _) ->
|
||||
["DIV ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)];
|
||||
format_op({'MOD', Dest, Left, Right}, _) ->
|
||||
["MOD ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)];
|
||||
format_op({'POW', Dest, Left, Right}, _) ->
|
||||
["POW ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)];
|
||||
format_op({'LT', Dest, Left, Right}, _) ->
|
||||
["LT ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)];
|
||||
format_op({'GT', Dest, Left, Right}, _) ->
|
||||
["GT ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)];
|
||||
format_op({'ELT', Dest, Left, Right}, _) ->
|
||||
["ELT ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)];
|
||||
format_op({'EGT', Dest, Left, Right}, _) ->
|
||||
["EGT ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)];
|
||||
format_op({'EQ', Dest, Left, Right}, _) ->
|
||||
["EQ ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)];
|
||||
format_op({'NEQ', Dest, Left, Right}, _) ->
|
||||
["NEQ ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)];
|
||||
format_op({'AND', Dest, Left, Right}, _) ->
|
||||
["AND ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)];
|
||||
format_op({'OR', Dest, Left, Right}, _) ->
|
||||
["OR ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)];
|
||||
format_op({'NOT', Dest, Name}, _) ->
|
||||
["NOT ", format_arg(Dest), " ", format_arg(Name)];
|
||||
format_op({'TUPLE', {immediate, Size}}, _) ->
|
||||
["TUPLE ", io_lib:format("~p", [Size])];
|
||||
format_op({'ELEMENT', Type, Dest, Which, Tuple}, _) ->
|
||||
[ "ELEMENT "
|
||||
, io_lib:format("~p ", [Type])
|
||||
, format_arg(Dest), " "
|
||||
, format_arg(Which), " "
|
||||
, format_arg(Tuple)];
|
||||
format_op({'MAP_EMPTY', Dest}, _) ->
|
||||
["MAP_EMPTY ", format_arg(Dest)];
|
||||
format_op({'MAP_LOOKUP', Dest, Map, Key}, _) ->
|
||||
["MAP_LOOKUP ", format_arg(Dest), " "
|
||||
, format_arg(Map), " ", format_arg(Key)];
|
||||
format_op({'MAP_DELETE', Dest, Map, Key}, _) ->
|
||||
["MAP_DELETE ", format_arg(Dest), " "
|
||||
, format_arg(Map), " ", format_arg(Key)];
|
||||
format_op({'MAP_LOOKUPD', Dest, Map, Key, Default}, _) ->
|
||||
["MAP_LOOKUPD ", format_arg(Dest), " "
|
||||
, format_arg(Map), " ", format_arg(Key), " ", format_arg(Default)];
|
||||
format_op({'MAP_UPDATE', Dest, Map, Key, Value}, _) ->
|
||||
["MAP_UPDATE ", format_arg(Dest), " "
|
||||
, format_arg(Map), " ", format_arg(Key), " ", format_arg(Value)];
|
||||
format_op({'MAP_MEMBER', Dest, Map, Key}, _) ->
|
||||
["MAP_MEMBER ", format_arg(Dest), " "
|
||||
, format_arg(Map), " ", format_arg(Key)];
|
||||
format_op({'MAP_FROM_LIST', Dest, List}, _) ->
|
||||
["MAP_FROM_LIST ", format_arg(Dest), " ", format_arg(List)];
|
||||
format_op({'NIL', Dest}, _) ->
|
||||
["NIL ", format_arg(Dest)];
|
||||
format_op({'IS_NIL', Dest, List}, _) ->
|
||||
["IS_NIL ", format_arg(Dest), " ", format_arg(List)];
|
||||
format_op({'CONS', Dest, Hd, Tl}, _) ->
|
||||
["CONS ", format_arg(Dest), " ", format_arg(Hd), " ", format_arg(Tl)];
|
||||
format_op({'HD', Dest, List}, _) ->
|
||||
["HD ", format_arg(Dest), " ", format_arg(List)];
|
||||
format_op({'TL', Dest, List}, _) ->
|
||||
["TL ", format_arg(Dest), " ", format_arg(List)];
|
||||
format_op({'LENGTH', Dest, List}, _) ->
|
||||
["LENGTH ", format_arg(Dest), " ", format_arg(List)];
|
||||
format_op({'STR_EQ', Dest, Str1, Str2}, _) ->
|
||||
["STR_EQ ", format_arg(Dest), " ", format_arg(Str1), format_arg(Str2)];
|
||||
format_op({'STR_JOIN', Dest, Str1, Str2}, _) ->
|
||||
["STR_JOIN ", format_arg(Dest), " ", format_arg(Str1), format_arg(Str2)];
|
||||
format_op({'INT_TO_STR', Dest, Str}, _) ->
|
||||
["INT_TO_STR ", format_arg(Dest), " ", format_arg(Str)];
|
||||
format_op({'ADDR_TO_STR', Dest, Str}, _) ->
|
||||
["ADDR_TO_STR ", format_arg(Dest), " ", format_arg(Str)];
|
||||
format_op({'STR_REVERSE', Dest, Str}, _) ->
|
||||
["STR_REVERSE ", format_arg(Dest), " ", format_arg(Str)];
|
||||
format_op({'INT_TO_ADDR', Dest, Str}, _) ->
|
||||
["INT_TO_ADDR ", format_arg(Dest), " ", format_arg(Str)];
|
||||
format_op({'VARIANT_TEST', Dest, Variant, Tag}, _) ->
|
||||
["VARIANT_TEST ", format_arg(Dest), " ", format_arg(Variant), " ", format_arg(Tag)];
|
||||
format_op({'VARIANT_ELEMENT', Dest, Variant, Index}, _) ->
|
||||
["VARIANT_ELEMENT ", format_arg(Dest), " ", format_arg(Variant), " ", format_arg(Index)];
|
||||
format_op({'VARIANT', Dest, SizeA, TagA, ElementsA}, _) ->
|
||||
["VARIANT ", format_arg(Dest), " ", format_arg(SizeA), " "
|
||||
, format_arg(TagA), " ", format_arg(ElementsA)];
|
||||
format_op('BITS_NONEA', _) -> "BITS_NONEA ";
|
||||
format_op({'BITS_NONE', To}, _) -> ["BITS_NONE ", format_arg(To)];
|
||||
format_op('BITS_ALLA', _) -> "BITS_ALLA";
|
||||
format_op({'BITS_ALL', To}, _) -> ["BITS_ALL ", format_arg(To)];
|
||||
format_op({'BITS_ALL_N', To, N}, _) ->
|
||||
["BITS_ALL_N ", format_arg(To), " ", format_arg(N)];
|
||||
format_op({'BITS_SET', To, Bits, Bit}, _) ->
|
||||
["BITS_SET ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)];
|
||||
format_op({'BITS_CLEAR', To, Bits, Bit}, _) ->
|
||||
["BITS_CLEAR ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)];
|
||||
format_op({'BITS_TEST', To, Bits, Bit}, _) ->
|
||||
["BITS_TEST ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)];
|
||||
format_op({'BITS_SUM', To, Bits}, _) ->
|
||||
["BITS_SUM ", format_arg(To), " ", format_arg(Bits)];
|
||||
format_op({'BITS_OR', To, Bits, Bit}, _) ->
|
||||
["BITS_OR ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)];
|
||||
format_op({'BITS_AND', To, Bits, Bit}, _) ->
|
||||
["BITS_AND ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)];
|
||||
format_op({'BITS_DIFF', To, Bits, Bit}, _) ->
|
||||
["BITS_DIFF ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)];
|
||||
format_op('DUPA', _) -> "DUPA";
|
||||
format_op({'DUP', {immediate, N}}, _) ->
|
||||
["DUP ", io_lib:format("~p", [N])];
|
||||
format_op({'POP', Dest}, _) ->
|
||||
["POP ", format_arg(Dest)];
|
||||
format_op({'STORE', Var, What}, _) ->
|
||||
["STORE ", format_arg(Var), " ", format_arg(What)];
|
||||
format_op('NOP', _) -> "NOP".
|
||||
|
||||
|
||||
read_file(Filename) ->
|
||||
{ok, File} = file:read_file(Filename),
|
||||
binary_to_list(File).
|
||||
|
||||
asm_to_bytecode(AssemblerCode, Options) ->
|
||||
{ok, Tokens, _} = aeb_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 = to_bytecode(Tokens, none, #{ functions => #{}
|
||||
, symbols => #{}
|
||||
, annotations => #{}
|
||||
}, [], Options),
|
||||
|
||||
ByteList = serialize(Env),
|
||||
Signatures = serialize_sigs(Env),
|
||||
SymbolTable = serialize_symbol_table(Env),
|
||||
Annotatations = serialize_annotations(Env),
|
||||
ByteCode = << (aeb_rlp:encode(list_to_binary(ByteList)))/binary,
|
||||
(aeb_rlp:encode(list_to_binary(Signatures)))/binary,
|
||||
(aeb_rlp:encode(SymbolTable))/binary,
|
||||
(aeb_rlp:encode(Annotatations))/binary
|
||||
>>,
|
||||
|
||||
case proplists:lookup(pp_hex_string, Options) of
|
||||
{pp_hex_string, true} ->
|
||||
io:format("Code: ~s~n",[to_hexstring(ByteList)]);
|
||||
none ->
|
||||
ok
|
||||
end,
|
||||
|
||||
{Env, ByteCode}.
|
||||
|
||||
strip(ByteCode) ->
|
||||
{Code, _Rest} = aeb_rlp:decode_one(ByteCode),
|
||||
Code.
|
||||
|
||||
bytecode_to_fate_code(Bytes, _Options) ->
|
||||
{ByteCode, Rest1} = aeb_rlp:decode_one(Bytes),
|
||||
{Signatures, Rest2} = aeb_rlp:decode_one(Rest1),
|
||||
{SymbolTable, Rest3} = aeb_rlp:decode_one(Rest2),
|
||||
{Annotations, <<>>} = aeb_rlp:decode_one(Rest3),
|
||||
|
||||
Env1 = deserialize(ByteCode, #{ function => none
|
||||
, bb => 0
|
||||
, current_bb_code => []
|
||||
, functions => #{}
|
||||
, code => #{}
|
||||
}),
|
||||
Env2 = deserialize_signatures(Signatures, Env1),
|
||||
Env3 = deserialize_symbols(SymbolTable, Env2),
|
||||
Env4 = deserialize_annotations(Annotations, Env3),
|
||||
Env4.
|
||||
|
||||
|
||||
deserialize(<<?FUNCTION:8, A, B, C, D, Rest/binary>>,
|
||||
#{ function := none
|
||||
, bb := 0
|
||||
, current_bb_code := []
|
||||
} = Env) ->
|
||||
{Sig, Rest2} = deserialize_signature(Rest),
|
||||
Env2 = Env#{function => {<<A,B,C,D>>, Sig}},
|
||||
deserialize(Rest2, Env2);
|
||||
deserialize(<<?FUNCTION:8, A, B, C, D, Rest/binary>>,
|
||||
#{ function := {F, Sig}
|
||||
, bb := BB
|
||||
, current_bb_code := Code
|
||||
, code := Program
|
||||
, functions := Funs} = Env) ->
|
||||
{NewSig, Rest2} = deserialize_signature(Rest),
|
||||
case Code of
|
||||
[] ->
|
||||
Env2 = Env#{ bb => 0
|
||||
, current_bb_code => []
|
||||
, function => {<<A,B,C,D>>, NewSig}
|
||||
, code => #{}
|
||||
, functions => Funs#{F => {Sig, Program}}},
|
||||
deserialize(Rest2, Env2);
|
||||
_ ->
|
||||
Env2 = Env#{ bb => 0
|
||||
, current_bb_code => []
|
||||
, function => {<<A,B,C,D>>, NewSig}
|
||||
, code => #{}
|
||||
, functions =>
|
||||
Funs#{F => {Sig,
|
||||
Program#{ BB => lists:reverse(Code)}}}},
|
||||
deserialize(Rest2, Env2)
|
||||
end;
|
||||
deserialize(<<Op:8, Rest/binary>>,
|
||||
#{ bb := BB
|
||||
, current_bb_code := Code
|
||||
, code := Program} = Env) ->
|
||||
{Rest2, OpCode} = deserialize_op(Op, Rest, Code),
|
||||
case aeb_fate_opcodes:end_bb(Op) of
|
||||
true ->
|
||||
deserialize(Rest2, Env#{ bb => BB+1
|
||||
, current_bb_code => []
|
||||
, code => Program#{BB =>
|
||||
lists:reverse(OpCode)}});
|
||||
false ->
|
||||
deserialize(Rest2, Env#{ current_bb_code => OpCode})
|
||||
end;
|
||||
deserialize(<<>>, #{ function := {F, Sig}
|
||||
, bb := BB
|
||||
, current_bb_code := Code
|
||||
, code := Program
|
||||
, functions := Funs} = Env) ->
|
||||
FunctionCode =
|
||||
case Code of
|
||||
[] -> Program;
|
||||
_ -> Program#{ BB => lists:reverse(Code)}
|
||||
end,
|
||||
Env#{ bb => 0
|
||||
, current_bb_code => []
|
||||
, function => none
|
||||
, code => #{}
|
||||
, functions => Funs#{F => {Sig, FunctionCode}}}.
|
||||
|
||||
deserialize_op(?ELEMENT, Rest, Code) ->
|
||||
{Type, Rest2} = deserialize_type(Rest),
|
||||
<<ArgType:8, Rest3/binary>> = Rest2,
|
||||
{Arg0, Rest4} = aeb_fate_encoding:deserialize_one(Rest3),
|
||||
{Arg1, Rest5} = aeb_fate_encoding:deserialize_one(Rest4),
|
||||
{Arg2, Rest6} = aeb_fate_encoding:deserialize_one(Rest5),
|
||||
Modifier0 = bits_to_modifier(ArgType band 2#11),
|
||||
Modifier1 = bits_to_modifier((ArgType bsr 2) band 2#11),
|
||||
Modifier2 = bits_to_modifier((ArgType bsr 4) band 2#11),
|
||||
{Rest6, [{ aeb_fate_opcodes:mnemonic(?ELEMENT)
|
||||
, Type
|
||||
, {Modifier0, Arg0}
|
||||
, {Modifier1, Arg1}
|
||||
, {Modifier2, Arg2}}
|
||||
| Code]};
|
||||
deserialize_op(?SWITCH_VN, Rest, Code) ->
|
||||
<<ArgType:8, Rest2/binary>> = Rest,
|
||||
{Arg0, Rest3} = aeb_fate_encoding:deserialize_one(Rest2),
|
||||
case aeb_fate_encoding:deserialize_one(Rest3) of
|
||||
{N, Rest4} when is_integer(N), N >= 0 ->
|
||||
Modifier0 = bits_to_modifier(ArgType band 2#11),
|
||||
immediate = bits_to_modifier((ArgType bsr 2) band 2#11),
|
||||
{BBs, Rest5} = deserialize_n(N, Rest4),
|
||||
{Rest5, [{aeb_fate_opcodes:mnemonic(?SWITCH_VN)
|
||||
, {Modifier0, Arg0}
|
||||
, {immediate, N}
|
||||
, list_to_tuple(BBs)}
|
||||
| Code]};
|
||||
_ -> exit(bad_argument_to_switch_vn)
|
||||
end;
|
||||
deserialize_op(Op, Rest, Code) ->
|
||||
OpName = aeb_fate_opcodes:mnemonic(Op),
|
||||
case aeb_fate_opcodes:args(Op) of
|
||||
0 -> {Rest, [OpName | Code]};
|
||||
1 ->
|
||||
<<ArgType:8, Rest2/binary>> = Rest,
|
||||
{Arg, Rest3} = aeb_fate_encoding:deserialize_one(Rest2),
|
||||
Modifier = bits_to_modifier(ArgType),
|
||||
{Rest3, [{OpName, {Modifier, Arg}} | Code]};
|
||||
2 ->
|
||||
<<ArgType:8, Rest2/binary>> = Rest,
|
||||
{Arg0, Rest3} = aeb_fate_encoding:deserialize_one(Rest2),
|
||||
{Arg1, Rest4} = aeb_fate_encoding:deserialize_one(Rest3),
|
||||
Modifier0 = bits_to_modifier(ArgType band 2#11),
|
||||
Modifier1 = bits_to_modifier((ArgType bsr 2) band 2#11),
|
||||
{Rest4, [{OpName, {Modifier0, Arg0},
|
||||
{Modifier1, Arg1}} | Code]};
|
||||
3 ->
|
||||
<<ArgType:8, Rest2/binary>> = Rest,
|
||||
{Arg0, Rest3} = aeb_fate_encoding:deserialize_one(Rest2),
|
||||
{Arg1, Rest4} = aeb_fate_encoding:deserialize_one(Rest3),
|
||||
{Arg2, Rest5} = aeb_fate_encoding:deserialize_one(Rest4),
|
||||
Modifier0 = bits_to_modifier(ArgType band 2#11),
|
||||
Modifier1 = bits_to_modifier((ArgType bsr 2) band 2#11),
|
||||
Modifier2 = bits_to_modifier((ArgType bsr 4) band 2#11),
|
||||
{Rest5, [{ OpName
|
||||
, {Modifier0, Arg0}
|
||||
, {Modifier1, Arg1}
|
||||
, {Modifier2, Arg2}}
|
||||
| Code]};
|
||||
4 ->
|
||||
<<ArgType:8, Rest2/binary>> = Rest,
|
||||
{Arg0, Rest3} = aeb_fate_encoding:deserialize_one(Rest2),
|
||||
{Arg1, Rest4} = aeb_fate_encoding:deserialize_one(Rest3),
|
||||
{Arg2, Rest5} = aeb_fate_encoding:deserialize_one(Rest4),
|
||||
{Arg3, Rest6} = aeb_fate_encoding:deserialize_one(Rest5),
|
||||
Modifier0 = bits_to_modifier(ArgType band 2#11),
|
||||
Modifier1 = bits_to_modifier((ArgType bsr 2) band 2#11),
|
||||
Modifier2 = bits_to_modifier((ArgType bsr 4) band 2#11),
|
||||
Modifier3 = bits_to_modifier((ArgType bsr 6) band 2#11),
|
||||
{Rest6, [{ OpName
|
||||
, {Modifier0, Arg0}
|
||||
, {Modifier1, Arg1}
|
||||
, {Modifier2, Arg2}
|
||||
, {Modifier3, Arg3}}
|
||||
| Code]}
|
||||
end.
|
||||
|
||||
deserialize_n(N, Binary) ->
|
||||
deserialize_n(N, Binary, []).
|
||||
|
||||
deserialize_n(0, Binary, Acc) ->
|
||||
{lists:reverse(Acc), Binary};
|
||||
deserialize_n(N, Binary, Acc) ->
|
||||
{Value, Rest} = aeb_fate_encoding:deserialize_one(Binary),
|
||||
deserialize_n(N-1, Rest, [Value|Acc]).
|
||||
|
||||
|
||||
|
||||
deserialize_signatures(_Signatures, Env) -> Env.
|
||||
|
||||
deserialize_symbols(Table, Env) ->
|
||||
?FATE_MAP_VALUE(SymbolTable) = aeb_fate_encoding:deserialize(Table),
|
||||
Env#{symbols => SymbolTable}.
|
||||
|
||||
deserialize_annotations(AnnotationsBin, Env) ->
|
||||
?FATE_MAP_VALUE(Annotations) = aeb_fate_encoding:deserialize(AnnotationsBin),
|
||||
Env#{annotations => Annotations}.
|
||||
|
||||
|
||||
|
||||
serialize_sigs(_Env) -> [].
|
||||
|
||||
serialize_symbol_table(#{ symbols := Symbols }) ->
|
||||
aeb_fate_encoding:serialize(aeb_fate_data:make_map(Symbols)).
|
||||
|
||||
serialize_annotations(#{ annotations := Annotations}) ->
|
||||
aeb_fate_encoding:serialize(aeb_fate_data:make_map(Annotations)).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
serialize(#{functions := Functions} =_Env) ->
|
||||
%% Sort the functions oon name to get a canonical serialisation.
|
||||
Code = [[?FUNCTION, Name, serialize_signature(Sig), C] ||
|
||||
{Name, {Sig, C}} <- lists:sort(maps:to_list(Functions))],
|
||||
serialize_code(lists:flatten(Code)).
|
||||
|
||||
|
||||
%% Argument encoding
|
||||
%% Agument Specification Byte
|
||||
%% bitpos: 6 4 2 0
|
||||
%% xx xx xx xx
|
||||
%% Arg3 Arg2 Arg1 Arg0
|
||||
%% Bit pattern
|
||||
%% 00 : stack/unused (depending on instruction)
|
||||
%% 01 : argN
|
||||
%% 10 : varN
|
||||
%% 11 : immediate
|
||||
|
||||
serialize_code([ {Arg0Type, Arg0}
|
||||
, {Arg1Type, Arg1}
|
||||
, {Arg2Type, Arg2}
|
||||
, {Arg3Type, Arg3}| Rest]) ->
|
||||
ArgSpec =
|
||||
modifier_bits(Arg0Type) bor
|
||||
(modifier_bits(Arg1Type) bsl 2) bor
|
||||
(modifier_bits(Arg2Type) bsl 4) bor
|
||||
(modifier_bits(Arg3Type) bsl 6),
|
||||
[ ArgSpec
|
||||
, serialize_data(Arg0Type, Arg0)
|
||||
, serialize_data(Arg1Type, Arg1)
|
||||
, serialize_data(Arg2Type, Arg2)
|
||||
, serialize_data(Arg3Type, Arg3)
|
||||
| serialize_code(Rest)];
|
||||
serialize_code([ {Arg0Type, Arg0}
|
||||
, {Arg1Type, Arg1}
|
||||
, {Arg2Type, Arg2}
|
||||
| Rest]) ->
|
||||
ArgSpec =
|
||||
modifier_bits(Arg0Type) bor
|
||||
(modifier_bits(Arg1Type) bsl 2) bor
|
||||
(modifier_bits(Arg2Type) bsl 4),
|
||||
[ArgSpec
|
||||
, serialize_data(Arg0Type, Arg0)
|
||||
, serialize_data(Arg1Type, Arg1)
|
||||
, serialize_data(Arg2Type, Arg2)
|
||||
| serialize_code(Rest)];
|
||||
serialize_code([ {Arg0Type, Arg0}
|
||||
, {Arg1Type, Arg1}
|
||||
| Rest]) ->
|
||||
ArgSpec =
|
||||
modifier_bits(Arg0Type) bor
|
||||
(modifier_bits(Arg1Type) bsl 2),
|
||||
[ArgSpec
|
||||
, serialize_data(Arg0Type, Arg0)
|
||||
, serialize_data(Arg1Type, Arg1)
|
||||
| serialize_code(Rest)];
|
||||
serialize_code([ {Arg0Type, Arg0} | Rest]) ->
|
||||
ArgSpec =
|
||||
modifier_bits(Arg0Type),
|
||||
[ArgSpec
|
||||
, serialize_data(Arg0Type, Arg0)
|
||||
| serialize_code(Rest)];
|
||||
serialize_code([ ?ELEMENT
|
||||
, ResType
|
||||
| Rest]) ->
|
||||
[?ELEMENT,
|
||||
serialize_type(ResType)
|
||||
| serialize_code(Rest)];
|
||||
serialize_code([ ?SWITCH_VN
|
||||
, {Arg0Type, Arg0}
|
||||
, {immediate, N}
|
||||
| Rest]) when is_integer(N), N >= 0 ->
|
||||
ArgSpec =
|
||||
modifier_bits(Arg0Type) bor
|
||||
(modifier_bits(immediate) bsl 2),
|
||||
{Serialization, Rest2} = serialize_n_ints(N, Rest),
|
||||
[?SWITCH_VN
|
||||
, ArgSpec
|
||||
, serialize_data(Arg0Type, Arg0)
|
||||
, serialize_data(immediate, N)
|
||||
| Serialization] ++ serialize_code(Rest2);
|
||||
serialize_code([B|Rest]) ->
|
||||
[B | serialize_code(Rest)];
|
||||
serialize_code([]) -> [].
|
||||
|
||||
serialize_n_ints(N, Rest) ->
|
||||
serialize_n_ints(N, Rest, []).
|
||||
|
||||
serialize_n_ints(0, Rest, Acc) ->
|
||||
%% Acc is a list of binaries.
|
||||
{lists:reverse(Acc), Rest};
|
||||
serialize_n_ints(N, [Int|Rest], Acc) when is_integer(Int), Int >= 0 ->
|
||||
serialize_n_ints(N - 1, Rest, [aeb_fate_encoding:serialize(Int)|Acc]);
|
||||
serialize_n_ints(_, [], _) ->
|
||||
exit(not_enough_bbs_for_switch_vn);
|
||||
serialize_n_ints(_, _, _) ->
|
||||
exit(bad_bbs_value_for_switch_vn).
|
||||
|
||||
|
||||
|
||||
%% 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) -> 2#00.
|
||||
|
||||
bits_to_modifier(2#11) -> immediate;
|
||||
bits_to_modifier(2#10) -> var;
|
||||
bits_to_modifier(2#01) -> arg;
|
||||
bits_to_modifier(2#00) -> stack.
|
||||
|
||||
serialize_data(_, Data) ->
|
||||
aeb_fate_encoding:serialize(Data).
|
||||
|
||||
serialize_signature({Args, RetType}) ->
|
||||
[serialize_type({tuple, Args}) |
|
||||
serialize_type(RetType)].
|
||||
|
||||
|
||||
|
||||
deserialize_signature(Binary) ->
|
||||
{{tuple, Args}, Rest} = deserialize_type(Binary),
|
||||
{RetType, Rest2} = deserialize_type(Rest),
|
||||
{{Args, RetType}, Rest2}.
|
||||
|
||||
deserialize_type(<<0, Rest/binary>>) -> {integer, Rest};
|
||||
deserialize_type(<<1, Rest/binary>>) -> {boolean, Rest};
|
||||
deserialize_type(<<2, Rest/binary>>) ->
|
||||
{T, Rest2} = deserialize_type(Rest),
|
||||
{{list, T}, Rest2};
|
||||
deserialize_type(<<3, N, Rest/binary>>) ->
|
||||
{Ts, Rest2} = deserialize_types(N, Rest, []),
|
||||
{{tuple, Ts}, Rest2};
|
||||
deserialize_type(<<4, Rest/binary>>) -> {address, Rest};
|
||||
deserialize_type(<<5, Rest/binary>>) -> {bits, Rest};
|
||||
deserialize_type(<<6, Rest/binary>>) ->
|
||||
{K, Rest2} = deserialize_type(Rest),
|
||||
{V, Rest3} = deserialize_type(Rest2),
|
||||
{{map, K, V}, Rest3}.
|
||||
|
||||
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]).
|
||||
|
||||
|
||||
to_hexstring(ByteList) ->
|
||||
"0x" ++ lists:flatten(
|
||||
[io_lib:format("~2.16.0b", [X])
|
||||
|| X <- ByteList]).
|
||||
|
||||
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
%% 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, 'ELEMENT'}|Rest], Address, Env, Code, Opts) ->
|
||||
OpCode = aeb_fate_opcodes:m_to_op('ELEMENT'),
|
||||
{RetType, Rest2} = to_type(Rest),
|
||||
to_bytecode(Rest2, Address, Env, [RetType, OpCode|Code], Opts);
|
||||
to_bytecode([{mnemonic,_line, Op}|Rest], Address, Env, Code, Opts) ->
|
||||
OpCode = aeb_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, N}|Rest], Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env, [{stack, N}|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([{hash,_line, Hash}|Rest], Address, Env, Code, Opts) ->
|
||||
to_bytecode(Rest, Address, Env, [{immediate, Hash}|Code], Opts);
|
||||
to_bytecode([{id,_line, ID}|Rest], Address, Env, Code, Opts) ->
|
||||
{Hash, Env2} = insert_symbol(ID, Env),
|
||||
to_bytecode(Rest, Address, Env2, [{immediate, Hash}|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) ->
|
||||
Env2 = insert_fun(Address, Code, Env),
|
||||
#{functions := Funs} = Env2,
|
||||
case proplists:lookup(pp_opcodes, Opts) of
|
||||
{pp_opcodes, true} ->
|
||||
Ops = [C || {_Name, {_Sig, C}} <- maps:to_list(Funs)],
|
||||
io:format("opcodes ~p~n", [Ops]);
|
||||
none ->
|
||||
ok
|
||||
end,
|
||||
Env2.
|
||||
|
||||
|
||||
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, _, "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_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.
|
||||
|
||||
|
||||
serialize_type(integer) -> [0];
|
||||
serialize_type(boolean) -> [1];
|
||||
serialize_type({list, T}) -> [2 | serialize_type(T)];
|
||||
serialize_type({tuple, Ts}) ->
|
||||
case length(Ts) of
|
||||
N when N =< 255 ->
|
||||
[3, N | [serialize_type(T) || T <- Ts]]
|
||||
end;
|
||||
serialize_type(address) -> 4;
|
||||
serialize_type(bits) -> 5;
|
||||
serialize_type({map, K, V}) -> [6 | serialize_type(K) ++ serialize_type(V)].
|
||||
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
%% Helper functions
|
||||
%% -------------------------------------------------------------------
|
||||
|
||||
%% State handling
|
||||
|
||||
insert_fun(none, [], Env) -> Env;
|
||||
insert_fun({Name, Type, RetType}, Code, #{functions := Functions} = Env) ->
|
||||
{Hash, Env2} = insert_symbol(Name, Env),
|
||||
Env2#{
|
||||
functions => Functions#{Hash => {{Type, RetType}, lists:reverse(Code)}}
|
||||
}.
|
||||
|
||||
mk_hash(Id) ->
|
||||
%% Use first 4 bytes of blake hash
|
||||
{ok, <<A:8, B:8, C:8, D:8,_/binary>> } = aeb_blake2:blake2b(?HASH_BYTES, list_to_binary(Id)),
|
||||
<<A,B,C,D>>.
|
||||
|
||||
%% Handle annotations
|
||||
|
||||
insert_annotation(comment, Line, Comment, #{annotations := A} = Env) ->
|
||||
Key = aeb_fate_data:make_tuple({aeb_fate_data:make_string("comment"), Line}),
|
||||
Value = aeb_fate_data:make_string(Comment),
|
||||
Env#{annotations => A#{ Key => Value}}.
|
||||
|
||||
get_comments(Annotations) ->
|
||||
[ {Line, Comment} ||
|
||||
{?FATE_TUPLE({?FATE_STRING_VALUE("comment"), Line}),
|
||||
?FATE_STRING_VALUE(Comment)} <- maps:to_list(Annotations)].
|
||||
|
||||
%% Symbols handling
|
||||
|
||||
insert_symbol(Id, Env) ->
|
||||
Hash = mk_hash(Id),
|
||||
insert_symbol(Id, Hash, Env).
|
||||
|
||||
insert_symbol(Id, Hash, #{symbols := Symbols} = Env) ->
|
||||
case maps:find(Hash, Symbols) of
|
||||
{ok, Id} -> {Hash, Env};
|
||||
{ok, Id2} ->
|
||||
%% Very unlikely...
|
||||
exit({two_symbols_with_same_hash, Id, Id2});
|
||||
error ->
|
||||
{Hash, Env#{symbols => Symbols#{ Id => Hash
|
||||
, Hash => Id}}}
|
||||
end.
|
||||
|
||||
%% Symbol table handling
|
||||
|
||||
lookup(Name, Symbols) ->
|
||||
maps:get(Name, Symbols, Name).
|
@ -1,207 +0,0 @@
|
||||
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2019, aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Handling FATE code.
|
||||
%%% @end
|
||||
%%% Created : 9 Jan 2019
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
Definitions.
|
||||
DIGIT = [0-9]
|
||||
HEXDIGIT = [0-9a-fA-F]
|
||||
LOWER = [a-z_]
|
||||
UPPER = [A-Z]
|
||||
INT = {DIGIT}+
|
||||
HEX = 0x{HEXDIGIT}+
|
||||
HASH = #{HEXDIGIT}+
|
||||
WS = [\000-\s]
|
||||
ID = {LOWER}[a-zA-Z0-9_]*
|
||||
|
||||
|
||||
Rules.
|
||||
arg{INT} : {token, {arg, TokenLine, parse_arg(TokenChars)}}.
|
||||
var{INT} : {token, {var, TokenLine, parse_var(TokenChars)}}.
|
||||
a : {token, {stack, TokenLine, 0}}.
|
||||
a{INT} : {token, {stack, TokenLine, parse_acc(TokenChars)}}.
|
||||
|
||||
true : {token, {boolean, TokenLine, true}}.
|
||||
false : {token, {boolean, TokenLine, false}}.
|
||||
|
||||
RETURN : {token, {mnemonic, TokenLine, 'RETURN'}}.
|
||||
RETURNR : {token, {mnemonic, TokenLine, 'RETURNR'}}.
|
||||
CALL : {token, {mnemonic, TokenLine, 'CALL'}}.
|
||||
NOP : {token, {mnemonic, TokenLine, 'NOP'}}.
|
||||
|
||||
CALL_R : {token, {mnemonic, TokenLine, 'CALL_R'}}.
|
||||
CALL_T : {token, {mnemonic, TokenLine, 'CALL_T'}}.
|
||||
CALL_TR : {token, {mnemonic, TokenLine, 'CALL_TR'}}.
|
||||
JUMP : {token, {mnemonic, TokenLine, 'JUMP'}}.
|
||||
JUMPIF : {token, {mnemonic, TokenLine, 'JUMPIF'}}.
|
||||
SWITCH_V2 : {token, {mnemonic, TokenLine, 'SWITCH_V2'}}.
|
||||
SWITCH_V3 : {token, {mnemonic, TokenLine, 'SWITCH_V3'}}.
|
||||
SWITCH_VN : {token, {mnemonic, TokenLine, 'SWITCH_VN'}}.
|
||||
|
||||
PUSH : {token, {mnemonic, TokenLine, 'PUSH'}}.
|
||||
DUP : {token, {mnemonic, TokenLine, 'DUP'}}.
|
||||
DUPA : {token, {mnemonic, TokenLine, 'DUPA'}}.
|
||||
POP : {token, {mnemonic, TokenLine, 'POP'}}.
|
||||
|
||||
STORE : {token, {mnemonic, TokenLine, 'STORE'}}.
|
||||
|
||||
ADD : {token, {mnemonic, TokenLine, 'ADD'}}.
|
||||
MUL : {token, {mnemonic, TokenLine, 'MUL'}}.
|
||||
SUB : {token, {mnemonic, TokenLine, 'SUB'}}.
|
||||
DIV : {token, {mnemonic, TokenLine, 'DIV'}}.
|
||||
MOD : {token, {mnemonic, TokenLine, 'MOD'}}.
|
||||
POW : {token, {mnemonic, TokenLine, 'POW'}}.
|
||||
|
||||
INC : {token, {mnemonic, TokenLine, 'INC'}}.
|
||||
DEC : {token, {mnemonic, TokenLine, 'DEC'}}.
|
||||
INCA : {token, {mnemonic, TokenLine, 'INCA'}}.
|
||||
DECA : {token, {mnemonic, TokenLine, 'DECA'}}.
|
||||
|
||||
LT : {token, {mnemonic, TokenLine, 'LT'}}.
|
||||
GT : {token, {mnemonic, TokenLine, 'GT'}}.
|
||||
EQ : {token, {mnemonic, TokenLine, 'EQ'}}.
|
||||
ELT : {token, {mnemonic, TokenLine, 'ELT'}}.
|
||||
EGT : {token, {mnemonic, TokenLine, 'EGT'}}.
|
||||
NEQ : {token, {mnemonic, TokenLine, 'NEQ'}}.
|
||||
|
||||
AND : {token, {mnemonic, TokenLine, 'AND'}}.
|
||||
OR : {token, {mnemonic, TokenLine, 'OR'}}.
|
||||
NOT : {token, {mnemonic, TokenLine, 'NOT'}}.
|
||||
|
||||
TUPLE : {token, {mnemonic, TokenLine, 'TUPLE'}}.
|
||||
ELEMENT : {token, {mnemonic, TokenLine, 'ELEMENT'}}.
|
||||
|
||||
MAP_EMPTY : {token, {mnemonic, TokenLine, 'MAP_EMPTY'}}.
|
||||
MAP_LOOKUP : {token, {mnemonic, TokenLine, 'MAP_LOOKUP'}}.
|
||||
MAP_LOOKUPD : {token, {mnemonic, TokenLine, 'MAP_LOOKUPD'}}.
|
||||
MAP_UPDATE : {token, {mnemonic, TokenLine, 'MAP_UPDATE'}}.
|
||||
MAP_MEMBER : {token, {mnemonic, TokenLine, 'MAP_MEMBER'}}.
|
||||
MAP_DELETE : {token, {mnemonic, TokenLine, 'MAP_DELETE'}}.
|
||||
MAP_FROM_LIST : {token, {mnemonic, TokenLine, 'MAP_FROM_LIST'}}.
|
||||
|
||||
NIL : {token, {mnemonic, TokenLine, 'NIL'}}.
|
||||
IS_NIL : {token, {mnemonic, TokenLine, 'IS_NIL'}}.
|
||||
CONS : {token, {mnemonic, TokenLine, 'CONS'}}.
|
||||
HD : {token, {mnemonic, TokenLine, 'HD'}}.
|
||||
TL : {token, {mnemonic, TokenLine, 'TL'}}.
|
||||
LENGTH : {token, {mnemonic, TokenLine, 'LENGTH'}}.
|
||||
|
||||
STR_EQ : {token, {mnemonic, TokenLine, 'STR_EQ'}}.
|
||||
STR_JOIN : {token, {mnemonic, TokenLine, 'STR_JOIN'}}.
|
||||
INT_TO_STR : {token, {mnemonic, TokenLine, 'INT_TO_STR'}}.
|
||||
ADDR_TO_STR : {token, {mnemonic, TokenLine, 'ADDR_TO_STR'}}.
|
||||
STR_REVERSE : {token, {mnemonic, TokenLine, 'STR_REVERSE'}}.
|
||||
|
||||
INT_TO_ADDR : {token, {mnemonic, TokenLine, 'INT_TO_ADDR'}}.
|
||||
|
||||
VARIANT : {token, {mnemonic, TokenLine, 'VARIANT'}}.
|
||||
VARIANT_TEST : {token, {mnemonic, TokenLine, 'VARIANT_TEST'}}.
|
||||
VARIANT_ELEMENT : {token, {mnemonic, TokenLine, 'VARIANT_ELEMENT'}}.
|
||||
|
||||
BITS_NONE : {token, {mnemonic, TokenLine, 'BITS_NONE'}}.
|
||||
BITS_NONEA : {token, {mnemonic, TokenLine, 'BITS_NONEA'}}.
|
||||
BITS_ALL : {token, {mnemonic, TokenLine, 'BITS_ALL'}}.
|
||||
BITS_ALLA : {token, {mnemonic, TokenLine, 'BITS_ALLA'}}.
|
||||
BITS_ALL_N : {token, {mnemonic, TokenLine, 'BITS_ALL_N'}}.
|
||||
BITS_SET : {token, {mnemonic, TokenLine, 'BITS_SET'}}.
|
||||
BITS_CLEAR : {token, {mnemonic, TokenLine, 'BITS_CLEAR'}}.
|
||||
BITS_TEST : {token, {mnemonic, TokenLine, 'BITS_TEST'}}.
|
||||
BITS_SUM : {token, {mnemonic, TokenLine, 'BITS_SUM'}}.
|
||||
BITS_OR : {token, {mnemonic, TokenLine, 'BITS_OR'}}.
|
||||
BITS_AND : {token, {mnemonic, TokenLine, 'BITS_AND'}}.
|
||||
BITS_DIFF : {token, {mnemonic, TokenLine, 'BITS_DIFF'}}.
|
||||
|
||||
|
||||
ADDRESS : {token, {mnemonic, TokenLine, 'ADDRESS'}}.
|
||||
BALANCE : {token, {mnemonic, TokenLine, 'BALANCE'}}.
|
||||
ORIGIN : {token, {mnemonic, TokenLine, 'ORIGIN'}}.
|
||||
CALLER : {token, {mnemonic, TokenLine, 'CALLER'}}.
|
||||
GASPRICE : {token, {mnemonic, TokenLine, 'GASPRICE'}}.
|
||||
BLOCKHASH : {token, {mnemonic, TokenLine, 'BLOCKHASH'}}.
|
||||
BENEFICIARY : {token, {mnemonic, TokenLine, 'BENEFICIARY'}}.
|
||||
TIMESTAMP : {token, {mnemonic, TokenLine, 'TIMESTAMP'}}.
|
||||
NUMBER : {token, {mnemonic, TokenLine, 'NUMBER'}}.
|
||||
DIFFICULTY : {token, {mnemonic, TokenLine, 'DIFFICULTY'}}.
|
||||
GASLIMIT : {token, {mnemonic, TokenLine, 'GASLIMIT'}}.
|
||||
GAS : {token, {mnemonic, TokenLine, 'GAS'}}.
|
||||
LOG0 : {token, {mnemonic, TokenLine, 'LOG0'}}.
|
||||
LOG1 : {token, {mnemonic, TokenLine, 'LOG1'}}.
|
||||
LOG2 : {token, {mnemonic, TokenLine, 'LOG2'}}.
|
||||
LOG3 : {token, {mnemonic, TokenLine, 'LOG3'}}.
|
||||
LOG4 : {token, {mnemonic, TokenLine, 'LOG4'}}.
|
||||
ABORT : {token, {mnemonic, TokenLine, 'ABORT'}}.
|
||||
EXIT : {token, {mnemonic, TokenLine, 'EXIT'}}.
|
||||
DEACTIVATE : {token, {mnemonic, TokenLine, 'DEACTIVATE'}}.
|
||||
COMMENT : {token, {mnemonic, TokenLine, 'COMMENT'}}.
|
||||
|
||||
FUNCTION : {token, {function, TokenLine, 'FUNCTION' }}.
|
||||
|
||||
{ID} :
|
||||
{token, {id, TokenLine, TokenChars}}.
|
||||
{HEX} :
|
||||
{token, {int, TokenLine, parse_hex(TokenChars)}}.
|
||||
{INT} :
|
||||
{token, {int, TokenLine, parse_int(TokenChars)}}.
|
||||
{HASH} :
|
||||
{token, {hash, TokenLine, parse_hash(TokenChars)}}.
|
||||
|
||||
|
||||
%% Symbols
|
||||
\-\> : {token, {'to', TokenLine}}.
|
||||
\: : {token, {'to', TokenLine}}.
|
||||
, : {token, {',', TokenLine}}.
|
||||
\( : {token, {'(', TokenLine}}.
|
||||
\) : {token, {')', TokenLine}}.
|
||||
\[ : {token, {'[', TokenLine}}.
|
||||
\] : {token, {']', TokenLine}}.
|
||||
\{ : {token, {'{', TokenLine}}.
|
||||
\} : {token, {'}', TokenLine}}.
|
||||
|
||||
;;.* :
|
||||
{token, {comment, TokenLine, drop_prefix($;, TokenChars)}}.
|
||||
|
||||
\. : skip_token.
|
||||
|
||||
|
||||
%% Whitespace ignore
|
||||
{WS} : skip_token.
|
||||
|
||||
%% Comments (TODO: nested comments)
|
||||
|
||||
|
||||
. : {error, "Unexpected token: " ++ TokenChars}.
|
||||
|
||||
Erlang code.
|
||||
|
||||
-export([scan/1]).
|
||||
|
||||
-dialyzer({nowarn_function, yyrev/2}).
|
||||
|
||||
-ignore_xref([format_error/1, string/2, token/2, token/3, tokens/2, tokens/3]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_fate_opcodes.hrl").
|
||||
|
||||
|
||||
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
|
||||
|
||||
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) ->
|
||||
N = list_to_integer(Chars, 16),
|
||||
<<N:256>>.
|
||||
|
||||
scan(S) ->
|
||||
string(S).
|
||||
|
||||
drop_prefix(C, [C|Rest]) ->
|
||||
drop_prefix(C, Rest);
|
||||
drop_prefix(_, Tail) -> Tail.
|
@ -1,303 +0,0 @@
|
||||
-module(aeb_fate_code).
|
||||
|
||||
-export([ return/0
|
||||
, return/1
|
||||
, call/1
|
||||
, call_t/1
|
||||
, call_r/2
|
||||
, call_tr/2
|
||||
, jump/1
|
||||
, jumpif/2
|
||||
, switch/3
|
||||
, switch/4
|
||||
, switch/5
|
||||
, switch/6
|
||||
, push/1
|
||||
, inc/0
|
||||
, inc/1
|
||||
, dec/0
|
||||
, dec/1
|
||||
, add/3
|
||||
, sub/3
|
||||
, mul/3
|
||||
, divide/3
|
||||
, modulo/3
|
||||
, pow/3
|
||||
, lt/3
|
||||
, gt/3
|
||||
, elt/3
|
||||
, egt/3
|
||||
, eq/3
|
||||
, neq/3
|
||||
, and_op/3
|
||||
, or_op/3
|
||||
, not_op/2
|
||||
, tuple/1
|
||||
, element_op/4
|
||||
, map_empty/1
|
||||
, map_lookup/3
|
||||
, map_lookup/4
|
||||
, map_update/4
|
||||
, map_member/3
|
||||
, map_from_list/2
|
||||
, nil/1
|
||||
, is_nil/2
|
||||
, cons/3
|
||||
, hd/2
|
||||
, tl/2
|
||||
, length/2
|
||||
, str_eq/3
|
||||
, str_join/3
|
||||
, int_to_str/2
|
||||
, addr_to_str/2
|
||||
, str_reverse/2
|
||||
, int_to_addr/2
|
||||
, variant_test/3
|
||||
, variant_element/3
|
||||
, variant/4
|
||||
, bits_none/0
|
||||
, bits_none/1
|
||||
, bits_all/0
|
||||
, bits_all/1
|
||||
, bits_all_n/2
|
||||
, bits_set/3
|
||||
, bits_clear/3
|
||||
, bits_test/3
|
||||
, bits_sum/2
|
||||
, bits_or/3
|
||||
, bits_and/3
|
||||
, bits_diff/3
|
||||
, dup/0
|
||||
, dup/1
|
||||
, pop/0
|
||||
, store/2
|
||||
, nop/0
|
||||
]).
|
||||
|
||||
-define(i(__X__), {immediate, __X__ }).
|
||||
|
||||
return() ->
|
||||
'RETURN'.
|
||||
|
||||
return(Arg) ->
|
||||
{'RETURNR', Arg}.
|
||||
|
||||
call(Function) when is_binary(Function)->
|
||||
{'CALL', ?i(Function) }.
|
||||
|
||||
call_t(Function) when is_binary(Function) ->
|
||||
{'CALL_T', ?i(Function)}.
|
||||
|
||||
call_r(Contract, Function) when is_binary(Function) ->
|
||||
{'CALL_R', Contract, ?i(Function)}.
|
||||
|
||||
call_tr(Contract, Function) when is_binary(Function) ->
|
||||
{'CALL_TR', Contract, ?i(Function)}.
|
||||
|
||||
jump(BB) when is_integer(BB) ->
|
||||
{'JUMP', ?i(BB)}.
|
||||
|
||||
jumpif(Arg, BB) when is_integer(BB) ->
|
||||
{'JUMPIF', Arg, ?i(BB)}.
|
||||
|
||||
switch(Arg, BB1, BB2) when is_integer(BB1),
|
||||
is_integer(BB2) ->
|
||||
{'SWITCH_V2', Arg, ?i(BB1), ?i(BB2)}.
|
||||
|
||||
switch(Arg, BB1, BB2, BB3) when is_integer(BB1),
|
||||
is_integer(BB2),
|
||||
is_integer(BB3) ->
|
||||
{'SWITCH_V3', Arg, ?i(BB1), ?i(BB2), ?i(BB3)}.
|
||||
|
||||
switch(Arg, BB1, BB2, BB3, BB4) when is_integer(BB1),
|
||||
is_integer(BB2),
|
||||
is_integer(BB3),
|
||||
is_integer(BB4) ->
|
||||
{'SWITCH_V4', Arg, ?i(BB1), ?i(BB2), ?i(BB3), ?i(BB4)}.
|
||||
|
||||
switch(Arg, BB1, BB2, BB3, BB4, BB5) when is_integer(BB1),
|
||||
is_integer(BB2),
|
||||
is_integer(BB3),
|
||||
is_integer(BB4),
|
||||
is_integer(BB5) ->
|
||||
{'SWITCH_V5', Arg, ?i(BB1), ?i(BB2), ?i(BB3), ?i(BB4), ?i(BB5)}.
|
||||
|
||||
push(Arg) ->
|
||||
{'PUSH', Arg}.
|
||||
|
||||
inc() ->
|
||||
'INCA'.
|
||||
|
||||
inc(Arg) ->
|
||||
{'INC', Arg}.
|
||||
|
||||
dec() ->
|
||||
'DECA'.
|
||||
|
||||
dec(Arg) ->
|
||||
{'DEC', Arg}.
|
||||
|
||||
add(Dest, Left, Right) ->
|
||||
{'ADD', Dest, Left, Right}.
|
||||
|
||||
sub(Dest, Left, Right) ->
|
||||
{'SUB', Dest, Left, Right}.
|
||||
|
||||
mul(Dest, Left, Right) ->
|
||||
{'MUL', Dest, Left, Right}.
|
||||
|
||||
divide(Dest, Left, Right) ->
|
||||
{'DIV', Dest, Left, Right}.
|
||||
|
||||
modulo(Dest, Left, Right) ->
|
||||
{'MOD', Dest, Left, Right}.
|
||||
|
||||
pow(Dest, Left, Right) ->
|
||||
{'POW', Dest, Left, Right}.
|
||||
|
||||
lt(Dest, Left, Right) ->
|
||||
{'LT', Dest, Left, Right}.
|
||||
|
||||
gt(Dest, Left, Right) ->
|
||||
{'GT', Dest, Left, Right}.
|
||||
|
||||
elt(Dest, Left, Right) ->
|
||||
{'ELT', Dest, Left, Right}.
|
||||
|
||||
egt(Dest, Left, Right) ->
|
||||
{'EGT', Dest, Left, Right}.
|
||||
|
||||
eq(Dest, Left, Right) ->
|
||||
{'EQ', Dest, Left, Right}.
|
||||
|
||||
neq(Dest, Left, Right) ->
|
||||
{'NEQ', Dest, Left, Right}.
|
||||
|
||||
and_op(Dest, Left, Right) ->
|
||||
{'AND', Dest, Left, Right}.
|
||||
|
||||
or_op(Dest, Left, Right) ->
|
||||
{'OR', Dest, Left, Right}.
|
||||
|
||||
not_op(Dest, Arg) ->
|
||||
{'NOT', Dest, Arg}.
|
||||
|
||||
tuple(Size) when is_integer(Size) ->
|
||||
{'TUPLE', ?i(Size)}.
|
||||
|
||||
element_op(Type, Dest, N, T) ->
|
||||
{'ELEMENT', Type, Dest, N, T}.
|
||||
|
||||
map_empty(Dest) ->
|
||||
{'MAP_EMPTY', Dest}.
|
||||
|
||||
map_lookup(Dest, Map, Key) ->
|
||||
{'MAP_LOOKUP', Dest, Map, Key}.
|
||||
|
||||
map_lookup(Dest, Map, Key, Default) ->
|
||||
{'MAP_LOOKUPD', Dest, Map, Key, Default}.
|
||||
|
||||
map_update(Dest, Map, Key, Value) ->
|
||||
{'MAP_UPDATE', Dest, Map, Key, Value}.
|
||||
|
||||
map_member(Dest, Map, Key) ->
|
||||
{'MAP_MEMBER', Dest, Map, Key}.
|
||||
|
||||
map_from_list(Dest, List) ->
|
||||
{'MAP_MEMBER', Dest, List}.
|
||||
|
||||
nil(Dest) ->
|
||||
{'NIL', Dest}.
|
||||
|
||||
is_nil(Dest, List) ->
|
||||
{'IS_NIL', Dest, List}.
|
||||
|
||||
cons(Dest, Hd, Tl) ->
|
||||
{'CONS', Dest, Hd, Tl}.
|
||||
|
||||
hd(Dest, List) ->
|
||||
{'HD', Dest, List}.
|
||||
|
||||
tl(Dest, List) ->
|
||||
{'TL', Dest, List}.
|
||||
|
||||
length(Dest, List) ->
|
||||
{'LENGTH', Dest, List}.
|
||||
|
||||
str_eq(Dest, Str1, Str2) ->
|
||||
{'STR_EQ', Dest, Str1, Str2}.
|
||||
|
||||
str_join(Dest, Str1, Str2) ->
|
||||
{'STR_JOIN', Dest, Str1, Str2}.
|
||||
|
||||
int_to_str(Dest, Str) ->
|
||||
{'INT_TO_STR', Dest, Str}.
|
||||
|
||||
addr_to_str(Dest, Str) ->
|
||||
{'ADDR_TO_STR', Dest, Str}.
|
||||
|
||||
str_reverse(Dest, Str) ->
|
||||
{'STR_REVERSE', Dest, Str}.
|
||||
|
||||
int_to_addr(Dest, Str) ->
|
||||
{'INT_TO_ADDR', Dest, Str}.
|
||||
|
||||
variant_test(Dest, Variant, Tag) ->
|
||||
{'VARIANT_TEST', Dest, Variant, Tag}.
|
||||
|
||||
variant_element( Dest, Variant, Index) ->
|
||||
{'VARIANT_ELEMENT', Dest, Variant, Index}.
|
||||
|
||||
variant(Dest, SizeA, TagA, ElementsA) ->
|
||||
{'VARIANT', Dest, SizeA, TagA, ElementsA}.
|
||||
|
||||
bits_none() ->
|
||||
'BITS_NONEA'.
|
||||
|
||||
bits_none(To) ->
|
||||
{'BITS_NONE', To}.
|
||||
|
||||
bits_all() ->
|
||||
'BITS_ALLA'.
|
||||
|
||||
bits_all(To) ->
|
||||
{'BITS_ALL', To}.
|
||||
|
||||
bits_all_n(To, N) ->
|
||||
{'BITS_ALL_N', To, N}.
|
||||
|
||||
bits_set(To, Bits, Bit) ->
|
||||
{'BITS_SET', To, Bits, Bit}.
|
||||
|
||||
bits_clear(To, Bits, Bit) ->
|
||||
{'BITS_CLEAR', To, Bits, Bit}.
|
||||
|
||||
bits_test(To, Bits, Bit) ->
|
||||
{'BITS_TEST', To, Bits, Bit}.
|
||||
|
||||
bits_sum(To, Bits) ->
|
||||
{'BITS_SUM', To, Bits}.
|
||||
|
||||
bits_or(To, Bits, Bit) ->
|
||||
{'BITS_OR', To, Bits, Bit}.
|
||||
|
||||
bits_and(To, Bits, Bit) ->
|
||||
{'BITS_AND', To, Bits, Bit}.
|
||||
|
||||
bits_diff(To, Bits, Bit) ->
|
||||
{'BITS_DIFF', To, Bits, Bit}.
|
||||
|
||||
dup() ->
|
||||
'DUPA'.
|
||||
|
||||
dup(N) when is_integer(N) ->
|
||||
{'DUP', ?i(N)}.
|
||||
|
||||
pop() ->
|
||||
'POP'.
|
||||
|
||||
store(Var, What) ->
|
||||
{'STORE', Var, What}.
|
||||
|
||||
nop() ->
|
||||
'NOP'.
|
@ -1,182 +0,0 @@
|
||||
%% 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_void() :: ?FATE_VOID_T.
|
||||
|
||||
-type fate_tuple() :: ?FATE_TUPLE_T.
|
||||
|
||||
-type fate_type() ::
|
||||
fate_boolean()
|
||||
| fate_integer()
|
||||
| fate_nil()
|
||||
| fate_list()
|
||||
| fate_unit()
|
||||
| fate_tuple()
|
||||
| fate_string()
|
||||
| fate_address()
|
||||
| fate_variant()
|
||||
| fate_map()
|
||||
| fate_list()
|
||||
| fate_tuple()
|
||||
| fate_void(). %% Not sure we need this.
|
||||
|
||||
-export_type([fate_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_VOID) -> "void";
|
||||
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) -> "unit";
|
||||
format(?FATE_TUPLE(T)) ->
|
||||
"{ " ++ [format(E) ++ " " || E <- erlang:tuple_to_list(T)] ++ "}";
|
||||
format(S) when ?IS_FATE_STRING(S) -> [S];
|
||||
format(?FATE_VARIANT(Size, Tag, T)) ->
|
||||
"( " ++ integer_to_list(Size) ++ ", "
|
||||
++ integer_to_list(Tag) ++ ", "
|
||||
++ [format(E) ++ " " || E <- erlang:tuple_to_list(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_list([]) -> " ]";
|
||||
format_list([E]) -> format(E) ++ " ]";
|
||||
format_list([H|T]) -> format(H) ++ ", " ++ format_list(T).
|
||||
|
||||
format_kvs([]) -> "";
|
||||
format_kvs([{K,V}]) -> "( " ++ format(K) ++ " => " ++ format(V) ++ " )";
|
||||
format_kvs([{K,V} | Rest]) ->
|
||||
"( " ++ format(K) ++ " => " ++ format(V) ++ " ), " ++ format_kvs(Rest).
|
||||
|
||||
|
||||
%% -- 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]).
|
@ -1,262 +0,0 @@
|
||||
%% 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, (aeb_rlp:encode(Bytes))/binary>>;
|
||||
serialize(?FATE_ADDRESS(Address)) when is_binary(Address) ->
|
||||
<<?ADDRESS, (aeb_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}|_] = 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 ->
|
||||
aeb_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} = aeb_rlp:decode_one(Rest),
|
||||
{?MAKE_FATE_INTEGER(-binary:decode_unsigned(Bint) - ?SMALL_INT_SIZE),
|
||||
Rest2};
|
||||
deserialize2(<<?POS_BIG_INT, Rest/binary>>) ->
|
||||
{Bint, Rest2} = aeb_rlp:decode_one(Rest),
|
||||
{?MAKE_FATE_INTEGER(binary:decode_unsigned(Bint) + ?SMALL_INT_SIZE),
|
||||
Rest2};
|
||||
deserialize2(<<?NEG_BITS, Rest/binary>>) ->
|
||||
{Bint, Rest2} = aeb_rlp:decode_one(Rest),
|
||||
{?FATE_BITS(-binary:decode_unsigned(Bint)), Rest2};
|
||||
deserialize2(<<?POS_BITS, Rest/binary>>) ->
|
||||
{Bint, Rest2} = aeb_rlp:decode_one(Rest),
|
||||
{?FATE_BITS(binary:decode_unsigned(Bint)), Rest2};
|
||||
deserialize2(<<?LONG_STRING, Rest/binary>>) ->
|
||||
{String, Rest2} = aeb_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} = aeb_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} = aeb_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} = aeb_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} = aeb_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}.
|
@ -1,363 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Opcodes
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeb_fate_opcodes).
|
||||
|
||||
-export([ args/1
|
||||
, end_bb/1
|
||||
, mnemonic/1
|
||||
, m_to_op/1
|
||||
, opcode/1
|
||||
]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_fate_opcodes.hrl").
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
|
||||
opcode(X) when X >= 0, X =< 255 -> X;
|
||||
opcode({comment,X}) -> ?COMMENT(X).
|
||||
|
||||
mnemonic(?NOP) -> 'NOP' ;
|
||||
mnemonic(?RETURN) -> 'RETURN' ;
|
||||
mnemonic(?CALL) -> 'CALL' ;
|
||||
mnemonic(?CALL_R) -> 'CALL_R' ;
|
||||
mnemonic(?CALL_T) -> 'CALL_T' ;
|
||||
mnemonic(?CALL_TR) -> 'CALL_TR' ;
|
||||
mnemonic(?JUMP) -> 'JUMP' ;
|
||||
mnemonic(?JUMPIF) -> 'JUMPIF' ;
|
||||
mnemonic(?PUSH) -> 'PUSH' ;
|
||||
mnemonic(?DUP) -> 'DUP' ;
|
||||
mnemonic(?DUPA) -> 'DUPA' ;
|
||||
mnemonic(?POP) -> 'POP' ;
|
||||
mnemonic(?STORE) -> 'STORE' ;
|
||||
mnemonic(?ADD) -> 'ADD' ;
|
||||
mnemonic(?MUL) -> 'MUL' ;
|
||||
mnemonic(?SUB) -> 'SUB' ;
|
||||
mnemonic(?DIV) -> 'DIV' ;
|
||||
mnemonic(?MOD) -> 'MOD' ;
|
||||
mnemonic(?POW) -> 'POW' ;
|
||||
mnemonic(?LT) -> 'LT' ;
|
||||
mnemonic(?GT) -> 'GT' ;
|
||||
mnemonic(?EQ) -> 'EQ' ;
|
||||
mnemonic(?ELT) -> 'ELT' ;
|
||||
mnemonic(?EGT) -> 'EGT' ;
|
||||
mnemonic(?NEQ) -> 'NEQ' ;
|
||||
mnemonic(?AND) -> 'AND' ;
|
||||
mnemonic(?OR) -> 'OR' ;
|
||||
mnemonic(?NOT) -> 'NOT' ;
|
||||
mnemonic(?TUPLE) -> 'TUPLE' ;
|
||||
mnemonic(?ELEMENT) -> 'ELEMENT' ;
|
||||
mnemonic(?MAP_EMPTY) -> 'MAP_EMPTY' ;
|
||||
mnemonic(?MAP_LOOKUP) -> 'MAP_LOOKUP' ;
|
||||
mnemonic(?MAP_UPDATE) -> 'MAP_UPDATE' ;
|
||||
mnemonic(?MAP_DELETE) -> 'MAP_DELETE' ;
|
||||
mnemonic(?MAP_MEMBER) -> 'MAP_MEMBER' ;
|
||||
mnemonic(?MAP_FROM_LIST) -> 'MAP_FROM_LIST' ;
|
||||
mnemonic(?NIL) -> 'NIL' ;
|
||||
mnemonic(?IS_NIL) -> 'IS_NIL' ;
|
||||
mnemonic(?CONS) -> 'CONS' ;
|
||||
mnemonic(?HD) -> 'HD' ;
|
||||
mnemonic(?TL) -> 'TL' ;
|
||||
mnemonic(?LENGTH) -> 'LENGTH' ;
|
||||
mnemonic(?STR_EQ) -> 'STR_EQ' ;
|
||||
mnemonic(?STR_JOIN) -> 'STR_JOIN' ;
|
||||
mnemonic(?ADDR_TO_STR) -> 'ADDR_TO_STR' ;
|
||||
mnemonic(?STR_REVERSE) -> 'STR_REVERSE' ;
|
||||
mnemonic(?INT_TO_ADDR) -> 'INT_TO_ADDR' ;
|
||||
mnemonic(?VARIANT) -> 'VARIANT' ;
|
||||
mnemonic(?VARIANT_TEST) -> 'VARIANT_TEST' ;
|
||||
mnemonic(?VARIANT_ELEMENT) -> 'VARIANT_ELEMENT' ;
|
||||
mnemonic(?BITS_NONE) -> 'BITS_NONE' ;
|
||||
mnemonic(?BITS_NONEA) -> 'BITS_NONEA' ;
|
||||
mnemonic(?BITS_ALL) -> 'BITS_ALL' ;
|
||||
mnemonic(?BITS_ALLA) -> 'BITS_ALLA' ;
|
||||
mnemonic(?BITS_SET) -> 'BITS_SET' ;
|
||||
mnemonic(?BITS_CLEAR) -> 'BITS_CLEAR' ;
|
||||
mnemonic(?BITS_TEST) -> 'BITS_TEST' ;
|
||||
mnemonic(?BITS_SUM) -> 'BITS_SUM' ;
|
||||
mnemonic(?BITS_OR) -> 'BITS_OR' ;
|
||||
mnemonic(?BITS_AND) -> 'BITS_AND' ;
|
||||
mnemonic(?BITS_DIFF) -> 'BITS_DIFF' ;
|
||||
mnemonic(?ADDRESS) -> 'ADDRESS' ;
|
||||
mnemonic(?BALANCE) -> 'BALANCE' ;
|
||||
mnemonic(?ORIGIN) -> 'ORIGIN' ;
|
||||
mnemonic(?CALLER) -> 'CALLER' ;
|
||||
mnemonic(?GASPRICE) -> 'GASPRICE' ;
|
||||
mnemonic(?BLOCKHASH) -> 'BLOCKHASH' ;
|
||||
mnemonic(?BENEFICIARY) -> 'BENEFICIARY' ;
|
||||
mnemonic(?TIMESTAMP) -> 'TIMESTAMP' ;
|
||||
mnemonic(?NUMBER) -> 'NUMBER' ;
|
||||
mnemonic(?DIFFICULTY) -> 'DIFFICULTY' ;
|
||||
mnemonic(?GASLIMIT) -> 'GASLIMIT' ;
|
||||
mnemonic(?GAS) -> 'GAS' ;
|
||||
mnemonic(?LOG0) -> 'LOG0' ;
|
||||
mnemonic(?LOG1) -> 'LOG1' ;
|
||||
mnemonic(?LOG2) -> 'LOG2' ;
|
||||
mnemonic(?LOG3) -> 'LOG3' ;
|
||||
mnemonic(?LOG4) -> 'LOG4' ;
|
||||
mnemonic(?ABORT) -> 'ABORT' ;
|
||||
mnemonic(?EXIT) -> 'EXIT' ;
|
||||
mnemonic(?DEACTIVATE) -> 'DEACTIVATE' ;
|
||||
mnemonic(?INC) -> 'INC' ;
|
||||
mnemonic(?DEC) -> 'DEC' ;
|
||||
mnemonic(?INCA) -> 'INCA' ;
|
||||
mnemonic(?DECA) -> 'DECA' ;
|
||||
mnemonic(?INT_TO_STR) -> 'INT_TO_STR' ;
|
||||
mnemonic(?SPEND) -> 'SPEND' ;
|
||||
mnemonic(?ORACLE_REGISTER) -> 'ORACLE_REGISTER' ;
|
||||
mnemonic(?ORACLE_QUERY) -> 'ORACLE_QUERY' ;
|
||||
mnemonic(?ORACLE_RESPOND) -> 'ORACLE_RESPOND' ;
|
||||
mnemonic(?ORACLE_EXTEND) -> 'ORACLE_EXTEND' ;
|
||||
mnemonic(?ORACLE_GET_ANSWER) -> 'ORACLE_GET_ANSWER' ;
|
||||
mnemonic(?ORACLE_GET_QUESTION) -> 'ORACLE_GET_QUESTION' ;
|
||||
mnemonic(?ORACLE_QUERY_FEE) -> 'ORACLE_QUERY_FEE' ;
|
||||
mnemonic(?AENS_RESOLVE) -> 'AENS_RESOLVE' ;
|
||||
mnemonic(?AENS_PRECLAIM) -> 'AENS_PRECLAIM' ;
|
||||
mnemonic(?AENS_CLAIM) -> 'AENS_CLAIM' ;
|
||||
mnemonic(?AENS_UPDATE) -> 'AENS_UPDATE' ;
|
||||
mnemonic(?AENS_TRANSFER) -> 'AENS_TRANSFER' ;
|
||||
mnemonic(?AENS_REVOKE) -> 'AENS_REVOKE' ;
|
||||
mnemonic(?ECVERIFY) -> 'ECVERIFY' ;
|
||||
mnemonic(?SHA3) -> 'SHA3' ;
|
||||
mnemonic(?SHA256) -> 'SHA256' ;
|
||||
mnemonic(?BLAKE2B) -> 'BLAKE2B' ;
|
||||
mnemonic(?RETURNR) -> 'RETURNR' ;
|
||||
mnemonic(?MAP_LOOKUPD) -> 'MAP_LOOKUPD' ;
|
||||
mnemonic(?SWITCH_V2) -> 'SWITCH_V2' ;
|
||||
mnemonic(?SWITCH_V3) -> 'SWITCH_V3' ;
|
||||
mnemonic(?SWITCH_VN) -> 'SWITCH_VN' ;
|
||||
mnemonic(?BITS_ALL_N) -> 'BITS_ALL_N' ;
|
||||
mnemonic(?FUNCTION) -> 'FUNCTION' ;
|
||||
mnemonic(?EXTEND) -> 'EXTEND'.
|
||||
|
||||
|
||||
m_to_op('NOP') -> ?NOP ;
|
||||
m_to_op('RETURN') -> ?RETURN ;
|
||||
m_to_op('CALL') -> ?CALL ;
|
||||
m_to_op('CALL_R') -> ?CALL_R ;
|
||||
m_to_op('CALL_T') -> ?CALL_T ;
|
||||
m_to_op('CALL_TR') -> ?CALL_TR ;
|
||||
m_to_op('JUMP') -> ?JUMP ;
|
||||
m_to_op('JUMPIF') -> ?JUMPIF ;
|
||||
m_to_op('PUSH') -> ?PUSH ;
|
||||
m_to_op('DUP') -> ?DUP ;
|
||||
m_to_op('DUPA') -> ?DUPA ;
|
||||
m_to_op('POP') -> ?POP ;
|
||||
m_to_op('STORE') -> ?STORE ;
|
||||
m_to_op('ADD') -> ?ADD ;
|
||||
m_to_op('MUL') -> ?MUL ;
|
||||
m_to_op('SUB') -> ?SUB ;
|
||||
m_to_op('DIV') -> ?DIV ;
|
||||
m_to_op('MOD') -> ?MOD ;
|
||||
m_to_op('POW') -> ?POW ;
|
||||
m_to_op('LT') -> ?LT ;
|
||||
m_to_op('GT') -> ?GT ;
|
||||
m_to_op('EQ') -> ?EQ ;
|
||||
m_to_op('ELT') -> ?ELT ;
|
||||
m_to_op('EGT') -> ?EGT ;
|
||||
m_to_op('NEQ') -> ?NEQ ;
|
||||
m_to_op('AND') -> ?AND ;
|
||||
m_to_op('OR') -> ?OR ;
|
||||
m_to_op('NOT') -> ?NOT ;
|
||||
m_to_op('TUPLE') -> ?TUPLE ;
|
||||
m_to_op('ELEMENT') -> ?ELEMENT ;
|
||||
m_to_op('MAP_EMPTY') -> ?MAP_EMPTY ;
|
||||
m_to_op('MAP_LOOKUP') -> ?MAP_LOOKUP ;
|
||||
m_to_op('MAP_UPDATE') -> ?MAP_UPDATE ;
|
||||
m_to_op('MAP_DELETE') -> ?MAP_DELETE ;
|
||||
m_to_op('MAP_MEMBER') -> ?MAP_MEMBER ;
|
||||
m_to_op('MAP_FROM_LIST') -> ?MAP_FROM_LIST ;
|
||||
m_to_op('NIL') -> ?NIL ;
|
||||
m_to_op('IS_NIL') -> ?IS_NIL ;
|
||||
m_to_op('CONS') -> ?CONS ;
|
||||
m_to_op('HD') -> ?HD ;
|
||||
m_to_op('TL') -> ?TL ;
|
||||
m_to_op('LENGTH') -> ?LENGTH ;
|
||||
m_to_op('STR_EQ') -> ?STR_EQ ;
|
||||
m_to_op('STR_JOIN') -> ?STR_JOIN ;
|
||||
m_to_op('ADDR_TO_STR') -> ?ADDR_TO_STR ;
|
||||
m_to_op('STR_REVERSE') -> ?STR_REVERSE ;
|
||||
m_to_op('INT_TO_ADDR') -> ?INT_TO_ADDR ;
|
||||
m_to_op('VARIANT') -> ?VARIANT ;
|
||||
m_to_op('VARIANT_TEST') -> ?VARIANT_TEST ;
|
||||
m_to_op('VARIANT_ELEMENT') -> ?VARIANT_ELEMENT ;
|
||||
m_to_op('BITS_NONEA') -> ?BITS_NONEA ;
|
||||
m_to_op('BITS_ALL') -> ?BITS_ALL ;
|
||||
m_to_op('BITS_ALLA') -> ?BITS_ALLA ;
|
||||
m_to_op('BITS_SET') -> ?BITS_SET ;
|
||||
m_to_op('BITS_CLEAR') -> ?BITS_CLEAR ;
|
||||
m_to_op('BITS_TEST') -> ?BITS_TEST ;
|
||||
m_to_op('BITS_SUM') -> ?BITS_SUM ;
|
||||
m_to_op('BITS_OR') -> ?BITS_OR ;
|
||||
m_to_op('BITS_AND') -> ?BITS_AND ;
|
||||
m_to_op('BITS_DIFF') -> ?BITS_DIFF ;
|
||||
m_to_op('ADDRESS') -> ?ADDRESS ;
|
||||
m_to_op('BALANCE') -> ?BALANCE ;
|
||||
m_to_op('ORIGIN') -> ?ORIGIN ;
|
||||
m_to_op('CALLER') -> ?CALLER ;
|
||||
m_to_op('GASPRICE') -> ?GASPRICE ;
|
||||
m_to_op('BLOCKHASH') -> ?BLOCKHASH ;
|
||||
m_to_op('BENEFICIARY') -> ?BENEFICIARY ;
|
||||
m_to_op('TIMESTAMP') -> ?TIMESTAMP ;
|
||||
m_to_op('NUMBER') -> ?NUMBER ;
|
||||
m_to_op('DIFFICULTY') -> ?DIFFICULTY ;
|
||||
m_to_op('GASLIMIT') -> ?GASLIMIT ;
|
||||
m_to_op('GAS') -> ?GAS ;
|
||||
m_to_op('LOG0') -> ?LOG0 ;
|
||||
m_to_op('LOG1') -> ?LOG1 ;
|
||||
m_to_op('LOG2') -> ?LOG2 ;
|
||||
m_to_op('LOG3') -> ?LOG3 ;
|
||||
m_to_op('LOG4') -> ?LOG4 ;
|
||||
m_to_op('ABORT') -> ?ABORT ;
|
||||
m_to_op('EXIT') -> ?EXIT ;
|
||||
m_to_op('DEACTIVATE') -> ?DEACTIVATE ;
|
||||
m_to_op('INC') -> ?INC ;
|
||||
m_to_op('DEC') -> ?DEC ;
|
||||
m_to_op('INCA') -> ?INCA ;
|
||||
m_to_op('DECA') -> ?DECA ;
|
||||
m_to_op('INT_TO_STR') -> ?INT_TO_STR ;
|
||||
m_to_op('SPEND') -> ?SPEND ;
|
||||
m_to_op('ORACLE_REGISTER') -> ?ORACLE_REGISTER ;
|
||||
m_to_op('ORACLE_QUERY') -> ?ORACLE_QUERY ;
|
||||
m_to_op('ORACLE_RESPOND') -> ?ORACLE_RESPOND ;
|
||||
m_to_op('ORACLE_EXTEND') -> ?ORACLE_EXTEND ;
|
||||
m_to_op('ORACLE_GET_ANSWER') -> ?ORACLE_GET_ANSWER ;
|
||||
m_to_op('ORACLE_GET_QUESTION') -> ?ORACLE_GET_QUESTION ;
|
||||
m_to_op('ORACLE_QUERY_FEE') -> ?ORACLE_QUERY_FEE ;
|
||||
m_to_op('AENS_RESOLVE') -> ?AENS_RESOLVE ;
|
||||
m_to_op('AENS_PRECLAIM') -> ?AENS_PRECLAIM ;
|
||||
m_to_op('AENS_CLAIM') -> ?AENS_CLAIM ;
|
||||
m_to_op('AENS_UPDATE') -> ?AENS_UPDATE ;
|
||||
m_to_op('AENS_TRANSFER') -> ?AENS_TRANSFER ;
|
||||
m_to_op('AENS_REVOKE') -> ?AENS_REVOKE ;
|
||||
m_to_op('ECVERIFY') -> ?ECVERIFY ;
|
||||
m_to_op('SHA3') -> ?SHA3 ;
|
||||
m_to_op('SHA256') -> ?SHA256 ;
|
||||
m_to_op('BLAKE2B') -> ?BLAKE2B ;
|
||||
m_to_op('RETURNR') -> ?RETURNR ;
|
||||
m_to_op('MAP_LOOKUPD') -> ?MAP_LOOKUPD ;
|
||||
m_to_op('SWITCH_V2') -> ?SWITCH_V2 ;
|
||||
m_to_op('SWITCH_V3') -> ?SWITCH_V3 ;
|
||||
m_to_op('SWITCH_VN') -> ?SWITCH_VN ;
|
||||
m_to_op('FUNCTION') -> ?FUNCTION ;
|
||||
m_to_op('EXTEND') -> ?EXTEND.
|
||||
|
||||
|
||||
|
||||
args(?NOP) -> 0;
|
||||
args(?RETURN) -> 0;
|
||||
args(?INCA) -> 0;
|
||||
args(?DECA) -> 0;
|
||||
args(?DUPA) -> 0;
|
||||
args(?BITS_NONEA) -> 0;
|
||||
args(?BITS_ALLA) -> 0;
|
||||
|
||||
args(?INC) -> 1;
|
||||
args(?DEC) -> 1;
|
||||
args(?RETURNR) -> 1;
|
||||
args(?PUSH) -> 1;
|
||||
args(?JUMP) -> 1;
|
||||
args(?CALL) -> 1;
|
||||
args(?CALL_T) -> 1;
|
||||
args(?TUPLE) -> 1;
|
||||
args(?MAP_EMPTY) -> 1;
|
||||
args(?DUP) -> 1;
|
||||
args(?POP) -> 1;
|
||||
args(?NIL) -> 1;
|
||||
args(?BITS_NONE) -> 1;
|
||||
args(?BITS_ALL) -> 1;
|
||||
args(?ADDRESS) -> 1;
|
||||
args(?BALANCE) -> 1;
|
||||
args(?ORIGIN) -> 1;
|
||||
args(?CALLER) -> 1;
|
||||
args(?GASPRICE) -> 1;
|
||||
args(?BLOCKHASH) -> 1;
|
||||
args(?BENEFICIARY) -> 1;
|
||||
args(?TIMESTAMP) -> 1;
|
||||
args(?NUMBER) -> 1;
|
||||
args(?DIFFICULTY)-> 1;
|
||||
args(?GASLIMIT) -> 1;
|
||||
args(?GAS) -> 1;
|
||||
args(?ABORT) -> 1;
|
||||
args(?EXIT) -> 1;
|
||||
|
||||
args(?JUMPIF) -> 2;
|
||||
args(?CALL_R) -> 2;
|
||||
args(?CALL_TR) -> 2;
|
||||
args(?HD) -> 2;
|
||||
args(?TL) -> 2;
|
||||
args(?NOT) -> 2;
|
||||
args(?STORE) -> 2;
|
||||
args(?LENGTH) -> 2;
|
||||
args(?IS_NIL) -> 2;
|
||||
args(?BITS_SUM) -> 2;
|
||||
args(?BITS_ALL_N) -> 2;
|
||||
args(?ADDR_TO_STR) -> 2;
|
||||
args(?STR_REVERSE) -> 2;
|
||||
args(?INT_TO_ADDR) -> 2;
|
||||
args(?MAP_FROM_LIST) -> 2;
|
||||
|
||||
|
||||
args(?ADD) -> 3;
|
||||
args(?SUB) -> 3;
|
||||
args(?MUL) -> 3;
|
||||
args(?DIV) -> 3;
|
||||
args(?MOD) -> 3;
|
||||
args(?POW) -> 3;
|
||||
args(?AND) -> 3;
|
||||
args(?OR) -> 3;
|
||||
args(?LT) -> 3;
|
||||
args(?GT) -> 3;
|
||||
args(?EGT) -> 3;
|
||||
args(?ELT) -> 3;
|
||||
args(?EQ) -> 3;
|
||||
args(?NEQ) -> 3;
|
||||
args(?CONS) -> 3;
|
||||
args(?STR_EQ) -> 3;
|
||||
args(?STR_JOIN) -> 3;
|
||||
args(?MAP_MEMBER) -> 3;
|
||||
args(?MAP_LOOKUP) -> 3;
|
||||
args(?MAP_DELETE) -> 3;
|
||||
args(?BITS_OR) -> 3;
|
||||
args(?BITS_AND) -> 3;
|
||||
args(?BITS_SET) -> 3;
|
||||
args(?BITS_DIFF) -> 3;
|
||||
args(?BITS_TEST) -> 3;
|
||||
args(?BITS_CLEAR) -> 3;
|
||||
args(?VARIANT_TEST) -> 3;
|
||||
args(?VARIANT_ELEMENT) -> 3;
|
||||
args(?INT_TO_STR) -> 3;
|
||||
args(?SWITCH_V2) -> 3;
|
||||
|
||||
args(?SWITCH_V3) -> 4;
|
||||
args(?ELEMENT) -> 4;
|
||||
args(?VARIANT) -> 4;
|
||||
args(?MAP_UPDATE) -> 4;
|
||||
args(?MAP_LOOKUPD) -> 4;
|
||||
|
||||
args(?SWITCH_VN) -> 2;
|
||||
|
||||
args(_) -> 0. %% TODO do not allow this
|
||||
|
||||
end_bb(?RETURN) -> true;
|
||||
end_bb(?RETURNR) -> true;
|
||||
end_bb(?JUMP) -> true;
|
||||
end_bb(?JUMPIF) -> true;
|
||||
end_bb(?CALL) -> true;
|
||||
end_bb(?CALL_T) -> true;
|
||||
end_bb(?CALL_R) -> true;
|
||||
end_bb(?CALL_TR) -> true;
|
||||
end_bb(?SWITCH_V2) -> true;
|
||||
end_bb(?SWITCH_V3) -> true;
|
||||
end_bb(?SWITCH_VN) -> true;
|
||||
end_bb(?ABORT) -> true;
|
||||
end_bb(?EXIT) -> true;
|
||||
|
||||
end_bb(_) -> false.
|
@ -1,91 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Implementation of the Recursive Length Prefix.
|
||||
%%%
|
||||
%%% https://github.com/ethereum/wiki/wiki/RLP
|
||||
%%%
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeb_rlp).
|
||||
-export([ decode/1
|
||||
, decode_one/1
|
||||
, encode/1
|
||||
]).
|
||||
|
||||
-export_type([ encodable/0
|
||||
, encoded/0
|
||||
]).
|
||||
|
||||
-type encodable() :: [encodable()] | binary().
|
||||
-type encoded() :: <<_:8, _:_*8>>.
|
||||
|
||||
-define(UNTAGGED_SIZE_LIMIT , 55).
|
||||
-define(UNTAGGED_LIMIT , 127).
|
||||
-define(BYTE_ARRAY_OFFSET , 128).
|
||||
-define(LIST_OFFSET , 192).
|
||||
|
||||
|
||||
-spec encode(encodable()) -> encoded().
|
||||
encode(X) ->
|
||||
encode(X, []).
|
||||
|
||||
encode(<<B>> = X,_Opts) when B =< ?UNTAGGED_LIMIT ->
|
||||
%% An untagged value
|
||||
X;
|
||||
encode(X,_Opts) when is_binary(X) ->
|
||||
%% Byte array
|
||||
add_size(?BYTE_ARRAY_OFFSET, X);
|
||||
encode(L, Opts) when is_list(L) ->
|
||||
%% Lists items are encoded and concatenated
|
||||
ByteArray = << << (encode(X, Opts))/binary >> || X <- L >>,
|
||||
add_size(?LIST_OFFSET, ByteArray).
|
||||
|
||||
add_size(Offset, X) when byte_size(X) =< ?UNTAGGED_SIZE_LIMIT ->
|
||||
%% The size fits in one tagged byte
|
||||
<<(Offset + byte_size(X)), X/binary>>;
|
||||
add_size(Offset, X) when is_binary(X) ->
|
||||
%% The size itself needs to be encoded as a byte array
|
||||
%% Add the tagged size of the size byte array
|
||||
SizeBin = binary:encode_unsigned(byte_size(X)),
|
||||
TaggedSize = ?UNTAGGED_SIZE_LIMIT + Offset + byte_size(SizeBin),
|
||||
true = (TaggedSize < 256 ), %% Assert
|
||||
<<TaggedSize, SizeBin/binary, X/binary>>.
|
||||
|
||||
-spec decode(encoded()) -> encodable().
|
||||
decode(Bin) when is_binary(Bin), byte_size(Bin) > 0 ->
|
||||
case decode_one(Bin) of
|
||||
{X, <<>>} -> X;
|
||||
{X, Left} -> error({trailing, X, Bin, Left})
|
||||
end.
|
||||
|
||||
decode_one(<<X, B/binary>>) when X =< ?UNTAGGED_LIMIT ->
|
||||
%% Untagged value
|
||||
{<<X>>, B};
|
||||
decode_one(<<L, _/binary>> = B) when L < ?LIST_OFFSET ->
|
||||
%% Byte array
|
||||
{Size, Rest} = decode_size(B, ?BYTE_ARRAY_OFFSET),
|
||||
<<X:Size/binary, Tail/binary>> = Rest,
|
||||
{X, Tail};
|
||||
decode_one(<<_/binary>> = B) ->
|
||||
%% List
|
||||
{Size, Rest} = decode_size(B, ?LIST_OFFSET),
|
||||
<<X:Size/binary, Tail/binary>> = Rest,
|
||||
{decode_list(X), Tail}.
|
||||
|
||||
decode_size(<<L, B/binary>>, Offset) when L =< Offset + ?UNTAGGED_SIZE_LIMIT->
|
||||
%% One byte tagged size.
|
||||
{L - Offset, B};
|
||||
decode_size(<<_, 0, _/binary>>,_Offset) ->
|
||||
error(leading_zeroes_in_size);
|
||||
decode_size(<<L, B/binary>>, Offset) ->
|
||||
%% Actual size is in a byte array.
|
||||
BinSize = L - Offset - ?UNTAGGED_SIZE_LIMIT,
|
||||
<<Size:BinSize/unit:8, Rest/binary>> = B,
|
||||
{Size, Rest}.
|
||||
|
||||
decode_list(<<>>) -> [];
|
||||
decode_list(B) ->
|
||||
{Element, Rest} = decode_one(B),
|
||||
[Element|decode_list(Rest)].
|
197
src/gmb_aevm_abi.erl
Normal file
197
src/gmb_aevm_abi.erl
Normal file
@ -0,0 +1,197 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @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.
|
||||
|
31
src/gmb_aevm_data.erl
Normal file
31
src/gmb_aevm_data.erl
Normal file
@ -0,0 +1,31 @@
|
||||
-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,4 +1,5 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc Assembler for aevm machine code.
|
||||
%%%
|
||||
@ -25,17 +26,19 @@
|
||||
%%% 4. labels as descibed above.
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Updated : 22 Jan 2025
|
||||
%%% Created : 21 Dec 2017
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeb_asm).
|
||||
-module(gmb_asm).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-export([ file/2
|
||||
, pp/1
|
||||
, to_hexstring/1
|
||||
]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include_lib("gmbytecode/include/gmb_opcodes.hrl").
|
||||
|
||||
|
||||
pp(Asm) ->
|
||||
@ -47,10 +50,10 @@ format(Asm) -> format(Asm, 0).
|
||||
format([{comment, Comment} | Rest], Address) ->
|
||||
";; " ++ Comment ++ "\n" ++ format(Rest, Address);
|
||||
format([Mnemonic | Rest], Address) ->
|
||||
Op = aeb_opcodes:m_to_op(Mnemonic),
|
||||
Op = gmb_opcodes:m_to_op(Mnemonic),
|
||||
case (Op >= ?PUSH1) andalso (Op =< ?PUSH32) of
|
||||
true ->
|
||||
Arity = aeb_opcodes:op_size(Op) - 1,
|
||||
Arity = gmb_opcodes:op_size(Op) - 1,
|
||||
{Args, Code} = get_args(Arity, Rest),
|
||||
" " ++ atom_to_list(Mnemonic)
|
||||
++ " " ++ Args ++ "\n"
|
||||
@ -72,7 +75,7 @@ get_args(N, [Arg|Code]) ->
|
||||
|
||||
file(Filename, Options) ->
|
||||
{ok, File} = file:read_file(Filename),
|
||||
{ok, Tokens, _} = aeb_asm_scan:scan(binary_to_list(File)),
|
||||
{ok, Tokens, _} = gmb_asm_scan:scan(binary_to_list(File)),
|
||||
|
||||
case proplists:lookup(pp_tokens, Options) of
|
||||
{pp_tokens, true} ->
|
||||
@ -100,8 +103,8 @@ to_hexstring(ByteList) ->
|
||||
|
||||
|
||||
to_bytecode([{mnemonic,_line, Op}|Rest], Address, Env, Code, Opts) ->
|
||||
OpCode = aeb_opcodes:m_to_op(Op),
|
||||
OpSize = aeb_opcodes:op_size(OpCode),
|
||||
OpCode = gmb_opcodes:m_to_op(Op),
|
||||
OpSize = gmb_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);
|
||||
@ -138,7 +141,7 @@ resolve_refs([Op | Rest], Env, Code) ->
|
||||
resolve_refs([],_Env, Code) -> Code.
|
||||
|
||||
expand_args([OP, Arg | Rest]) when OP >= ?PUSH1 andalso OP =< ?PUSH32 ->
|
||||
BitSize = (aeb_opcodes:op_size(OP) - 1) * 8,
|
||||
BitSize = (gmb_opcodes:op_size(OP) - 1) * 8,
|
||||
Bin = << << X:BitSize>> || X <- [Arg] >>,
|
||||
ArgByteList = binary_to_list(Bin),
|
||||
[OP | ArgByteList] ++ expand_args(Rest);
|
@ -1,5 +1,6 @@
|
||||
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc Assembler lexer.
|
||||
%%%
|
||||
@ -195,7 +196,7 @@ Erlang code.
|
||||
|
||||
-ignore_xref([format_error/1, string/2, token/2, token/3, tokens/2, tokens/3]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include_lib("gmbytecode/include/gmb_opcodes.hrl").
|
||||
|
||||
|
||||
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
|
@ -1,19 +1,22 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Prettyprint aevm machine code
|
||||
%%% @end
|
||||
%%% Created : 2 Oct 2017
|
||||
%%% Updated : 22 Jan 2025
|
||||
%%% Created : 02 Oct 2017
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeb_disassemble).
|
||||
-module(gmb_disassemble).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-export([ pp/1,
|
||||
format/2,
|
||||
format_address/1
|
||||
]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include_lib("gmbytecode/include/gmb_opcodes.hrl").
|
||||
|
||||
|
||||
pp(Binary) ->
|
||||
@ -26,37 +29,37 @@ format(Binary, ErrFormatFun) ->
|
||||
pp(Address, [Op|Ops], Assembly, ErrFormatFun) ->
|
||||
case Op of
|
||||
X when (X >= ?STOP) andalso (X =< ?SIGNEXTEND) ->
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?LT) andalso (X =< ?BYTE) ->
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?SHA3) andalso (X =< ?SHA3) ->
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?ADDRESS) andalso (X =< ?EXTCODECOPY) ->
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?BLOCKHASH) andalso (X =< ?GASLIMIT) ->
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?POP) andalso (X =< ?JUMPDEST) ->
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, gmb_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, aeb_opcodes:mnemonic(Op), [{Arg,8*Bytes}]),
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), [{Arg,8*Bytes}]),
|
||||
next(Address+Bytes, NextOps, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?DUP1) andalso (X =< ?LOG4) ->
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?CREATE) andalso (X =< ?DELEGATECALL) ->
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
X when (X >= ?INVALID) andalso (X =< ?SUICIDE) ->
|
||||
Instr = pp_instruction(Address, aeb_opcodes:mnemonic(Op), []),
|
||||
Instr = pp_instruction(Address, gmb_opcodes:mnemonic(Op), []),
|
||||
next(Address, Ops, Instr, Assembly, ErrFormatFun);
|
||||
_ ->
|
||||
ErrFormatFun("unhandled op ~p at ~p",[Op, Address]),
|
82
src/gmb_fate_abi.erl
Normal file
82
src/gmb_fate_abi.erl
Normal file
@ -0,0 +1,82 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @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.
|
522
src/gmb_fate_asm.erl
Normal file
522
src/gmb_fate_asm.erl
Normal file
@ -0,0 +1,522 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @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).
|
148
src/gmb_fate_asm_scan.template
Normal file
148
src/gmb_fate_asm_scan.template
Normal file
@ -0,0 +1,148 @@
|
||||
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Handling FATE code.
|
||||
%%% @end
|
||||
%%% ###REPLACEWITHNOTE###
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
Definitions.
|
||||
DIGIT = [0-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}+
|
||||
WS = [\000-\s]
|
||||
ID = {LOWER}[a-zA-Z0-9_]*
|
||||
STRING = "[^"]*"
|
||||
BITS = (\!)?\<[\s01]*\>
|
||||
|
||||
Rules.
|
||||
arg{INT} : {token, {arg, TokenLine, parse_arg(TokenChars)}}.
|
||||
var{INT} : {token, {var, TokenLine, parse_var(TokenChars)}}.
|
||||
a : {token, {stack, TokenLine}}.
|
||||
|
||||
true : {token, {boolean, TokenLine, true}}.
|
||||
false : {token, {boolean, TokenLine, false}}.
|
||||
|
||||
%% ###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} :
|
||||
{token, {int, TokenLine, parse_hex(TokenChars)}}.
|
||||
{INT} :
|
||||
{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 ".
|
||||
{STRING} :
|
||||
{token, {string, TokenLine, unicode:characters_to_binary(
|
||||
lists:sublist(TokenChars, 2, length(TokenChars) - 2))}}.
|
||||
{BITS} :
|
||||
{token, {bits, TokenLine, bits(TokenChars)}}.
|
||||
|
||||
|
||||
%% Symbols
|
||||
\-\> : {token, {to, TokenLine}}.
|
||||
\: : {token, {to, TokenLine}}.
|
||||
|
||||
\=\> : {token, {arrow, TokenLine}}.
|
||||
\(\| : {token, {start_variant, TokenLine}}.
|
||||
\|\) : {token, {end_variant, TokenLine}}.
|
||||
|
||||
, : {token, {',', TokenLine}}.
|
||||
\( : {token, {'(', TokenLine}}.
|
||||
\) : {token, {')', TokenLine}}.
|
||||
\[ : {token, {'[', TokenLine}}.
|
||||
\] : {token, {']', TokenLine}}.
|
||||
\{ : {token, {'{', TokenLine}}.
|
||||
\} : {token, {'}', TokenLine}}.
|
||||
\| : {token, {'|', TokenLine}}.
|
||||
\' : {token, {typerep, TokenLine}}.
|
||||
|
||||
;;.* :
|
||||
{token, {comment, TokenLine, drop_prefix($;, TokenChars)}}.
|
||||
|
||||
\. : skip_token.
|
||||
|
||||
|
||||
%% Whitespace ignore
|
||||
{WS} : skip_token.
|
||||
|
||||
%% Comments (TODO: nested comments)
|
||||
|
||||
|
||||
. : {error, "Unexpected token: " ++ TokenChars}.
|
||||
|
||||
Erlang code.
|
||||
|
||||
-export([scan/1]).
|
||||
|
||||
-dialyzer({nowarn_function, yyrev/2}).
|
||||
|
||||
-ignore_xref([format_error/1, string/2, token/2, token/3, tokens/2, tokens/3]).
|
||||
|
||||
-include_lib("gmbytecode/include/gmb_fate_opcodes.hrl").
|
||||
|
||||
|
||||
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
|
||||
|
||||
parse_int(Chars) -> list_to_integer(Chars).
|
||||
|
||||
parse_arg("arg" ++ N) -> list_to_integer(N).
|
||||
parse_var("var" ++ 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.
|
||||
|
||||
scan(S) ->
|
||||
string(S).
|
||||
|
||||
drop_prefix(C, [C|Rest]) ->
|
||||
drop_prefix(C, Rest);
|
||||
drop_prefix(_, Tail) -> Tail.
|
||||
|
||||
bits([$!, $< | Rest]) ->
|
||||
bits(Rest, -1);
|
||||
bits([$< | Rest]) ->
|
||||
bits(Rest, 0).
|
||||
|
||||
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).
|
448
src/gmb_fate_code.erl
Normal file
448
src/gmb_fate_code.erl
Normal file
@ -0,0 +1,448 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @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).
|
396
src/gmb_fate_data.erl
Normal file
396
src/gmb_fate_data.erl
Normal file
@ -0,0 +1,396 @@
|
||||
%% 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.
|
||||
|
519
src/gmb_fate_encoding.erl
Normal file
519
src/gmb_fate_encoding.erl
Normal file
@ -0,0 +1,519 @@
|
||||
%% 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.
|
133
src/gmb_fate_generate_docs.erl
Normal file
133
src/gmb_fate_generate_docs.erl
Normal file
@ -0,0 +1,133 @@
|
||||
-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]
|
||||
).
|
800
src/gmb_fate_generate_ops.erl
Normal file
800
src/gmb_fate_generate_ops.erl
Normal file
@ -0,0 +1,800 @@
|
||||
-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.
|
||||
|
220
src/gmb_fate_maps.erl
Normal file
220
src/gmb_fate_maps.erl
Normal file
@ -0,0 +1,220 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @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
Normal file
332
src/gmb_heap.erl
Normal file
@ -0,0 +1,332 @@
|
||||
-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.
|
||||
|
||||
|
||||
|
||||
|
22
src/gmb_memory.erl
Normal file
22
src/gmb_memory.erl
Normal file
@ -0,0 +1,22 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @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,12 +1,15 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Opcodes
|
||||
%%% @end
|
||||
%%% Created : 2 Oct 2017
|
||||
%%% Updated : 22 Jan 2025
|
||||
%%% Created : 02 Oct 2017
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeb_opcodes).
|
||||
-module(gmb_opcodes).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-export([ dup/1
|
||||
, mnemonic/1
|
||||
@ -17,7 +20,7 @@
|
||||
, swap/1
|
||||
]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include_lib("gmbytecode/include/gmb_opcodes.hrl").
|
||||
|
||||
|
||||
%%====================================================================
|
||||
@ -51,6 +54,7 @@ opcode(?SHL) -> ?SHL;
|
||||
opcode(?SHR) -> ?SHR;
|
||||
opcode(?SAR) -> ?SAR;
|
||||
opcode(?SHA3) -> ?SHA3;
|
||||
opcode(?CREATOR) -> ?CREATOR;
|
||||
opcode(?ADDRESS) -> ?ADDRESS;
|
||||
opcode(?BALANCE) -> ?BALANCE;
|
||||
opcode(?ORIGIN) -> ?ORIGIN;
|
||||
@ -191,6 +195,7 @@ mnemonic(?SHL) -> 'SHL' ;
|
||||
mnemonic(?SHR) -> 'SHR' ;
|
||||
mnemonic(?SAR) -> 'SAR' ;
|
||||
mnemonic(?SHA3) -> 'SHA3' ;
|
||||
mnemonic(?CREATOR) -> 'CREATOR' ;
|
||||
mnemonic(?ADDRESS) -> 'ADDRESS' ;
|
||||
mnemonic(?BALANCE) -> 'BALANCE' ;
|
||||
mnemonic(?ORIGIN) -> 'ORIGIN' ;
|
||||
@ -332,6 +337,7 @@ 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,18 +1,21 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Handle interaction with the aeternity chain
|
||||
%%% Handle interaction with the gmternity chain
|
||||
%%% through calls to AEternity primitive operations at address 0.
|
||||
%%% @end
|
||||
%%% Updated : 22 Jan 2025
|
||||
%%% Created : 18 Dec 2018
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeb_primops).
|
||||
-module(gmb_primops).
|
||||
-vsn("3.4.1").
|
||||
-export([ is_local_primop_op/1
|
||||
, op_needs_type_check/1
|
||||
]).
|
||||
|
||||
-include("aeb_opcodes.hrl").
|
||||
-include("gmb_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,10 +1,12 @@
|
||||
{application, aebytecode,
|
||||
[{description, "Bytecode definitions, serialization and deserialization for aeternity."},
|
||||
{vsn, "2.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,[]},
|
@ -1,4 +1,5 @@
|
||||
-module(aefateasm).
|
||||
-module(gmfateasm).
|
||||
-vsn("3.4.1").
|
||||
|
||||
-export([main/1]).
|
||||
|
||||
@ -9,7 +10,7 @@
|
||||
, {outfile, $o, "out", string, "Output file (experimental)"} ]).
|
||||
|
||||
usage() ->
|
||||
getopt:usage(?OPT_SPEC, "aefateasm").
|
||||
getopt:usage(?OPT_SPEC, "gmfateasm").
|
||||
|
||||
main(Args) ->
|
||||
case getopt:parse(?OPT_SPEC, Args) of
|
||||
@ -43,8 +44,8 @@ assemble(File, Opts) ->
|
||||
Verbose = proplists:get_value(verbose, Opts, false),
|
||||
case proplists:get_value(outfile, Opts, undefined) of
|
||||
undefined ->
|
||||
Asm = aeb_fate_asm:read_file(File),
|
||||
{Env, BC} = aeb_fate_asm:asm_to_bytecode(Asm, Opts),
|
||||
Asm = gmb_fate_asm:read_file(File),
|
||||
{Env, BC} = gmb_fate_asm:asm_to_bytecode(Asm, Opts),
|
||||
case Verbose of
|
||||
true ->
|
||||
io:format("Env: ~0p~n", [Env]);
|
||||
@ -52,6 +53,6 @@ assemble(File, Opts) ->
|
||||
end,
|
||||
io:format("Code: ~0p~n", [BC]);
|
||||
OutFile ->
|
||||
aeb_fate_asm:assemble_file(File, OutFile, Opts)
|
||||
gmb_fate_asm:assemble_file(File, OutFile, Opts)
|
||||
end.
|
||||
|
@ -1,82 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @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_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">>)})
|
||||
|
||||
].
|
@ -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(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 => #{}}, #{})).
|
||||
;; 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 => #{}}, #{})).
|
||||
|
||||
;; Test the code from the shell with tracing.
|
||||
;; 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})).
|
||||
;; 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})).
|
||||
|
||||
|
||||
;; Test the code from the shell with tracing.
|
||||
;; 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})).
|
||||
;; 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})).
|
||||
|
||||
;; 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
|
||||
|
||||
;; aeb_aefa:file("../../../../test/asm_code/identity.fate", []).
|
||||
;; gmb_gmfa:file("../../../../test/asm_code/identity.fate", []).
|
||||
|
83
test/asm_code/immediates.fate
Normal file
83
test/asm_code/immediates.fate
Normal file
@ -0,0 +1,83 @@
|
||||
;; CONTRACT immediates
|
||||
|
||||
FUNCTION integer() : integer
|
||||
RETURNR 42
|
||||
|
||||
FUNCTION neg_integer() : integer
|
||||
RETURNR -2374683271468723648732648736498712634876147
|
||||
|
||||
FUNCTION hex_integer() : integer
|
||||
RETURNR 0x0deadbeef0
|
||||
|
||||
FUNCTION bool() : boolean
|
||||
RETURNR true
|
||||
|
||||
FUNCTION bool_f() : boolean
|
||||
RETURNR false
|
||||
|
||||
FUNCTION string() : string
|
||||
RETURNR "Hello"
|
||||
|
||||
FUNCTION map() : {map, integer, boolean}
|
||||
RETURNR {}
|
||||
|
||||
FUNCTION map2() : {map, integer, boolean}
|
||||
RETURNR {1 => true}
|
||||
|
||||
FUNCTION map3() : {map, integer, boolean}
|
||||
RETURNR {1 => true,
|
||||
2 => false}
|
||||
|
||||
FUNCTION map4() : {map, integer, {map, string, boolean}}
|
||||
RETURNR {1 => { "foo" => true, "bar" => false},
|
||||
2 => {},
|
||||
3 => { "foo" => false}}
|
||||
|
||||
FUNCTION nil() : {list, integer}
|
||||
RETURNR []
|
||||
|
||||
FUNCTION list1() : {list, integer}
|
||||
RETURNR [1]
|
||||
|
||||
FUNCTION list2() : {list, integer}
|
||||
RETURNR [1, 2]
|
||||
|
||||
|
||||
FUNCTION no_bits() : bits
|
||||
RETURNR <>
|
||||
|
||||
FUNCTION all_bits() : bits
|
||||
RETURNR !<>
|
||||
|
||||
FUNCTION some_bits() : bits
|
||||
RETURNR <101010>
|
||||
|
||||
FUNCTION many_bits() : bits
|
||||
RETURNR !<010101>
|
||||
|
||||
FUNCTION group_bits() : bits
|
||||
RETURNR <1010 1010 0011 1001>
|
||||
|
||||
FUNCTION unit() : {tuple, []}
|
||||
RETURNR ()
|
||||
|
||||
FUNCTION tuple() : {tuple, [integer, boolean, string, {tuple, [integer, integer]}]}
|
||||
RETURNR (42, true, "FooBar", (1, 2))
|
||||
|
||||
|
||||
FUNCTION address() : address
|
||||
RETURNR @ak_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
|
||||
|
||||
FUNCTION contract() : contract
|
||||
RETURNR @ct_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
|
||||
|
||||
FUNCTION channel() : channel
|
||||
RETURNR @ch_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
|
||||
|
||||
;; Option(integer) = NONE | SOME(integer)
|
||||
FUNCTION variant_none() : {variant, [{tuple, []}, {tuple, [integer]}]}
|
||||
RETURNR (| [0,1] | 0 | () |)
|
||||
|
||||
;; Option(integer) = NONE | SOME(integer)
|
||||
FUNCTION variant_some() : {variant, [{tuple, []}, {tuple, [integer]}]}
|
||||
RETURNR (| [0,1] | 1 | (42) |)
|
7
test/asm_code/mapofmap.fate
Normal file
7
test/asm_code/mapofmap.fate
Normal file
@ -0,0 +1,7 @@
|
||||
;; CONTRACT mapofmap
|
||||
FUNCTION map() : {map, integer, {map, string, boolean}}
|
||||
RETURNR {1 => { "foo" => true, "bar" => false},
|
||||
2 => {},
|
||||
3 => { "foo" => false}}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
FUNCTION call(integer):integer
|
||||
STORE var1 arg0
|
||||
PUSH 0
|
||||
CALL write
|
||||
CALL "write"
|
||||
PUSH var1
|
||||
RETURN
|
||||
|
||||
|
12
test/asm_code/meta.fate
Normal file
12
test/asm_code/meta.fate
Normal file
@ -0,0 +1,12 @@
|
||||
;; 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
|
21
test/asm_code/names.fate
Normal file
21
test/asm_code/names.fate
Normal file
@ -0,0 +1,21 @@
|
||||
;; 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,27 +19,23 @@ 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
|
||||
INCA
|
||||
RETURN
|
||||
|
||||
FUNCTION remote_tailcall(integer) : integer
|
||||
PUSH arg0
|
||||
CALL_TR remote add_five
|
||||
;; FUNCTION remote_call(integer) : integer
|
||||
;; PUSH arg0
|
||||
;; CALL_R remote.add_five {tuple, [integer]} integer 0 ;; typereps don't parse
|
||||
;; INCA
|
||||
;; RETURN
|
||||
|
||||
;; Test the code from the shell
|
||||
;; _build/default/rel/aessembler/bin/aessembler console
|
||||
|
||||
;; 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, []).
|
||||
;; 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, []).
|
||||
|
@ -1,13 +1,13 @@
|
||||
FUNCTION make_0tuple():{tuple, []}
|
||||
;; BB : 0
|
||||
TUPLE 0
|
||||
TUPLE a 0
|
||||
RETURN
|
||||
|
||||
FUNCTION make_2tuple(integer, integer):{tuple, [integer, integer]}
|
||||
;; BB : 0
|
||||
PUSH arg0
|
||||
PUSH arg1
|
||||
TUPLE 2
|
||||
TUPLE a 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 5
|
||||
TUPLE a 5
|
||||
RETURN
|
||||
|
||||
FUNCTION element1(integer, integer): integer
|
||||
;; BB : 0
|
||||
PUSH arg0
|
||||
PUSH arg1
|
||||
TUPLE 2
|
||||
ELEMENT integer a 1 a
|
||||
TUPLE a 2
|
||||
ELEMENT a 1 a
|
||||
RETURN
|
||||
|
||||
FUNCTION element({tuple, [integer, integer]}, integer): integer
|
||||
;; BB : 0
|
||||
ELEMENT integer a arg1 arg0
|
||||
ELEMENT a arg1 arg0
|
||||
RETURN
|
||||
|
@ -1,12 +1,13 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc Basic tests for Fate data
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeb_data_test).
|
||||
-module(gmb_data_test).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
format_integer_test() ->
|
||||
"0" = aeb_fate_data:format(0).
|
||||
"0" = gmb_fate_data:format(0).
|
@ -1,19 +1,20 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc Basic tests for Fate serialization
|
||||
%%%
|
||||
%%% To run:
|
||||
%%% TEST=aeb_fate_asm_test rebar3 eunit
|
||||
%%% TEST=gmb_fate_asm_test rebar3 eunit
|
||||
%%%
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeb_fate_asm_test).
|
||||
-module(gmb_fate_asm_test).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
asm_path() ->
|
||||
filename:join(code:lib_dir(aebytecode, test), "asm_code").
|
||||
filename:join(code:lib_dir(gmbytecode, test), "asm_code").
|
||||
|
||||
|
||||
file_path(File) ->
|
||||
@ -21,16 +22,11 @@ file_path(File) ->
|
||||
|
||||
read_file(File) ->
|
||||
FilePath = file_path(File),
|
||||
Asm = aeb_fate_asm:read_file(FilePath),
|
||||
Asm = gmb_fate_asm:read_file(FilePath),
|
||||
Asm.
|
||||
|
||||
assemble(Asm) ->
|
||||
{Env, BC} = aeb_fate_asm:asm_to_bytecode(Asm, []),
|
||||
{Env, BC}.
|
||||
|
||||
disassemble(BC) ->
|
||||
aeb_fate_asm:bytecode_to_fate_code(BC, []).
|
||||
|
||||
gmb_fate_asm:asm_to_bytecode(Asm, []).
|
||||
|
||||
asm_disasm_idenity_test() ->
|
||||
check_roundtrip(identity).
|
||||
@ -50,16 +46,21 @@ sources() ->
|
||||
, "remote"
|
||||
, "test"
|
||||
, "tuple"
|
||||
, "mapofmap"
|
||||
, "immediates"
|
||||
, "names"
|
||||
, "meta"
|
||||
].
|
||||
|
||||
check_roundtrip(File) ->
|
||||
AssemblerCode = read_file(File),
|
||||
{Env, ByteCode} = assemble(AssemblerCode),
|
||||
FateCode = disassemble(ByteCode),
|
||||
DissasmCode = aeb_fate_asm:to_asm(FateCode),
|
||||
io:format("~s~n", [AssemblerCode]),
|
||||
io:format("~s~n", [DissasmCode]),
|
||||
{Env2, ByteCode2} = assemble(DissasmCode),
|
||||
Code1 = aeb_fate_asm:strip(ByteCode),
|
||||
Code2 = aeb_fate_asm:strip(ByteCode2),
|
||||
?assertEqual(Code1, Code2).
|
||||
{_Env, ByteCode} = assemble(AssemblerCode),
|
||||
FateCode = gmb_fate_code:deserialize(ByteCode),
|
||||
DissasmCode = gmb_fate_asm:to_asm(FateCode),
|
||||
{_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).
|
98
test/gmb_serialize_test.erl
Normal file
98
test/gmb_serialize_test.erl
Normal file
@ -0,0 +1,98 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @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>>)
|
||||
].
|
@ -1,4 +1,4 @@
|
||||
-module(aebytecode_SUITE).
|
||||
-module(gmbytecode_SUITE).
|
||||
|
||||
%% common_test exports
|
||||
-export([ all/0 ]).
|
||||
@ -12,8 +12,8 @@ all() ->
|
||||
[ roundtrip_identy ].
|
||||
|
||||
roundtrip_identy(_Cfg) ->
|
||||
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)]),
|
||||
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)]),
|
||||
ok.
|
19
zomp.meta
Normal file
19
zomp.meta
Normal file
@ -0,0 +1,19 @@
|
||||
{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
Executable file
20
zomp_prep
Executable file
@ -0,0 +1,20 @@
|
||||
#! /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