Compare commits

...

146 Commits

Author SHA1 Message Date
skkw 3f85375cb2 adding subname TX 2019-08-30 13:46:29 +02:00
Ulf Norell e7f2be7ce8 Merge pull request #74 from aeternity/fate-map-fixes
Fate map fixes
2019-08-27 10:55:37 +02:00
Ulf Norell c6475fe1c2 Fix typo 2019-08-26 11:57:21 +02:00
Ulf Norell 4e4c20c387 Remember to unfold store map caches too! 2019-08-26 09:06:43 +02:00
Thomas Arts 1d5e5be252 Merge pull request #72 from aeternity/PT-166330348-check-map-keys-fate
Pt 166330348 check map keys fate
2019-08-26 08:37:52 +02:00
Ulf Norell 6efa4a0cb8 Merge pull request #71 from aeternity/missing-case-in-allocate-maps
Add missing case for map tombstones
2019-08-23 17:13:24 +02:00
Thomas Arts 59b7b786ac Update quickcheck/aefate_code_eqc.erl
Co-Authored-By: Hans Svensson <hanssv@gmail.com>
2019-08-23 15:50:39 +02:00
Thomas Arts f31887c2ed Update properties 2019-08-23 15:00:12 +02:00
Thomas Arts d794566363 Fix check for no maps in keys 2019-08-23 14:45:15 +02:00
Ulf Norell 850a5e2c35 Add missing case for map tombstones 2019-08-23 14:30:07 +02:00
Thomas Arts c270c794c3 Fix tests to new datastructure containing attributes 2019-08-23 11:31:54 +02:00
Hans Svensson 10cc127883 Merge pull request #70 from aeternity/PT-167996886-a_proper_ecverify
PT-167996886 a proper ecverify
2019-08-21 11:07:59 +02:00
Hans Svensson 50df849709 VERIFY_SIG_SECP256K1 was too long for code generation 2019-08-21 09:26:01 +02:00
Hans Svensson dfa9b80a3c Change ECVERIFY to VERIFY_SIG and add proper ECVERIFY 2019-08-21 09:25:28 +02:00
Hans Svensson befa1e3ff9 Merge pull request #69 from aeternity/PT-162578406-payable_modifier
PT-162578406 Add payable modifier
2019-08-19 08:54:31 +02:00
Hans Svensson efb4afeafa Add IS_PAYABLE opcode 2019-08-16 09:31:27 +02:00
Hans Svensson e75336486e Track payable (and private) in FATE/AEVM type info
Privateness is only tracked for FATE.
2019-08-16 09:31:27 +02:00
Ulf Norell fdd660a219 Merge pull request #67 from aeternity/PT-167221635-remote-type-check
PT-167221635 remote type check
2019-08-16 09:10:47 +02:00
Tino Breddin 3954bd22da Merge pull request #64 from aeternity/newby/ecrecover
[PT-167805291] Add opcode for ecrecover
2019-08-14 16:08:45 +02:00
Ulf Norell 13211887a3 Update src/aeb_fate_generate_ops.erl
Co-Authored-By: Hans Svensson <hanssv@gmail.com>
2019-08-14 09:32:23 +02:00
Ulf Norell 834ab298d1 typereps are values 2019-08-14 09:27:51 +02:00
Ulf Norell 52781060b2 fix type spec 2019-08-14 09:01:53 +02:00
Ulf Norell 3721fde7e8 Add typereps to remote call instructions 2019-08-14 09:01:53 +02:00
Tino Breddin 23ee7e0ca4 Add missing crypto entries to all_instructions 2019-08-13 16:24:17 +02:00
Ulf Norell af6224cb3b Merge pull request #66 from aeternity/PT-166788647-fate-efficient-maps
PT-166788647 FATE efficient maps
2019-08-13 15:51:13 +02:00
Tino Breddin 197dfd5da1 Let ecrecover only require two parameters 2019-08-13 15:15:03 +02:00
Ulf Norell 087ec31698 Fix bad type spec 2019-08-13 13:17:41 +02:00
Ulf Norell c49140fd5d Fix type and some code cleanup 2019-08-13 11:33:21 +02:00
Ulf Norell 414c45fbf7 Export refcount type 2019-08-13 11:25:54 +02:00
Ulf Norell f5a9be67d9 Remove CALL_TR and CALL_GTR 2019-08-13 09:20:29 +02:00
Ulf Norell 15095a74ab Add has_store_maps function 2019-08-12 14:40:34 +02:00
Ulf Norell 5aee70b8ff Add arity to CALL_R and CALL_GR
and deprecate CALL_TR and CALL_GTR
2019-08-12 14:38:10 +02:00
John Newby 44ec31d958 fixed ordering 2019-08-12 13:52:27 +02:00
John Newby 8fde1e5e24 Added FATE opcode for ecrecover 2019-08-12 13:36:49 +02:00
Ulf Norell 54dcf364e5 Store map reference counting 2019-08-12 10:59:30 +02:00
johnsnewby 7c6a80fef7 Update include/aeb_opcodes.hrl
Co-Authored-By: Hans Svensson <hanssv@gmail.com>
2019-08-12 10:52:47 +02:00
John Newby c0bc71b0b7 Added opcode for ecrecover 2019-08-09 16:32:00 +02:00
Ulf Norell c30bfd7b1c Unfolding store maps 2019-08-09 12:52:31 +02:00
Ulf Norell e184028261 Code for allocating store maps 2019-08-08 12:42:03 +02:00
Ulf Norell 986a7e6734 Add a store map fate value.
A store map is a (contract local) unique id pointing to a map saved in the contract store, plus a cache of updates.
2019-08-08 12:41:42 +02:00
Thomas Arts 2a9035d5ef Merge pull request #63 from aeternity/PT-167126818
Pt 167126818
2019-08-06 13:15:39 +02:00
Thomas Arts 8a50d20a67 Bring tests up-to-date 2019-08-06 13:04:25 +02:00
Thomas Arts 0b0cc38444 Provide API for querying implemented abi version 2019-08-06 12:21:51 +02:00
Tobias Lindahl 17c2a93e72 Merge pull request #62 from aeternity/PT-167164508-auto-generate-offchain-capabilities
Add information on offchain capabilities of operation
2019-07-10 11:10:49 +02:00
Tobias Lindahl e62cedb22c Add information on offchain capabilities of operation 2019-07-10 10:09:17 +02:00
Tobias Lindahl 76ae61b66c Merge pull request #61 from aeternity/PT-166927306-names-as-strings
Pt 166927306 names as strings
2019-06-28 13:48:42 +02:00
Tobias Lindahl edea526f38 Renum ordinals 2019-06-28 11:22:33 +02:00
Tobias Lindahl 487e087287 Use string instead of name hash for transfer and revoke 2019-06-26 15:33:24 +02:00
Tobias Lindahl c63ac888dd Pt 166233700 fate nameservice (#60)
* Introduce AENS instructions in FATE

* Remove name object and fixup some documentation
2019-06-26 13:19:44 +02:00
Hans Svensson 4d12b124f3 Merge pull request #59 from aeternity/PT-164629640-limit_fate_in_auth_context
Add in_auth field to aeb_fate_generate_ops
2019-06-26 12:29:17 +02:00
Hans Svensson 35ce283736 Add in_auth field to aeb_fate_generate_ops 2019-06-26 11:31:10 +02:00
Ulf Norell 677712b0b8 Merge pull request #58 from aeternity/PT-166233670-fate-events
Add FATE oracle check instructions (and others)
2019-06-25 19:56:56 +02:00
Ulf Norell 5171b800cc Add FATE oracle check instructions (and others) 2019-06-25 10:01:22 +02:00
Ulf Norell bf05e14661 Merge pull request #57 from aeternity/bytes-to-x
Add bytes_to_int and bytes_to_str instructions to FATE
2019-06-24 14:24:28 +02:00
Ulf Norell e3a00905de Add bytes_to_int and bytes_to_str instructions to FATE 2019-06-24 10:55:12 +02:00
Hans Svensson 59af12bf34 Merge pull request #56 from aeternity/PT-166788837-bytes
PT-166788837 bytes
2019-06-20 15:37:12 +02:00
Ulf Norell f7f0dfde51 Update and fix quickcheck fuzz test 2019-06-20 15:27:16 +02:00
Ulf Norell 33a1d5f4fb Perform sanity checks both in serialize and deserialize 2019-06-20 14:30:04 +02:00
Ulf Norell eeaf646a86 Allow variants as map keys 2019-06-20 14:30:04 +02:00
Ulf Norell 7fdc7a6cee Update asm tests 2019-06-20 14:30:04 +02:00
Ulf Norell f13ba67a2c Update and clean up quickcheck tests 2019-06-20 14:29:34 +02:00
Ulf Norell f421c1e361 Add bytes type and values
and remove hash and signature
2019-06-20 14:27:18 +02:00
Thomas Arts f91c8fabdd Merge pull request #51 from aeternity/PT-166696064-decode-calldata-fate
Pt 166696064 decode calldata fate
2019-06-20 13:05:44 +02:00
Tobias Lindahl 9dfc5f4f1d Merge pull request #55 from aeternity/PT-166786424-check-oracle-types
Add oracle types to some oracle instructions
2019-06-20 09:21:23 +02:00
Thomas Arts 1fda6912da Fix error 2019-06-19 17:41:47 +02:00
Thomas Arts cb83224c60 Add query to generate QuickCheck data 2019-06-19 17:41:47 +02:00
Thomas Arts 9840b22546 Add decoding function 2019-06-19 17:41:47 +02:00
Thomas Arts e3f843fd91 Do not return types, create_calldata does not need those 2019-06-19 17:41:47 +02:00
Thomas Arts 803ebc0854 Three new opcodes had been added 2019-06-19 17:41:47 +02:00
Hans Svensson 7e96e3baef Merge pull request #54 from aeternity/fix-bad-spec
Fix incorrect type spec
2019-06-19 13:26:54 +02:00
Hans Svensson 768e0d4fbb Fix incorrect type spec 2019-06-19 13:25:05 +02:00
Tobias Lindahl f92e23c955 Add oracle types to some oracle instructions 2019-06-19 12:11:22 +02:00
Tobias Lindahl e321882b98 Pt 166233685 fate oracles (#52)
* Introduce typereps

* Use typereps for registering oracles

* Add TTL to oracle register

* Introduce oracle query object and ORACLE_QUERY operation

* Stub the remaining oracle instructions

* Adapt oracle respond and extend

* Document oracle ops

* Add unit tests for all oracle instructions
2019-06-18 13:56:48 +02:00
Hans Svensson b45509962e Merge pull request #53 from aeternity/fate_abi_fun
Auth.tx_hash + new functions in aeb_fate_abi
2019-06-18 13:54:21 +02:00
Hans Svensson c1fb3a47c7 More functions in aeb_fate_abi 2019-06-18 13:51:06 +02:00
Hans Svensson a0c3a990ed Add function_name_from_function_hash to aeb_fate_abi 2019-06-18 11:59:54 +02:00
Hans Svensson 506f9ca72e Add AUTH_TX_HASH operation 2019-06-11 15:31:16 +02:00
Thomas Arts 7dd9c29cc0 Merge pull request #50 from aeternity/PT-166602172-calldat-for-fate
Add creation of fate calldata
2019-06-11 14:58:40 +02:00
Thomas Arts 242700e084 Add creation of fate calldata 2019-06-11 14:47:38 +02:00
Hans Svensson 29b5ee3e68 Merge pull request #49 from aeternity/fate-crypto-ops
Fate crypto ops + contract_to_address
2019-06-11 11:57:57 +02:00
Hans Svensson 896290ad3b contract_to_address instruction 2019-06-11 09:20:13 +02:00
Ulf Norell 876e8504c8 crypto instructions 2019-06-05 14:21:47 +02:00
Ulf Norell 53a055b90a Merge pull request #48 from aeternity/PT-166407568-polymoprhic-functions
Add serialization of any and type variables
2019-06-05 12:10:50 +02:00
Ulf Norell 409d761b18 Add serialization of any and type variables 2019-06-05 11:13:52 +02:00
Thomas Arts f15315adb7 Merge pull request #44 from aeternity/PT-165173962-fuzz-testing-aefate
Pt 165173962 fuzz testing aefate
2019-06-03 18:37:49 +02:00
Thomas Arts b8b316aae0 Make sorting idempotent 2019-06-03 14:49:13 +02:00
Thomas Arts 985e5358c9 Sorting should be idempotent 2019-06-03 14:49:02 +02:00
Thomas Arts ffebc13d08 Make sort testable 2019-06-03 14:45:08 +02:00
Thomas Arts 3ff4df42ff Make sure arguments not provided are maskes 2#00. 2019-06-03 13:43:49 +02:00
Thomas Arts d6fbc73450 Quicker testing with smaller terms 2019-06-03 13:34:37 +02:00
Thomas Arts 3d6ac9df92 Slightly different property 2019-06-03 11:08:08 +02:00
Thomas Arts e8390e52d1 Bug fix and little different way of writing logic 2019-06-03 11:07:00 +02:00
Erik Stenman 58daf1bb5c Add quick check test for order and handle variants. 2019-05-31 14:42:00 +02:00
Erik Stenman cb8e2b07a4 Use FATE order to sort maps iterator. 2019-05-31 13:52:07 +02:00
Erik Stenman 46f9d34447 Total order for FATE types. 2019-05-31 13:17:50 +02:00
Erik Stenman 942c7fb069 Add first draft of total order for FATE terms. 2019-05-30 12:31:40 +02:00
Erik Stenman 53130fc638 Add FATE_BOOLEAN_VALUE 2019-05-30 12:31:01 +02:00
Thomas Arts 8bf19dc060 Do not generate maps and variants as keys in fate maps 2019-05-29 16:23:45 +02:00
Thomas Arts a5bfdf63d5 Define a specific sorting for key-Value pairs
Sort on key and check: no duplicates and no maps and variants as key
2019-05-29 16:23:43 +02:00
Thomas Arts 41860b041e We should not deserialize function blocks only containing opcodes, but not a function 2019-05-29 16:23:43 +02:00
Thomas Arts 25ef7e7fe3 Remove special cases for NIL and empty MAP 2019-05-29 11:18:41 +02:00
Thomas Arts bcc409f302 serialize and deserialize integers with validity check 2019-05-29 11:17:13 +02:00
Thomas Arts 42719e7000 Fail on deserializing negative zero 2019-05-29 08:52:24 +02:00
Thomas Arts 0d6322c0aa Fix property 2019-05-29 08:31:04 +02:00
Thomas Arts f7a4c40c50 Only decode correctly encoded negative binaries 2019-05-29 08:21:25 +02:00
Thomas Arts ec0af8046a Safer serialization of opcode arguments 2019-05-28 19:09:42 +02:00
Thomas Arts 73c80e1168 Extend model to find {stack, N} errors as argument 2019-05-28 19:09:25 +02:00
Thomas Arts 45ff418699 More dsitintc fault injection 2019-05-28 18:23:39 +02:00
Thomas Arts 032277ae8b model so far
improved model
2019-05-28 18:13:27 +02:00
Thomas Arts 0bb4ac0fea Rough model to fuzz test aeb_fate_code 2019-05-28 18:13:24 +02:00
Thomas Arts 4a90e3b2b4 Add two more opcodes 2019-05-28 18:12:43 +02:00
Thomas Arts 8b7fefc8a9 Extend tests to encoded opcodes 2019-05-28 18:12:41 +02:00
Thomas Arts 6f59ef7a7c Fuzz test aeb_fate_encoding 2019-05-28 18:11:42 +02:00
Thomas Arts 302c1c211d Erik's fix to LONG strings 2019-05-28 18:11:42 +02:00
Thomas Arts 74791cfe52 typo 2019-05-28 18:11:42 +02:00
Thomas Arts 453f68fa39 Serialize only code blocks that have operators in right order 2019-05-28 18:11:42 +02:00
Thomas Arts aa9d2bf893 Do not allow empty code blocks 2019-05-28 18:11:42 +02:00
Thomas Arts 23b98f7d65 Add unit test to trigger eqc property 2019-05-28 18:11:42 +02:00
Thomas Arts 5d7bd73bcb Serialization of functions only succeeds for 4 byte iudentifiers 2019-05-28 18:11:42 +02:00
Thomas Arts 34b9684b6b Deserialize code without functions 2019-05-28 18:11:42 +02:00
Thomas Arts ccbb0ed6c7 Make code easier to test 2019-05-28 18:11:42 +02:00
Ulf Norell f1298870e5 Merge pull request #47 from aeternity/add-target-register-to-tuple
Add target register to TUPLE
2019-05-28 14:17:16 +02:00
Ulf Norell 9cfd369c5d Update tests 2019-05-28 12:54:10 +02:00
Ulf Norell f115feb16d Print state variables as storeN instead of var-N 2019-05-28 11:53:13 +02:00
Tobias Lindahl 241a96ebaa Change the correct function name to not shadow builting guard (#46) 2019-05-28 11:32:52 +02:00
Ulf Norell 880cf573aa Add a target register to TUPLE instruction 2019-05-28 11:24:38 +02:00
Tobias Lindahl 89f5ebc84b Add missing basic instructions (#45)
* Add missing basic instructions

* MAP_SIZE
* MAP_TO_LIST
* STR_LENGTH

* Change name of erlang function to not shadow builtin guard
2019-05-28 11:15:04 +02:00
Erik Stenman e98298cce4 Add accepted types to operator declarations. Check opcode numbering. (#43)
* Add accepted types to operator declarations. 
* Check opcode numbering.
2019-05-24 13:21:49 +02:00
Erik Stenman 0d1899b32a Pt 165352420 dissallow stack n (#42)
* Get rid of redundant arity field from op defs. Reorder and renumber ops. Fix bb_end for abort and exit.

* FATE does not accept arbitrary stack positions, only the accumulator aka stack 0.
2019-05-23 13:40:49 +02:00
Erik Stenman 3e0e289f2f Get rid of redundant arity field from op defs. Reorder and renumber ops. Fix bb_end for abort and exit. (#41) 2019-05-23 13:37:32 +02:00
Tobias Lindahl 11a8997ac7 Pt 166148534 refactor fate code (#40)
* Change names of generated aeb_fate_code -> aeb_fate_ops

* Break out fate code to separate adt module

* Fix documentation of the SPEND op

* More compact implementation of serialization/deserialization

* Changed argument specification order
2019-05-23 08:15:18 +02:00
Tobias Lindahl 2f4e1888c2 Merge pull request #38 from aeternity/PT-165857097-add-gas-and-value-to-calls
Pt 165857097 add gas and value to calls
2019-05-09 14:18:15 +02:00
Tobias Lindahl a9389e4e69 Add the CALL_VALUE instruction 2019-05-09 10:57:18 +02:00
Tobias Lindahl 2d3cede235 Add value to remote calls and the new ops CALL_GR and CALL_GTR 2019-05-09 10:57:18 +02:00
Hans Svensson 08a09b065b Merge pull request #39 from aeternity/PT-165440601-165713319-sophia_addons
Add CREATOR opcode and address check primops
2019-05-09 09:51:40 +02:00
Hans Svensson 5fd076f043 Add CREATOR opcode and address check primops 2019-05-09 09:35:48 +02:00
Ulf Norell 2555868990 Merge pull request #37 from aeternity/fate-compiler
Fate compiler
2019-05-07 13:04:53 +02:00
Tobias Lindahl 7eafbc22ae Merge pull request #36 from aeternity/PT-165760129-fix-object-parsing
Fix object parsing
2019-05-07 12:40:20 +02:00
Tobias Lindahl 3ed0fcbe05 Add test for parsing immediate objects 2019-05-07 11:28:54 +02:00
Tobias Lindahl b6019eb81b Fix object parsing 2019-05-07 11:16:05 +02:00
Ulf Norell 6eab9a32c9 Bump aeserialization dependency 2019-05-07 10:14:23 +02:00
Ulf Norell 91fc56c322 Change local calls to allow dynamic function name 2019-05-07 09:04:24 +02:00
Ulf Norell 1887486d36 Replace STR_EQ by APPEND
STR_EQ is not needed, the regular EQ instruction can handle strings as well. Having
an instruction for list append is quite handy though (would need two passes to do it
tail recursive in FATE assembly)
2019-05-07 09:04:24 +02:00
Ulf Norell bf6741eac4 Format of li should be {immediate, [integer()]} 2019-05-07 08:38:23 +02:00
Ulf Norell 491489ca7d Change ~w to ~p 2019-05-07 08:38:23 +02:00
Thomas Arts 91c4ab5bea Merge pull request #35 from aeternity/PT-property-based-tests
Pt property based tests
2019-05-06 14:51:04 +02:00
32 changed files with 2075 additions and 1081 deletions
+1 -1
View File
@@ -14,8 +14,8 @@ aeb_fate_asm_scan.xrl
_build/
aefateasm
include/aeb_fate_opcodes.hrl
src/aeb_fate_code.erl
src/aeb_fate_opcodes.erl
src/aeb_fate_ops.erl
src/aeb_fate_pp.erl
*.erl~
*.hrl~
+1 -1
View File
@@ -1,4 +1,4 @@
GENERATED_SRC = src/aeb_fate_opcodes.erl src/aeb_fate_code.erl include/aeb_fate_opcodes.hrl src/aeb_fate_asm_scan.xrl src/aeb_fate_pp.erl
GENERATED_SRC = src/aeb_fate_opcodes.erl src/aeb_fate_ops.erl include/aeb_fate_opcodes.hrl src/aeb_fate_asm_scan.xrl src/aeb_fate_pp.erl
GENERATOR_DEPS = ebin/aeb_fate_generate_ops.beam src/aeb_fate_asm_scan.template
REBAR ?= rebar3
+4 -6
View File
@@ -49,7 +49,7 @@ or start with stack followed by an integer
`stack1`
`a`
Immediate values can be of 11 types:
Immediate values can be of 10 types:
1. Integers as decimals: {Digits} or -{Digits}
`42`
@@ -70,8 +70,8 @@ Immediate values can be of 11 types:
2c. Oracle address: @ok_{base58char}+
`@ok_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv`
2d. Name address: @nm_{base58char}+
`@nm_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv`
2d. Oracle query: @oq_{base58char}+
`@oq_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv`
2e. Channel address: @ch_{base58char}+
`@ch_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv`
@@ -104,11 +104,9 @@ Immediate values can be of 11 types:
9. Variants: (| [Arities] | Tag | ( Elements ) |)
`(| [1,3,5,2] | 3 | ( "foo", 12) |)`
10. Hashes: #{base64char}+
10. Bytes: #{base64char}+
`#AQIDCioLFQ==`
11. Signatures: $sg_{base58char}+
Where
+22 -13
View File
@@ -5,30 +5,35 @@
-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_HASH_T, {hash, binary()}).
-define(FATE_SIGNATURE_T, {signature, binary()}).
-define(FATE_BYTES_T(N), {bytes, binary()}).
-define(FATE_CONTRACT_T, {contract, <<_:256>>}).
-define(FATE_ORACLE_T, {oracle, <<_:256>>}).
-define(FATE_NAME_T, {name, <<_: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(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_HASH(X), (is_tuple(X) andalso (hash == element(1, X) andalso is_binary(element(2, X))))).
-define(IS_FATE_SIGNATURE(X), (is_tuple(X) andalso (signature == 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_NAME(X), (is_tuple(X) andalso (name == 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)
@@ -39,31 +44,35 @@
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(FATE_UNIT, {tuple, {}}).
-define(FATE_TUPLE(T), {tuple, T}).
-define(FATE_ADDRESS(A), {address, A}).
-define(FATE_HASH(X), {hash, X}).
-define(FATE_SIGNATURE(S), {signature, S}).
-define(FATE_BYTES(X), {bytes, X}).
-define(FATE_CONTRACT(X), {contract, X}).
-define(FATE_ORACLE(X), {oracle, X}).
-define(FATE_NAME(X), {name, 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_HASH_VALUE(X), (element(2, X))).
-define(FATE_SIGNATURE_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_NAME_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_TRUE, true).
+13 -2
View File
@@ -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).
@@ -173,6 +176,7 @@
-define(PRIM_CALL_AENS_UPDATE, 203).
-define(PRIM_CALL_AENS_TRANSFER, 204).
-define(PRIM_CALL_AENS_REVOKE, 205).
-define(PRIM_CALL_AENS_SUBNAME, 206).
-define(PRIM_CALL_IN_MAP_RANGE(__TTYPE__), (((__TTYPE__) > 299) andalso ((__TTYPE__) < 400))).
-define(PRIM_CALL_MAP_EMPTY, 300).
@@ -183,13 +187,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_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_ECVERIFY_SECP256K1, 410).
-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).
+27
View 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=aeb_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(aeb_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(aefate_code_eqc, prop_opcodes, 200),
?EQC_EUNIT(aefate_code_eqc, prop_serializes, 3000),
?EQC_EUNIT(aefate_code_eqc, prop_fail_serializes, 3000),
?EQC_EUNIT(aefate_code_eqc, prop_fuzz, 3000)
]}.
+3 -1
View File
@@ -21,5 +21,7 @@
quickcheck_test_() ->
{setup, fun() -> eqc:start() end,
[ ?EQC_EUNIT(aefate_eqc, prop_roundtrip, 500),
?EQC_EUNIT(aefate_eqc, prop_format_scan, 2000)
?EQC_EUNIT(aefate_eqc, prop_format_scan, 2000),
?EQC_EUNIT(aefate_eqc, prop_order, 2000),
?EQC_EUNIT(aefate_eqc, prop_fuzz, 2000)
]}.
+3 -1
View File
@@ -21,5 +21,7 @@
quickcheck_test_() ->
{setup, fun() -> eqc:start() end,
[ ?EQC_EUNIT(aefate_type_eqc, prop_roundtrip, 1000),
?EQC_EUNIT(aefate_eqc, prop_serializes, 1000)
?EQC_EUNIT(aefate_eqc, prop_serializes, 1000),
?EQC_EUNIT(aefate_eqc, prop_no_maps_in_keys, 1000),
?EQC_EUNIT(aefate_eqc, prop_idempotent, 1000)
]}.
+167
View 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(aefate_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() -> aeb_fate_code:serialize(FateCode) end),
?WHENFAIL(eqc:format("serialized:\n ~120p~n", [Binary]),
begin
{T1, Decoded} = timer:tc(fun() -> aeb_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 aeb_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), aeb_fate_code:serialize(FateCode)),
?FORALL(FuzzedBin, fuzz(Binary),
try aeb_fate_code:deserialize(FuzzedBin) of
Code ->
?WHENFAIL(eqc:format("Code:\n ~p\n", [Code]),
begin
Bin1 = aeb_fate_code:serialize(Code),
Code1 = aeb_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 = aeb_fate_opcodes:mnemonic(Opcode),
?WHENFAIL(eqc:format("opcode ~p -> ~p", [Opcode, M]),
conjunction([{valid, lists:member(Opcode, valid_opcodes())},
{eq, equals(aeb_fate_opcodes:m_to_op(M), Opcode)}]))
catch
_:_ ->
not lists:member(Opcode, valid_opcodes())
end).
valid_opcodes() ->
lists:seq(0, 16#7f) ++ lists:seq(16#fa, 16#fd).
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(aefate_type_eqc:fate_type(Size div 3)), aefate_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))},
aeb_fate_code:update_annotations(
aeb_fate_code:update_symbols(
aeb_fate_code:update_functions(
aeb_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(), aeb_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 = aeb_fate_opcodes:mnemonic(Op),
Arity = aeb_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 = aeb_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, aefate_type_eqc:fate_type(Size div 3)),
begin
Bin = iolist_to_binary(aeb_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, aefate_eqc:fate_data())).
small_fate_data_key(N) ->
?SIZED(Size, ?LET(Data, aefate_eqc:fate_data(Size div N, []), eqc_symbolic:eval(Data))).
+155 -66
View File
@@ -10,6 +10,7 @@
-module(aefate_eqc).
-include_lib("eqc/include/eqc.hrl").
-include("../include/aeb_fate_data.hrl").
-compile([export_all, nowarn_export_all]).
@@ -23,7 +24,7 @@ prop_roundtrip() ->
end)).
prop_format_scan() ->
?FORALL(FateData, fate_data(),
?FORALL(FateData, fate_data([variant, map]),
?WHENFAIL(eqc:format("Trying to format ~p failed~n", [FateData]),
begin
String = aeb_fate_data:format(FateData),
@@ -32,91 +33,179 @@ prop_format_scan() ->
end)).
prop_serializes() ->
?FORALL(FateDatas, non_empty(?SIZED(Size, resize(Size div 2, list(fate_data())))),
?WHENFAIL(eqc:format("Trying to serialize/deserialize ~p failed~n", [FateDatas]),
?FORALL({Data, Garbage}, {fate_data(), binary()},
?WHENFAIL(eqc:format("Trying to serialize/deserialize ~p failed~n", [Data]),
begin
{T1, Binary} =
timer:tc( fun() ->
<< begin B = aeb_fate_encoding:serialize(Data),
<<B/binary>> end || Data <- FateDatas >>
end),
{T2, {FateData, _}} =
timer:tc(fun() -> aeb_fate_encoding:deserialize_one(Binary) end),
Binary = <<(aeb_fate_encoding:serialize(Data))/binary, Garbage/binary>>,
{FateData, Rest} = aeb_fate_encoding:deserialize_one(Binary),
measure(binary_size, size(Binary),
measure(encode, T1,
measure(decode, T2,
conjunction([{equal, equals(hd(FateDatas), FateData)},
{size, size(Binary) < 500000}]))))
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 aeb_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())), aeb_fate_encoding:serialize(FateData)),
?FORALL(InjectedBin, injection(Binary),
try Org = aeb_fate_encoding:deserialize(InjectedBin),
NewBin = aeb_fate_encoding:serialize(Org),
NewOrg = aeb_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, aeb_fate_data:lt(I, Min)])},
{maximum, is_empty([ {Max, '<', I} || I<-Items, aeb_fate_data:lt(Max, I)])},
{asym, aeb_fate_data:lt(Min, Max) orelse Min == Max}])
end).
lt_min([X, Y | Rest]) ->
case aeb_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 aeb_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(aeb_fate_encoding:sort(Items),
aeb_fate_encoding:sort(aeb_fate_encoding:sort(Items)))).
fate_data(Kind) ->
?SIZED(Size, ?LET(Data, fate_data(Size, Kind), eqc_symbolic:eval(Data))).
fate_data() ->
?SIZED(Size, ?LET(Data, fate_data(Size, [map]), eqc_symbolic:eval(Data))).
fate_data([map, variant, store_map]).
fate_data(0, _Options) ->
%% keys may contain variants but no maps
fate_data_key() ->
fate_data([variant]).
fate_data(0, Options) ->
?LAZY(
oneof([fate_integer(),
fate_boolean(),
fate_nil(),
fate_unit(),
fate_string(),
fate_address(),
fate_hash(),
fate_signature(),
fate_contract(),
fate_oracle(),
fate_name(),
fate_bits(),
fate_channel()]));
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) ->
oneof([?LAZY(fate_data(Size - 1, Options)),
?LAZY(fate_list( fate_data(Size div 5, Options) )),
?LAZY(fate_tuple( list(fate_data(Size div 5, Options)) )),
?LAZY(fate_variant( list(fate_data(Size div 5, Options)))) ] ++
[
?LAZY(fate_map( fate_data(Size div 8, Options -- [map]),
fate_data(Size div 5, Options)))
|| lists:member(map, 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() -> {call, aeb_fate_data, make_integer, [oneof([int(), largeint()])]}.
fate_bits() -> {call, aeb_fate_data, make_bits, [oneof([int(), largeint()])]}.
fate_boolean() -> {call, aeb_fate_data, make_boolean, [elements([true, false])]}.
fate_nil() -> {call, aeb_fate_data, make_list, [[]]}.
fate_unit() -> {call, aeb_fate_data, make_unit, []}.
fate_string() -> {call, aeb_fate_data, make_string,
[frequency([{10, non_quote_string()}, {2, list(non_quote_string())},
{1, ?LET(N, choose(64-3, 64+3), vector(N, $a))}])]}.
fate_address() -> {call, aeb_fate_data, make_address, [non_zero_binary(256 div 8)]}.
fate_hash() -> {call, aeb_fate_data, make_hash, [non_zero_binary(32)]}.
fate_signature() -> {call, aeb_fate_data, make_signature, [non_zero_binary(64)]}.
fate_contract() -> {call, aeb_fate_data, make_contract, [non_zero_binary(256 div 8)]}.
fate_oracle() -> {call, aeb_fate_data, make_oracle, [non_zero_binary(256 div 8)]}.
fate_name() -> {call, aeb_fate_data, make_name, [non_zero_binary(256 div 8)]}.
fate_channel() -> {call, aeb_fate_data, make_channel, [non_zero_binary(256 div 8)]}.
fate_integer() -> ?LET(X, oneof([int(), largeint()]), return(aeb_fate_data:make_integer(X))).
fate_bits() -> ?LET(X, oneof([int(), largeint()]), return(aeb_fate_data:make_bits(X))).
fate_boolean() -> ?LET(X, elements([true, false]), return(aeb_fate_data:make_boolean(X))).
fate_nil() -> aeb_fate_data:make_list([]).
fate_unit() -> aeb_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(aeb_fate_data:make_string(X))).
fate_address() -> ?LET(X, binary(256 div 8), return(aeb_fate_data:make_address(X))).
fate_bytes() -> ?LET(X, non_empty(binary()), return(aeb_fate_data:make_bytes(X))).
fate_contract() -> ?LET(X, binary(256 div 8), return(aeb_fate_data:make_contract(X))).
fate_oracle() -> ?LET(X, binary(256 div 8), return(aeb_fate_data:make_oracle(X))).
fate_oracle_q() -> ?LET(X, binary(256 div 8), return(aeb_fate_data:make_oracle_query(X))).
fate_channel() -> ?LET(X, binary(256 div 8), return(aeb_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(ListGen) ->
{call, aeb_fate_data, make_tuple, [?LET(Elements, ListGen, list_to_tuple(Elements))]}.
fate_tuple(Size, Options) ->
?LET(N, choose(0, 6),
?LETSHRINK(Elements, fate_values(Size, N, Options),
return(aeb_fate_data:make_tuple(list_to_tuple(Elements))))).
fate_variant(ListGen) ->
?LET({L1, L2, TupleAsList}, {list(choose(0, 255)), list(choose(0,255)), ListGen},
{call, aeb_fate_data, make_variant,
[L1 ++ [length(TupleAsList)] ++ L2, length(L1), list_to_tuple(TupleAsList)]}).
fate_variant(Size, Options) ->
?LET({L1, L2, {tuple, Args}}, {list(choose(0, 255)), list(choose(0,255)), fate_tuple(Size, Options)},
return(aeb_fate_data:make_variant(L1 ++ [tuple_size(Args)] ++ L2,
length(L1), Args))).
fate_list(Gen) ->
{call, aeb_fate_data, make_list, [frequency([{20, list(Gen)}, {1, ?LET(N, choose(64-3, 64+3), vector(N, Gen))}])]}.
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(aeb_fate_data:make_list(Vs)))).
fate_map(KeyGen, ValGen) ->
{call, aeb_fate_data, make_map, [map(KeyGen, ValGen)]}.
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(aeb_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(aeb_fate_data:make_store_map(abs(X)))).
non_zero_binary(N) ->
Bits = N*8,
?SUCHTHAT(Bin, binary(N), begin <<V:Bits>> = Bin, V =/= 0 end).
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(aeb_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.
+17 -10
View File
@@ -11,9 +11,12 @@
-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(FateType,
collect(kind(FateType),
begin
Serialized = aeb_fate_encoding:serialize_type(FateType),
BinSerialized = list_to_binary(Serialized),
@@ -32,18 +35,22 @@ fate_type(0) ->
oneof([integer,
boolean,
address,
hash,
signature,
{bytes, nat()},
contract,
oracle,
name,
channel,
bits,
string]);
fate_type(Size) ->
oneof([?LAZY(fate_type(Size div 2)),
{list, ?LAZY(fate_type(Size div 2))},
{tuple, list(?LAZY(fate_type(Size div 2)))},
{variant, list(?LAZY(fate_type(Size div 2)))},
?LETSHRINK([T1, T2], [?LAZY(fate_type(Size div 2)), ?LAZY(fate_type(Size div 2))],
{map, T1, T2})]).
?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)))).
+2 -2
View File
@@ -6,7 +6,7 @@
{deps, [ {eblake2, "1.0.0"}
, {aeserialization, {git, "https://github.com/aeternity/aeserialization.git",
{ref, "6dce265"}}}
{ref, "816bf99"}}}
, {getopt, "1.0.1"}
]}.
@@ -53,7 +53,7 @@
"/njs /njh /nfl /ndl & exit /b 0"} % silence things
]}
]},
{eqc, [{erl_opts, [{parse_transform, eqc_cover}]},
{eqc, [{erl_opts, [{parse_transform, eqc_cover}, {d, 'EQC'}]},
{extra_src_dirs, ["quickcheck"]} %% May not be called eqc!
]}
]}.
+1 -1
View File
@@ -1,7 +1,7 @@
{"1.1.0",
[{<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git",
{ref,"6dce265753af4e651f77746e77ea125145c85dd3"}},
{ref,"816bf994ffb5cee218c3f22dc5fea296c9e0882e"}},
0},
{<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git",
+69 -25
View File
@@ -2,23 +2,25 @@
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% Encode and decode data and function calls according to
%%% Sophia-AEVM-ABI.
%%% Sophia-AEVM-ABI
%%% @end
%%% Created : 25 Jan 2018
%%%
%%%-------------------------------------------------------------------
-module(aeb_abi).
-module(aeb_aevm_abi).
-define(HASH_SIZE, 32).
-export([ create_calldata/4
, check_calldata/2
, function_type_info/3
, 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.
@@ -26,6 +28,7 @@
-type typerep() :: aeb_aevm_data:type().
-type function_type_info() :: { FunctionHash :: hash()
, FunctionName :: function_name()
, Payable :: boolean()
, ArgType :: binary() %% binary typerep
, OutType :: binary() %% binary typerep
}.
@@ -35,6 +38,11 @@
%%% API
%%%===================================================================
%% Shall match ?ABI_AEVM_SOPHIA_1
-spec abi_version() -> integer().
abi_version() ->
1.
%%%===================================================================
%%% Handle calldata
@@ -43,14 +51,26 @@ create_calldata(FunName, Args, ArgTypes0, RetType) ->
<<TypeHashInt:?HASH_SIZE/unit:8>> =
function_type_hash(list_to_binary(FunName), ArgTypes, RetType),
Data = aeb_heap:to_binary({TypeHashInt, list_to_tuple(Args)}),
{ok, Data, {tuple, [word, ArgTypes]}, RetType}.
{ok, Data}.
-spec check_calldata(binary(), type_info()) ->
-spec check_calldata(binary(), type_info(), boolean()) ->
{'ok', typerep(), typerep()} | {'error', atom()}.
check_calldata(CallData, TypeInfo) ->
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 aeb_heap:from_binary({tuple, [word, ArgType]}, CallData) of
@@ -64,11 +84,9 @@ check_calldata(CallData, TypeInfo) ->
end;
{error, _} ->
{error, unknown_function}
end;
{error, _What} ->
{error, bad_call_data}
end.
-spec get_function_hash_from_calldata(CallData::binary()) ->
{ok, binary()} | {error, term()}.
get_function_hash_from_calldata(CallData) ->
@@ -80,12 +98,13 @@ get_function_hash_from_calldata(CallData) ->
%%%===================================================================
%%% Handle type info from contract meta data
-spec function_type_info(function_name(), [typerep()], typerep()) ->
-spec function_type_info(function_name(), boolean(), [typerep()], typerep()) ->
function_type_info().
function_type_info(Name, ArgTypes, OutType) ->
function_type_info(Name, Payable, ArgTypes, OutType) ->
ArgType = {tuple, ArgTypes},
{ function_type_hash(Name, ArgType, OutType)
, Name
, Payable
, aeb_heap:to_binary(ArgType)
, aeb_heap:to_binary(OutType)
}.
@@ -104,35 +123,46 @@ function_type_hash(Name, ArgType, OutType) when is_binary(Name) ->
{'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} ->
case aeb_heap:from_binary(typerep, ArgTypeBin) of
{ok, ArgType} -> {ok, ArgType};
{error,_} -> {error, bad_type_data}
end;
{_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 aeb_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} ->
case {aeb_heap:from_binary(typerep, ArgTypeBin),
aeb_heap:from_binary(typerep, OutTypeBin)} of
{{ok, ArgType}, {ok, OutType}} -> {ok, ArgType, OutType};
{_, _} -> {error, bad_type_data}
end;
{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 {aeb_heap:from_binary(typerep, ArgTBin), aeb_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} ->
{TypeHash, Function, _ArgTypeBin, _OutTypeBin} ->
{ok, Function};
{TypeHash, Function, _Payable, _ArgTypeBin, _OutTypeBin} ->
{ok, Function};
false ->
{error, unknown_function}
@@ -143,8 +173,22 @@ function_name_from_type_hash(TypeHash, TypeInfo) ->
| {'error', 'unknown_function'}.
type_hash_from_function_name(Name, TypeInfo) ->
case lists:keyfind(Name, 2, TypeInfo) of
{TypeHash, Name,_ArgTypeBin,_OutTypeBin} ->
{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.
+77
View File
@@ -0,0 +1,77 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Encode and decode data and function calls according to
%%% Sophia-FATE-ABI
%%% @end
%%% Created : 11 Jun 2019
%%%
%%%-------------------------------------------------------------------
-module(aeb_fate_abi).
-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/aeb_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 = aeb_fate_code:symbol_identifier(list_to_binary(FunName)),
{ok, aeb_fate_encoding:serialize(
aeb_fate_data:make_tuple({FunctionId,
aeb_fate_data:make_tuple(list_to_tuple(Args))}))}.
-spec decode_calldata(list(), binary()) -> {ok, term()} | {error, term()}.
decode_calldata(FunName, Calldata) ->
FunctionId = aeb_fate_code:symbol_identifier(list_to_binary(FunName)),
case ?FATE_TUPLE_ELEMENTS(aeb_fate_encoding:deserialize(Calldata)) of
[FunctionId, FateArgs] -> {ok, ?FATE_TUPLE_ELEMENTS(FateArgs)};
_ -> {error, decode_error}
end.
-spec get_function_name_from_function_hash(binary(), aeb_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 = aeb_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(aeb_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(), aeb_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 = aeb_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.
+65 -586
View File
@@ -30,12 +30,10 @@
%%% 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
%%% References to the top of the stack is the letter a (for accumulator)
%%% a
%%%
%%% Immediate values can be of 11 types:
%%% Immediate values can be of 10 types:
%%% 1a. Integers as decimals: {Digits} or -{Digits}
%%% 42
%%% -2374683271468723648732648736498712634876147
@@ -44,7 +42,7 @@
%%% 2a. account addresses, a base58c encoded string prefixed with @ak_
%%% 2b. contract address: @ct_{base58char}+
%%% 2c. oracle address: @ok_{base58char}+
%%% 2d. name address: @nm_{base58char}+
%%% 2d. oracle query id: @oq_{base58char}+
%%% 2e. channel address: @ch_{base58char}+
%%% 3. Boolean true or false
%%% true
@@ -67,15 +65,15 @@
%%% (1, "foo")
%%% 9. Variants: (| [Arities] | Tag | ( Elements ) |)
%%% (| [0,1,2] | 2 | ( "foo", 12) |)
%%% 10. Hashes: #{base64char}+
%%% 10. Bytes: #{base64char}+
%%% #AQIDCioLFQ==
%%% 11. Signatures: $sg_{base58char}+
%%%
%%% Where Digits: [0123456789]
%%% Hexdigits: [0123456789abcdef]
%%% base58char: [123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]
%%% base64char: [ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxy0123456789+/=]
%%% Characters any printable ascii character 0..255 (except " no quoting yet)
%%% 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
@@ -89,13 +87,11 @@
-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").
@@ -132,9 +128,10 @@ pp(FateCode) ->
io_lib:format("~ts~n",[Listing]).
to_asm(#{ functions := Functions
, symbols := Symbols
, annotations := Annotations} = _FateCode) ->
to_asm(FateCode) ->
Functions = aeb_fate_code:functions(FateCode),
Symbols = aeb_fate_code:symbols(FateCode),
Annotations = aeb_fate_code:annotations(FateCode),
insert_comments(get_comments(Annotations), 1,
lists:flatten(
io_lib:format("~s",
@@ -150,19 +147,13 @@ 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)].
{Name, {_Attrs, Sig, CodeMap}} <- maps:to_list(Functions)].
format(Name, Sig, BBs, Symbols) ->
@@ -218,521 +209,22 @@ asm_to_bytecode(AssemblerCode, Options) ->
none ->
ok
end,
Env = #{ fate_code => aeb_fate_code:new()
, functions => #{}
},
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 = << (aeser_rlp:encode(list_to_binary(ByteList)))/binary,
(aeser_rlp:encode(list_to_binary(Signatures)))/binary,
(aeser_rlp:encode(SymbolTable))/binary,
(aeser_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,
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 = aeb_fate_code:serialize(FateCode, FunctionsBin, Options),
{Env, ByteCode}.
strip(ByteCode) ->
{Code, _Rest} = aeser_rlp:decode_one(ByteCode),
Code.
bytecode_to_fate_code(Bytes, _Options) ->
{ByteCode, Rest1} = aeser_rlp:decode_one(Bytes),
{Signatures, Rest2} = aeser_rlp:decode_one(Rest1),
{SymbolTable, Rest3} = aeser_rlp:decode_one(Rest2),
{Annotations, <<>>} = aeser_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(?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
{L, Rest4} when is_list(L) ->
Modifier0 = bits_to_modifier(ArgType band 2#11),
immediate = bits_to_modifier((ArgType bsr 2) band 2#11),
{Rest4, [{aeb_fate_opcodes:mnemonic(?SWITCH_VN)
, {Modifier0, Arg0}
, {immediate, L}
}
| 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]};
5 ->
<<ArgType:8, ArgType2: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),
{Arg4, Rest7} = aeb_fate_encoding:deserialize_one(Rest6),
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),
Modifier4 = bits_to_modifier(ArgType2 band 2#11),
{Rest7, [{ OpName
, {Modifier0, Arg0}
, {Modifier1, Arg1}
, {Modifier2, Arg2}
, {Modifier3, Arg3}
, {Modifier4, Arg4}
}
| Code]};
6 ->
<<ArgType:8, ArgType2: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),
{Arg4, Rest7} = aeb_fate_encoding:deserialize_one(Rest6),
{Arg5, Rest8} = aeb_fate_encoding:deserialize_one(Rest7),
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),
Modifier4 = bits_to_modifier(ArgType2 band 2#11),
Modifier5 = bits_to_modifier((ArgType2 bsr 2) band 2#11),
{Rest8, [{ OpName
, {Modifier0, Arg0}
, {Modifier1, Arg1}
, {Modifier2, Arg2}
, {Modifier3, Arg3}
, {Modifier4, Arg4}
, {Modifier5, Arg5}
}
| Code]};
7 ->
<<ArgType:8, ArgType2: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),
{Arg4, Rest7} = aeb_fate_encoding:deserialize_one(Rest6),
{Arg5, Rest8} = aeb_fate_encoding:deserialize_one(Rest7),
{Arg6, Rest9} = aeb_fate_encoding:deserialize_one(Rest8),
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),
Modifier4 = bits_to_modifier(ArgType2 band 2#11),
Modifier5 = bits_to_modifier((ArgType2 bsr 2) band 2#11),
Modifier6 = bits_to_modifier((ArgType2 bsr 4) band 2#11),
{Rest9, [{ OpName
, {Modifier0, Arg0}
, {Modifier1, Arg1}
, {Modifier2, Arg2}
, {Modifier3, Arg3}
, {Modifier4, Arg4}
, {Modifier5, Arg5}
, {Modifier6, Arg6}
}
| Code]};
8 ->
<<ArgType:8, ArgType2: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),
{Arg4, Rest7} = aeb_fate_encoding:deserialize_one(Rest6),
{Arg5, Rest8} = aeb_fate_encoding:deserialize_one(Rest7),
{Arg6, Rest9} = aeb_fate_encoding:deserialize_one(Rest8),
{Arg7, Rest10} = aeb_fate_encoding:deserialize_one(Rest9),
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),
Modifier4 = bits_to_modifier(ArgType2 band 2#11),
Modifier5 = bits_to_modifier((ArgType2 bsr 2) band 2#11),
Modifier6 = bits_to_modifier((ArgType2 bsr 4) band 2#11),
Modifier7 = bits_to_modifier((ArgType2 bsr 6) band 2#11),
{Rest10, [{ OpName
, {Modifier0, Arg0}
, {Modifier1, Arg1}
, {Modifier2, Arg2}
, {Modifier3, Arg3}
, {Modifier4, Arg4}
, {Modifier5, Arg5}
, {Modifier6, Arg6}
, {Modifier7, Arg7}
}
| Code]}
end.
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
%% For 5-8 args another Argument Spec Byte is used
%% Bit pattern
%% 00 : stack/unused (depending on instruction)
%% 01 : argN
%% 10 : varN
%% 11 : immediate
serialize_code([ {Arg0Type, Arg0}
, {Arg1Type, Arg1}
, {Arg2Type, Arg2}
, {Arg3Type, Arg3}
, {Arg4Type, Arg4}
, {Arg5Type, Arg5}
, {Arg6Type, Arg6}
, {Arg7Type, Arg7}
| Rest]) ->
ArgSpec1 =
modifier_bits(Arg0Type) bor
(modifier_bits(Arg1Type) bsl 2) bor
(modifier_bits(Arg2Type) bsl 4) bor
(modifier_bits(Arg3Type) bsl 6),
ArgSpec2 =
modifier_bits(Arg4Type) bor
(modifier_bits(Arg5Type) bsl 2) bor
(modifier_bits(Arg6Type) bsl 4) bor
(modifier_bits(Arg7Type) bsl 6),
[ ArgSpec1
, ArgSpec2
, serialize_data(Arg0Type, Arg0)
, serialize_data(Arg1Type, Arg1)
, serialize_data(Arg2Type, Arg2)
, serialize_data(Arg3Type, Arg3)
, serialize_data(Arg4Type, Arg4)
, serialize_data(Arg5Type, Arg5)
, serialize_data(Arg6Type, Arg6)
, serialize_data(Arg7Type, Arg7)
| serialize_code(Rest)];
serialize_code([ {Arg0Type, Arg0}
, {Arg1Type, Arg1}
, {Arg2Type, Arg2}
, {Arg3Type, Arg3}
, {Arg4Type, Arg4}
, {Arg5Type, Arg5}
, {Arg6Type, Arg6}
| Rest]) ->
ArgSpec1 =
modifier_bits(Arg0Type) bor
(modifier_bits(Arg1Type) bsl 2) bor
(modifier_bits(Arg2Type) bsl 4) bor
(modifier_bits(Arg3Type) bsl 6),
ArgSpec2 =
modifier_bits(Arg4Type) bor
(modifier_bits(Arg5Type) bsl 2) bor
(modifier_bits(Arg6Type) bsl 4),
[ ArgSpec1
, ArgSpec2
, serialize_data(Arg0Type, Arg0)
, serialize_data(Arg1Type, Arg1)
, serialize_data(Arg2Type, Arg2)
, serialize_data(Arg3Type, Arg3)
, serialize_data(Arg4Type, Arg4)
, serialize_data(Arg5Type, Arg5)
, serialize_data(Arg6Type, Arg6)
| serialize_code(Rest)];
serialize_code([ {Arg0Type, Arg0}
, {Arg1Type, Arg1}
, {Arg2Type, Arg2}
, {Arg3Type, Arg3}
, {Arg4Type, Arg4}
, {Arg5Type, Arg5}
| Rest]) ->
ArgSpec1 =
modifier_bits(Arg0Type) bor
(modifier_bits(Arg1Type) bsl 2) bor
(modifier_bits(Arg2Type) bsl 4) bor
(modifier_bits(Arg3Type) bsl 6),
ArgSpec2 =
modifier_bits(Arg4Type) bor
(modifier_bits(Arg5Type) bsl 2),
[ ArgSpec1
, ArgSpec2
, serialize_data(Arg0Type, Arg0)
, serialize_data(Arg1Type, Arg1)
, serialize_data(Arg2Type, Arg2)
, serialize_data(Arg3Type, Arg3)
, serialize_data(Arg4Type, Arg4)
, serialize_data(Arg5Type, Arg5)
| serialize_code(Rest)];
serialize_code([ {Arg0Type, Arg0}
, {Arg1Type, Arg1}
, {Arg2Type, Arg2}
, {Arg3Type, Arg3}
, {Arg4Type, Arg4}
| Rest]) ->
ArgSpec1 =
modifier_bits(Arg0Type) bor
(modifier_bits(Arg1Type) bsl 2) bor
(modifier_bits(Arg2Type) bsl 4) bor
(modifier_bits(Arg3Type) bsl 6),
ArgSpec2 =
modifier_bits(Arg4Type),
[ ArgSpec1
, ArgSpec2
, serialize_data(Arg0Type, Arg0)
, serialize_data(Arg1Type, Arg1)
, serialize_data(Arg2Type, Arg2)
, serialize_data(Arg3Type, Arg3)
, serialize_data(Arg4Type, Arg4)
| serialize_code(Rest)];
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([ ?SWITCH_VN
, {Arg0Type, Arg0}
, {immediate, L}
| Rest]) ->
ArgSpec =
modifier_bits(Arg0Type) bor
(modifier_bits(immediate) bsl 2),
[?SWITCH_VN
, ArgSpec
, serialize_data(Arg0Type, Arg0)
, serialize_data(immediate, L)] ++ serialize_code(Rest);
serialize_code([B|Rest]) ->
[B | serialize_code(Rest)];
serialize_code([]) -> [].
%% 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}) ->
[aeb_fate_encoding:serialize_type({tuple, Args}) |
aeb_fate_encoding:serialize_type(RetType)].
deserialize_signature(Binary) ->
{{tuple, Args}, Rest} = aeb_fate_encoding:deserialize_type(Binary),
{RetType, Rest2} = aeb_fate_encoding:deserialize_type(Rest),
{{Args, RetType}, Rest2}.
to_hexstring(ByteList) ->
"0x" ++ lists:flatten(
[io_lib:format("~2.16.0b", [X])
|| X <- ByteList]).
%% -------------------------------------------------------------------
%% Parser
%% Asm tokens -> Fate code env
@@ -749,8 +241,8 @@ 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([{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) ->
@@ -774,29 +266,24 @@ to_bytecode([{object,_line, {oracle, Value}}|Rest],
to_bytecode(Rest, Address, Env,
[{immediate, aeb_fate_data:make_oracle(Value)}|Code],
Opts);
to_bytecode([{object,_line, {name, Value}}|Rest],
to_bytecode([{object,_line, {oracle_query, Value}}|Rest],
Address, Env, Code, Opts) ->
to_bytecode(Rest, Address, Env,
[{immediate, aeb_fate_data:make_name(Value)}|Code],
[{immediate, aeb_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, aeb_fate_data:make_contract(Value)}|Code],
Opts);
to_bytecode([{hash,_line, Value}|Rest],
to_bytecode([{bytes,_line, Value}|Rest],
Address, Env, Code, Opts) ->
to_bytecode(Rest, Address, Env,
[{immediate, aeb_fate_data:make_hash(Value)}|Code],
Opts);
to_bytecode([{signature,_line, {signature, Value}}|Rest],
Address, Env, Code, Opts) ->
to_bytecode(Rest, Address, Env,
[{immediate, aeb_fate_data:make_signature(Value)}|Code],
[{immediate, aeb_fate_data:make_bytes(Value)}|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);
{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);
@@ -811,6 +298,10 @@ to_bytecode([{start_variant,_line}|_] = Tokens, Address, Env, Code, Opts) ->
{Arities, Tag, Values, Rest} = parse_variant(Tokens),
Variant = aeb_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 = aeb_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, aeb_fate_data:make_bits(Bits)}|Code], Opts);
@@ -819,17 +310,8 @@ 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_bytecode([], Address, Env, Code,_Opts) ->
insert_fun(Address, Code, Env).
parse_map([{'}',_line}|Rest]) ->
{#{}, Rest};
@@ -906,20 +388,22 @@ parse_value([{start_variant,_line}|_] = Tokens) ->
{Variant, Rest};
parse_value([{string,_line, String} | Rest]) ->
{aeb_fate_data:make_string(String), Rest};
parse_value([{address,_line, {address, Address}} | Rest]) ->
parse_value([{object,_line, {address, Address}} | Rest]) ->
{aeb_fate_data:make_address(Address), Rest};
parse_value([{address,_line, {contract, Address}} | Rest]) ->
parse_value([{object,_line, {contract, Address}} | Rest]) ->
{aeb_fate_data:make_contract(Address), Rest};
parse_value([{address,_line, {oracle, Address}} | Rest]) ->
parse_value([{object,_line, {oracle, Address}} | Rest]) ->
{aeb_fate_data:make_oracle(Address), Rest};
parse_value([{address,_line, {name, Address}} | Rest]) ->
{aeb_fate_data:make_name(Address), Rest};
parse_value([{address,_line, {channel, Address}} | Rest]) ->
parse_value([{object,_line, {oracle_query, Address}} | Rest]) ->
{aeb_fate_data:make_oracle_query(Address), Rest};
parse_value([{object,_line, {channel, Address}} | Rest]) ->
{aeb_fate_data:make_channel(Address), Rest};
parse_value([{hash,_line, Hash} | Rest]) ->
{aeb_fate_data:make_hash(Hash), Rest};
parse_value([{signature,_line, Hash} | Rest]) ->
{aeb_fate_data:make_signature(Hash), Rest}.
{aeb_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),
@@ -945,6 +429,7 @@ 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};
@@ -963,6 +448,9 @@ to_type([{'{', _}, {id, _, "map"}, {',', _} | Rest]) ->
{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"}
, {',', _}
@@ -990,11 +478,24 @@ to_list_of_types(Tokens) ->
%% 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)}}
}.
insert_fun({NameString, ArgType, RetType}, Code, #{ fate_code := FateCode
, functions := Funs} = Env) ->
Name = list_to_binary(NameString),
{FateCode1, Id} = aeb_fate_code:insert_symbol(Name, FateCode),
BodyByteCode = aeb_fate_code:serialize_code(lists:reverse(Code)),
SigByteCode = aeb_fate_code:serialize_signature({ArgType, RetType}),
FunByteCode = [?FUNCTION, Id, aeb_fate_encoding:serialize(0), SigByteCode, BodyByteCode],
Env#{ functions => Funs#{ Id => FunByteCode }
, fate_code => FateCode1}.
insert_symbol(Name, #{ fate_code := FateCode } = Env) ->
{FateCode1, Id} = aeb_fate_code:insert_symbol(Name, FateCode),
{ Env#{ fate_code => FateCode1 }
, Id}.
insert_annotation(comment, Line, Comment, #{ fate_code := FateCode } = Env) ->
FateCode1 = aeb_fate_code:insert_annotation(comment, Line, Comment, FateCode),
Env#{ fate_code => FateCode1}.
mk_hash(Id) ->
%% Use first 4 bytes of blake hash
@@ -1003,33 +504,11 @@ mk_hash(Id) ->
%% 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) ->
+7 -10
View File
@@ -17,8 +17,7 @@ BASE64 = [A-Za-z0-9+/=]
INT = {DIGIT}+
HEX = 0x{HEXDIGIT}+
OBJECT = @[a-z][a-z]_{BASE58}+
HASH = #{BASE64}+
SIG = \$sg_{BASE58}+
BYTES = #{BASE64}+
WS = [\000-\s]
ID = {LOWER}[a-zA-Z0-9_]*
STRING = "[^"]*"
@@ -27,8 +26,7 @@ BITS = (\!)?\<[\s01]*\>
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)}}.
a : {token, {stack, TokenLine}}.
true : {token, {boolean, TokenLine, true}}.
false : {token, {boolean, TokenLine, false}}.
@@ -37,10 +35,8 @@ false : {token, {boolean, TokenLine, false}}.
FUNCTION : {token, {function, TokenLine, 'FUNCTION' }}.
{HASH} :
{token, {hash, TokenLine, parse_hash(TokenChars)}}.
{SIG} :
{token, {signature, TokenLine, parse_object(TokenChars)}}.
{BYTES} :
{token, {bytes, TokenLine, parse_hash(TokenChars)}}.
{OBJECT} :
{token, {object, TokenLine, parse_object(TokenChars)}}.
{ID} :
@@ -76,6 +72,7 @@ FUNCTION : {token, {function, TokenLine, 'FUNCTION' }}.
\{ : {token, {'{', TokenLine}}.
\} : {token, {'}', TokenLine}}.
\| : {token, {'|', TokenLine}}.
\' : {token, {typerep, TokenLine}}.
;;.* :
{token, {comment, TokenLine, drop_prefix($;, TokenChars)}}.
@@ -108,7 +105,7 @@ 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) ->
@@ -119,7 +116,7 @@ parse_object([_|Chars]) ->
{account_pubkey, Bin} -> {address, Bin};
{contract_pubkey, Bin} -> {contract, Bin};
{oracle_pubkey, Bin} -> {oracle, Bin};
{name, Bin} -> {name, Bin};
{oracle_query_id, Bin} -> {oracle_query, Bin};
{channel, Bin} -> {channel, Bin};
{signature, Bin} -> {signature, Bin}
end.
+441
View File
@@ -0,0 +1,441 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% ADT for fate byte code/fate code
%%% @end
%%%
%%%-------------------------------------------------------------------
-module(aeb_fate_code).
-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
, symbol_identifier/1
, symbols/1
]).
-include("../include/aeb_fate_opcodes.hrl").
-include("../include/aeb_fate_data.hrl").
-ifdef(EQC).
-export([ update_annotations/2
, update_functions/2
, update_symbols/2]).
-endif.
-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 = aeb_fate_data:make_tuple({aeb_fate_data:make_string("comment"), Line}),
Value = aeb_fate_data:make_string(Comment),
update_annotations(FCode, #{ Key => Value }).
%%%===================================================================
%%% 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 = << (aeser_rlp:encode(Functions))/binary,
(aeser_rlp:encode(SymbolTable))/binary,
(aeser_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 ]),
aeb_fate_encoding:serialize(?MAKE_FATE_INTEGER(AttrVal)).
attr_value(private) -> 1;
attr_value(payable) -> 2.
serialize_signature({Args, RetType}) ->
[aeb_fate_encoding:serialize_type({tuple, Args}) |
aeb_fate_encoding:serialize_type(RetType)].
serialize_symbol_table(#fcode{ symbols = Symbols }) ->
aeb_fate_encoding:serialize(aeb_fate_data:make_map(Symbols)).
serialize_annotations(#fcode{ annotations = Annotations }) ->
aeb_fate_encoding:serialize(aeb_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,
[aeb_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, aeb_fate_opcodes:m_to_op(Mnemonic), Args).
safe_sanity_check(IsLast, Op, Args) ->
case length(Args) == aeb_fate_opcodes:args(Op) of
true ->
case IsLast == aeb_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) ->
aeb_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} = aeser_rlp:decode_one(Bytes),
{SymbolTable, Rest2} = aeser_rlp:decode_one(Rest1),
{Annotations, <<>>} = aeser_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 aeb_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 = aeb_fate_opcodes:mnemonic(Op),
case aeb_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} = aeb_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} = aeb_fate_encoding:deserialize_one(Acc),
{{Modifier, Arg}, Acc2}
end
end, Rest, ArgMods).
deserialize_attributes(Binary) ->
{AttrVal, Rest} = aeb_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} = aeb_fate_encoding:deserialize_type(Binary),
{RetType, Rest2} = aeb_fate_encoding:deserialize_type(Rest),
{{Args, RetType}, Rest2}.
deserialize_symbols(Table) ->
?FATE_MAP_VALUE(SymbolTable) = aeb_fate_encoding:deserialize(Table),
SymbolTable.
deserialize_annotations(AnnotationsBin) ->
?FATE_MAP_VALUE(Annotations) = aeb_fate_encoding:deserialize(AnnotationsBin),
Annotations.
assert_zero([]) ->
true;
assert_zero([0|Rest]) ->
assert_zero(Rest);
assert_zero([_|_]) ->
error(argument_defined_outside_range).
+191 -17
View File
@@ -10,17 +10,18 @@
-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_HASH_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_name() :: ?FATE_NAME_T.
-type fate_channel() :: ?FATE_CHANNEL_T.
-type fate_signature() :: ?FATE_SIGNATURE_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_type_type() :: integer
| boolean
@@ -32,7 +33,6 @@
| signature
| contract
| oracle
| name
| channel
| bits
| string
@@ -52,11 +52,11 @@
| fate_signature()
| fate_contract()
| fate_oracle()
| fate_name()
| fate_channel()
| fate_variant()
| fate_map()
| fate_bits().
| fate_bits()
| fate_typerep().
-export_type([fate_type/0
, fate_boolean/0
@@ -71,10 +71,10 @@
, fate_signature/0
, fate_contract/0
, fate_oracle/0
, fate_name/0
, fate_channel/0
, fate_variant/0
, fate_map/0
, fate_store_map/0
, fate_bits/0
, fate_type_type/0
]).
@@ -86,17 +86,25 @@
, 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_name/1
, make_oracle_query/1
, make_channel/1
, make_bits/1
, make_unit/0
, make_typerep/1
]).
-export([format/1]).
-export([
elt/2
, lt/2
, format/1
, ordinal/1]).
make_boolean(true) -> ?FATE_TRUE;
@@ -106,18 +114,22 @@ 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_hash(X) -> ?FATE_HASH(X).
make_signature(X) -> ?FATE_SIGNATURE(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_name(X) -> ?FATE_NAME(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).
%% Tag points to the selected variant (zero based)
%% The arity of this variant is read from the list of provided arities
@@ -160,19 +172,19 @@ format(?FATE_VARIANT(Arities, Tag, T)) ->
" |)"];
format(M) when ?IS_FATE_MAP(M) ->
["{ ", format_kvs(maps:to_list(?FATE_MAP_VALUE(M))), " }"];
format(?FATE_HASH(X)) -> ["#", base64:encode(X)];
format(?FATE_BYTES(X)) -> ["#", base64:encode(X)];
format(?FATE_ADDRESS(X)) ->
["@", aeser_api_encoder:encode(account_pubkey, X)];
format(?FATE_SIGNATURE(X)) ->
["$", aeser_api_encoder:encode(signature, X)];
format(?FATE_CONTRACT(X)) ->
["@", aeser_api_encoder:encode(contract_pubkey, X)];
format(?FATE_ORACLE(X)) ->
["@", aeser_api_encoder:encode(oracle_pubkey, X)];
format(?FATE_NAME(X)) ->
["@", aeser_api_encoder:encode(name, X)];
format(?FATE_ORACLE_Q(X)) ->
["@", aeser_api_encoder:encode(oracle_query_id, X)];
format(?FATE_CHANNEL(X)) ->
["@", aeser_api_encoder:encode(channel, X)];
format(?FATE_TYPEREP(X)) ->
["'", io_lib:format("~p", [X])];
format(V) -> exit({not_a_fate_type, V}).
format_bits(0, Acc) -> Acc;
@@ -193,3 +205,165 @@ format_list(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
-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).
-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.
-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_STRING,?FATE_STRING(A), ?FATE_STRING(B)) ->
SizeA = size(A),
SizeB = size(B),
case SizeA - SizeB of
0 -> A < B;
N -> N < 0
end;
lt(?ORD_TUPLE,?FATE_TUPLE(A), ?FATE_TUPLE(B)) ->
SizeA = size(A),
SizeB = 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])) ->
O1 = ordinal(A),
O2 = ordinal(B),
if O1 == O2 ->
if A == B -> lt(RA, RB);
true -> A < B
end;
true -> O1 < O2
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 ->
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(_, A, B) -> A < B.
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.
+171 -75
View File
@@ -1,21 +1,29 @@
%% 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:
%% * There has to be 1 and only 1 byte sequence
%% representing each unique value in FATE.
%% * 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 should fullfill the following:
%% 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 should fullfill the following:
%% 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.
@@ -40,6 +48,10 @@
, serialize_type/1
]).
-ifdef(EQC).
-export([sort/1]).
-endif.
-include("aeb_fate_data.hrl").
%% Definition of tag scheme.
@@ -61,13 +73,13 @@
-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]
%% 1001 0111
-define(TYPE_BYTES , 2#10010111). %% 1001 0111 - Bytes typedef
%% 1010 0111
%% 1011 0111
%% 1100 0111
%% 1101 0111
%% 1110 0111
%% 1111 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
@@ -81,7 +93,7 @@
%% %% 1000 1111 - FREE (Possibly for bytecode in the future.)
-define(OBJECT , 2#10011111). %% 1001 1111 | ObjectType | RLP encoded Array
-define(VARIANT , 2#10101111). %% 1010 1111 | [encoded arities] | encoded tag | [encoded values]
-define(NIL , 2#10111111). %% 1011 1111 - Empty list
-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)
@@ -97,12 +109,23 @@
%% Object types
-define(OTYPE_ADDRESS, 0).
-define(OTYPE_HASH, 1).
-define(OTYPE_SIGNATURE, 2).
-define(OTYPE_CONTRACT, 3).
-define(OTYPE_ORACLE, 4).
-define(OTYPE_NAME, 5).
-define(OTYPE_CHANNEL, 6).
-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_MAP orelse
X =:= ?TYPE_STRING orelse
X =:= ?TYPE_VARIANT)).
%% --------------------------------------------------
%% Serialize
@@ -112,9 +135,7 @@
-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);
@@ -128,19 +149,19 @@ serialize(String) when ?IS_FATE_STRING(String),
?FATE_STRING_SIZE(String) > 0,
?FATE_STRING_SIZE(String) >= ?SHORT_STRING_SIZE ->
Bytes = ?FATE_STRING_VALUE(String),
<<?LONG_STRING, (aeser_rlp:encode(Bytes))/binary>>;
<<?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, (aeser_rlp:encode(Address))/binary>>;
serialize(?FATE_HASH(Address)) when is_binary(Address) ->
<<?OBJECT, ?OTYPE_HASH, (aeser_rlp:encode(Address))/binary>>;
serialize(?FATE_SIGNATURE(Address)) when is_binary(Address) ->
<<?OBJECT, ?OTYPE_SIGNATURE, (aeser_rlp:encode(Address))/binary>>;
serialize(?FATE_CONTRACT(Address)) when is_binary(Address) ->
<<?OBJECT, ?OTYPE_CONTRACT, (aeser_rlp:encode(Address))/binary>>;
serialize(?FATE_ORACLE(Address)) when is_binary(Address) ->
<<?OBJECT, ?OTYPE_ORACLE, (aeser_rlp:encode(Address))/binary>>;
serialize(?FATE_NAME(Address)) when is_binary(Address) ->
<<?OBJECT, ?OTYPE_NAME, (aeser_rlp:encode(Address))/binary>>;
serialize(?FATE_ORACLE_Q(Address)) when is_binary(Address) ->
<<?OBJECT, ?OTYPE_ORACLE_Q, (aeser_rlp:encode(Address))/binary>>;
serialize(?FATE_CHANNEL(Address)) when is_binary(Address) ->
<<?OBJECT, ?OTYPE_CHANNEL, (aeser_rlp:encode(Address))/binary>>;
serialize(?FATE_TUPLE(T)) when size(T) > 0 ->
@@ -150,28 +171,32 @@ serialize(?FATE_TUPLE(T)) when size(T) > 0 ->
if S < ?SHORT_TUPLE_SIZE ->
<<S:4, ?SHORT_TUPLE:4, Rest/binary>>;
true ->
Size = rlp_integer(S - ?SHORT_TUPLE_SIZE),
Size = rlp_encode_int(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),
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),
Val = rlp_encode_int(S - ?SHORT_LIST_SIZE),
<<?LONG_LIST, Val/binary, Rest/binary>>
end;
serialize(Map) when ?IS_FATE_MAP(Map) ->
L = [{_K,_V}|_] = lists:sort(maps:to_list(?FATE_MAP_VALUE(Map))),
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 = << <<(serialize(K1))/binary, (serialize(V1))/binary>> || {K1,V1} <- L >>,
Elements =
list_to_binary([ <<(serialize(K))/binary, (serialize(V))/binary>> || {K, V} <- sort_and_check(L) ]),
<<?MAP,
(rlp_integer(Size))/binary,
(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),
@@ -188,7 +213,9 @@ serialize(?FATE_VARIANT(Arities, Tag, Values)) ->
(serialize(?FATE_TUPLE(Values)))/binary
>>
end
end.
end;
serialize(?FATE_TYPEREP(T)) ->
iolist_to_binary(serialize_type(T)).
%% -----------------------------------------------------
@@ -196,18 +223,20 @@ serialize(?FATE_VARIANT(Arities, Tag, Values)) ->
-spec serialize_type(aeb_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, N}) when 0 =< N ->
[?TYPE_BYTES | binary_to_list(serialize_integer(N))];
serialize_type(address) -> [?TYPE_OBJECT, ?OTYPE_ADDRESS];
serialize_type(hash) -> [?TYPE_OBJECT, ?OTYPE_HASH];
serialize_type(signature) -> [?TYPE_OBJECT, ?OTYPE_SIGNATURE];
serialize_type(contract) -> [?TYPE_OBJECT, ?OTYPE_CONTRACT];
serialize_type(oracle) -> [?TYPE_OBJECT, ?OTYPE_ORACLE];
serialize_type(name) -> [?TYPE_OBJECT, ?OTYPE_NAME];
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
@@ -223,20 +252,24 @@ serialize_type({variant, ListOfVariants}) ->
-spec deserialize_type(binary()) -> {aeb_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) andalso N >= 0,
{{bytes, N}, Rest2};
deserialize_type(<<?TYPE_OBJECT, ObjectType, Rest/binary>>) ->
case ObjectType of
?OTYPE_ADDRESS -> {address, Rest};
?OTYPE_HASH -> {hash, Rest};
?OTYPE_SIGNATURE -> {signature, Rest};
?OTYPE_CONTRACT -> {contract, Rest};
?OTYPE_ORACLE -> {oracle, Rest};
?OTYPE_NAME -> {name, Rest};
?OTYPE_ORACLE_Q -> {oracle_query, Rest};
?OTYPE_CHANNEL -> {channel, Rest}
end;
deserialize_type(<<?TYPE_BITS, Rest/binary>>) -> {bits, Rest};
@@ -267,9 +300,23 @@ deserialize_types(N, Binary, Acc) ->
%% -----------------------------------------------------
rlp_integer(S) when S >= 0 ->
rlp_encode_int(S) when S >= 0 ->
aeser_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} = aeser_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),
@@ -279,20 +326,16 @@ serialize_integer(I) when ?IS_FATE_INTEGER(I) ->
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>>;
(rlp_encode_int(Abs - ?SMALL_INT_SIZE))/binary>>;
Sign =:= ?POS_SIGN -> <<?POS_BIG_INT,
(rlp_integer(Abs - ?SMALL_INT_SIZE))/binary>>
(rlp_encode_int(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>>
B < 0 -> <<?NEG_BITS, (rlp_encode_int(Abs))/binary>>;
B >= 0 -> <<?POS_BITS, (rlp_encode_int(Abs))/binary>>
end.
-spec deserialize(binary()) -> aeb_fate_data:fate_type().
@@ -305,38 +348,50 @@ 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};
if I =/= 0 -> {?MAKE_FATE_INTEGER(-I), Rest};
I == 0 -> error({illegal_sign, I})
end;
deserialize2(<<?NEG_BIG_INT, Rest/binary>>) ->
{Bint, Rest2} = aeser_rlp:decode_one(Rest),
{?MAKE_FATE_INTEGER(-binary:decode_unsigned(Bint) - ?SMALL_INT_SIZE),
{Bint, Rest2} = rlp_decode_int(Rest),
{?MAKE_FATE_INTEGER(-Bint - ?SMALL_INT_SIZE),
Rest2};
deserialize2(<<?POS_BIG_INT, Rest/binary>>) ->
{Bint, Rest2} = aeser_rlp:decode_one(Rest),
{?MAKE_FATE_INTEGER(binary:decode_unsigned(Bint) + ?SMALL_INT_SIZE),
{Bint, Rest2} = rlp_decode_int(Rest),
{?MAKE_FATE_INTEGER(Bint + ?SMALL_INT_SIZE),
Rest2};
deserialize2(<<?NEG_BITS, Rest/binary>>) ->
{Bint, Rest2} = aeser_rlp:decode_one(Rest),
{?FATE_BITS(-binary:decode_unsigned(Bint)), Rest2};
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} = aeser_rlp:decode_one(Rest),
{?FATE_BITS(binary:decode_unsigned(Bint)), Rest2};
{Bint, Rest2} = rlp_decode_int(Rest),
{?FATE_BITS(Bint), Rest2};
deserialize2(<<?LONG_STRING, Rest/binary>>) ->
{String, Rest2} = aeser_rlp:decode_one(Rest),
{?MAKE_FATE_STRING(String), Rest2};
{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(<<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} = aeser_rlp:decode_one(Rest),
Val =
case ObjectType of
?OTYPE_ADDRESS -> ?FATE_ADDRESS(A);
?OTYPE_HASH -> ?FATE_HASH(A);
?OTYPE_SIGNATURE -> ?FATE_SIGNATURE(A);
?OTYPE_CONTRACT -> ?FATE_CONTRACT(A);
?OTYPE_ORACLE -> ?FATE_ORACLE(A);
?OTYPE_NAME -> ?FATE_NAME(A);
?OTYPE_ORACLE_Q -> ?FATE_ORACLE_Q(A);
?OTYPE_CHANNEL -> ?FATE_CHANNEL(A)
end,
{Val, Rest2};
@@ -344,36 +399,40 @@ deserialize2(<<?TRUE, Rest/binary>>) ->
{?FATE_TRUE, Rest};
deserialize2(<<?FALSE, Rest/binary>>) ->
{?FATE_FALSE, Rest};
deserialize2(<<?NIL, Rest/binary>>) ->
{?FATE_NIL, Rest};
deserialize2(<<?EMPTY_TUPLE, Rest/binary>>) ->
{?FATE_UNIT, Rest};
deserialize2(<<?EMPTY_MAP, Rest/binary>>) ->
{?MAKE_FATE_MAP(#{}), Rest};
deserialize2(<<?EMPTY_STRING, Rest/binary>>) ->
{?FATE_EMPTY_STRING, Rest};
deserialize2(<<?LONG_TUPLE, Rest/binary>>) ->
{BSize, Rest1} = aeser_rlp:decode_one(Rest),
N = binary:decode_unsigned(BSize) + ?SHORT_TUPLE_SIZE,
{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>>) ->
{BLength, Rest1} = aeser_rlp:decode_one(Rest),
Length = binary:decode_unsigned(BLength) + ?SHORT_LIST_SIZE,
{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>>) ->
{BSize, Rest1} = aeser_rlp:decode_one(Rest),
Size = binary:decode_unsigned(BSize),
{Size, Rest1} = rlp_decode_int(Rest),
{List, Rest2} = deserialize_elements(2*Size, Rest1),
Map = insert_kv(List, #{}),
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>>} = aeser_rlp:decode_one(Rest),
Arities = binary_to_list(AritiesBin),
@@ -388,10 +447,13 @@ deserialize2(<<?VARIANT, Rest/binary>>) ->
true ->
{?FATE_VARIANT(Arities, Tag, T), Rest3}
end
end.
end;
deserialize2(<<TypeTag, _/binary>> = Bin) when ?IS_TYPE_TAG(TypeTag) ->
{Type, Rest} = deserialize_type(Bin),
{?FATE_TYPEREP(Type), Rest}.
insert_kv([], M) -> M;
insert_kv([K,V|R], M) -> insert_kv(R, maps:put(K, V, M)).
insert_kv([]) -> [];
insert_kv([K, V | R]) -> [{K, V} | insert_kv(R)].
deserialize_elements(0, Rest) ->
{[], Rest};
@@ -399,3 +461,37 @@ 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, _}) ->
aeb_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.
+234 -157
View File
@@ -4,7 +4,7 @@
, generate/0
, generate_documentation/1
, get_ops/0
, test_asm_generator/1]).
, test_asm_generator/1 ]).
gen_and_halt([SrcDirArg, IncludeDirArg]) ->
generate(atom_to_list(SrcDirArg),
@@ -16,138 +16,175 @@ 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 ++ "aeb_fate_opcodes.hrl",
generate_header_file(HrlFile, Ops),
generate_opcodes_ops(aeb_fate_opcodes, HrlFile, Src, Ops),
generate_code_ops(aeb_fate_code, Src, Ops),
generate_code_ops(aeb_fate_ops, Src, Ops),
generate_scanner("aeb_fate_asm_scan.template", "aeb_fate_asm_scan.xrl", Src, Ops),
gen_asm_pp(aeb_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#fa -> check_numbering(16#fa+1, Rest);
_ when OpCode < N -> {duplicate_opcode, OpCode};
_ when OpCode > N -> {missing_opcode, N}
end;
check_numbering(_, []) -> true.
%% TODO: Some real gas numbers...
ops_defs() ->
%% Opname, Opcode, args, end_bb, gas, format, Constructor, Documentation
[ { 'RETURN', 16#00, 0, true, 2, atomic, return, "Return from function call pop stack to arg0. The type of the retun value has to match the return type of the function."}
, { 'RETURNR', 16#01, 1, true, 2, [a], returnr, "Return from function call copy Arg0 to arg0. The type of the retun value has to match the return type of the function."}
, { 'CALL', 16#02, 1, true, 4, [is], call, "Call given function with args on stack. The types of the arguments has to match the argument typs of the function."}
, { 'CALL_R', 16#03, 2, true, 8, [a,is], call_r, "Remote call to given contract and function. The types of the arguments has to match the argument typs of the function."}
, { 'CALL_T', 16#04, 1, true, 4, [is], call_t, "Tail call to given function. The types of the arguments has to match the argument typs of the function. And the return type of the called function has to match the type of the current function."}
, { 'CALL_TR', 16#05, 2, true, 8, [a,is], call_tr, "Remote tail call to given contract and function. The types of the arguments has to match the argument typs of the function. And the return type of the called function has to match the type of the current function."}
, { 'JUMP', 16#06, 1, true, 3, [ii], jump, "Jump to a basic block. The basic block has to exist in the current function."}
, { 'JUMPIF', 16#07, 2, true, 4, [a,ii], jumpif, "Conditional jump to a basic block. If Arg0 then jump to Arg1."}
, { 'SWITCH_V2', 16#08, 3, true, 4, [a,ii,ii], switch, "Conditional jump to a basic block on variant tag."}
, { 'SWITCH_V3', 16#09, 4, true, 4, [a,ii,ii,ii], switch, "Conditional jump to a basic block on variant tag."}
, { 'SWITCH_VN', 16#0a, 2, true, 4, [a, li], switch, "Conditional jump to a basic block on variant tag."}
, { 'PUSH', 16#0b, 1, false, 2, [a], push, "Push argument to stack."}
, { 'DUPA', 16#0c, 0, false, 3, atomic, dup, "push copy of accumulator on stack."}
, { 'DUP', 16#0d, 1, false, 3, [a], dup, "push Arg0 stack pos on top of stack."}
, { 'POP', 16#0e, 1, false, 3, [a], pop, "Arg0 := top of stack."}
, { 'STORE', 16#0f, 2, false, 3, [a,a], store, "Arg0 := Arg1."}
, { 'INCA', 16#10, 0, false, 2, atomic, inc, "Increment accumulator."}
, { 'INC', 16#11, 1, false, 2, [a], inc, "Increment argument."}
, { 'DECA', 16#12, 0, false, 2, atomic, dec, "Decrement accumulator."}
, { 'DEC', 16#13, 1, false, 2, [a], dec, "Decrement argument."}
, { 'ADD', 16#14, 3, false, 3, [a,a,a], add, "Arg0 := Arg1 + Arg2."}
, { 'SUB', 16#15, 3, false, 3, [a,a,a], sub, "Arg0 := Arg1 - Arg2."}
, { 'MUL', 16#16, 3, false, 3, [a,a,a], mul, "Arg0 := Arg1 * Arg2."}
, { 'DIV', 16#17, 3, false, 3, [a,a,a], divide, "Arg0 := Arg1 / Arg2."}
, { 'MOD', 16#18, 3, false, 3, [a,a,a], modulo, "Arg0 := Arg1 mod Arg2."}
, { 'POW', 16#19, 3, false, 3, [a,a,a], pow, "Arg0 := Arg1 ^ Arg2."}
, { 'LT', 16#20, 3, false, 3, [a,a,a], lt, "Arg0 := Arg1 < Arg2."}
, { 'GT', 16#21, 3, false, 3, [a,a,a], gt, "Arg0 := Arg1 > Arg2."}
, { 'EQ', 16#22, 3, false, 3, [a,a,a], eq, "Arg0 := Arg1 = Arg2."}
, { 'ELT', 16#23, 3, false, 3, [a,a,a], elt, "Arg0 := Arg1 =< Arg2."}
, { 'EGT', 16#24, 3, false, 3, [a,a,a], egt, "Arg0 := Arg1 >= Arg2."}
, { 'NEQ', 16#25, 3, false, 3, [a,a,a], neq, "Arg0 := Arg1 /= Arg2."}
, { 'AND', 16#26, 3, false, 3, [a,a,a], and_op, "Arg0 := Arg1 and Arg2."}
, { 'OR', 16#27, 3, false, 3, [a,a,a], or_op, "Arg0 := Arg1 or Arg2."}
, { 'NOT', 16#28, 2, false, 3, [a,a], not_op, "Arg0 := not Arg1."}
, { 'TUPLE', 16#29, 1, false, 3, [ii], tuple, "Create a tuple of size = Arg0. Elements on stack."}
, { 'ELEMENT', 16#2a, 3, false, 3, [a,a,a], element_op, "Arg1 := element(Arg2, Arg3)."}
, { 'MAP_EMPTY', 16#2b, 1, false, 3, [a], map_empty, "Arg0 := #{}."}
, { 'MAP_LOOKUP', 16#2c, 3, false, 3, [a,a,a], map_lookup, "Arg0 := lookup key Arg2 in map Arg1."}
, { 'MAP_LOOKUPD', 16#2d, 4, false, 3, [a,a,a,a], map_lookup, "Arg0 := lookup key Arg2 in map Arg1 if key exists in map otherwise Arg0 := Arg3."}
, { 'MAP_UPDATE', 16#2e, 4, false, 3, [a,a,a,a], map_update, "Arg0 := update key Arg2 in map Arg1 with value Arg3."}
, { 'MAP_DELETE', 16#2f, 3, false, 3, [a,a,a], map_delete, "Arg0 := delete key Arg2 from map Arg1."}
, { 'MAP_MEMBER', 16#30, 3, false, 3, [a,a,a], map_member, "Arg0 := true if key Arg2 is in map Arg1."}
, { 'MAP_FROM_LIST',16#31, 2, false, 3, [a,a], map_from_list, "Arg0 := make a map from (key, value) list in Arg1."}
, { 'NIL', 16#32, 1, false, 3, [a], nil, "Arg0 := []."}
, { 'IS_NIL', 16#33, 2, false, 3, [a,a], is_nil, "Arg0 := true if Arg1 == []."}
, { 'CONS', 16#34, 3, false, 3, [a,a,a], cons, "Arg0 := [Arg1|Arg2]."}
, { 'HD', 16#35, 2, false, 3, [a,a], hd, "Arg0 := head of list Arg1."}
, { 'TL', 16#36, 2, false, 3, [a,a], tl, "Arg0 := tail of list Arg1."}
, { 'LENGTH', 16#37, 2, false, 3, [a,a], length, "Arg0 := length of list Arg1."}
, { 'STR_EQ', 16#38, 3, false, 3, [a,a,a], str_eq, "Arg0 := true iff the strings Arg1 and Arg2 are the same."}
, { 'STR_JOIN', 16#39, 3, false, 3, [a,a,a], str_join, "Arg0 := string Arg1 followed by string Arg2."}
, { 'INT_TO_STR', 16#40, 2, false, 3, [a,a], int_to_str, "Arg0 := turn integer Arg1 into a string."}
, { 'ADDR_TO_STR', 16#41, 2, false, 3, [a,a], addr_to_str, "Arg0 := turn address Arg1 into a string."}
, { 'STR_REVERSE', 16#42, 2, false, 3, [a,a], str_reverse, "Arg0 := the reverse of string Arg1."}
, { 'INT_TO_ADDR', 16#43, 2, false, 3, [a,a], int_to_addr, "Arg0 := turn integer Arg1 into an address."}
, { 'VARIANT', 16#44, 4, false, 3, [a,a,a,a], variant, "Arg0 := create a variant of size Arg1 with the tag Arg2 (Arg2 < Arg1) and take Arg3 elements from the stack."}
, { 'VARIANT_TEST', 16#45, 3, false, 3, [a,a,a], variant_test, "Arg0 := true if variant Arg1 has the tag Arg2."}
, { 'VARIANT_ELEMENT',16#46, 3, false, 3, [a,a,a], variant_element, "Arg0 := element number Arg2 from variant Arg1."}
, { 'BITS_NONEA', 16#47, 0, false, 3, atomic, bits_none, "accumulator := empty bitmap."}
, { 'BITS_NONE', 16#48, 1, false, 3, [a], bits_none, "Arg0 := empty bitmap."}
, { 'BITS_ALLA', 16#49, 0, false, 3, atomic, bits_all, "accumulator := full bitmap."}
, { 'BITS_ALL', 16#50, 1, false, 3, [a], bits_all, "Arg0 := full bitmap."}
, { 'BITS_ALL_N', 16#51, 2, false, 3, [a,a], bits_all_n, "Arg0 := bitmap with Arg1 bits set."}
, { 'BITS_SET', 16#52, 3, false, 3, [a,a,a], bits_set, "Arg0 := set bit Arg2 of bitmap Arg1."}
, { 'BITS_CLEAR', 16#53, 3, false, 3, [a,a,a], bits_clear, "Arg0 := clear bit Arg2 of bitmap Arg1."}
, { 'BITS_TEST', 16#54, 3, false, 3, [a,a,a], bits_test, "Arg0 := true if bit Arg2 of bitmap Arg1 is set."}
, { 'BITS_SUM', 16#55, 2, false, 3, [a,a], bits_sum, "Arg0 := sum of set bits in bitmap Arg1. Exception if infinit bitmap."}
, { 'BITS_OR', 16#56, 3, false, 3, [a,a,a], bits_or, "Arg0 := Arg1 v Arg2."}
, { 'BITS_AND', 16#57, 3, false, 3, [a,a,a], bits_and, "Arg0 := Arg1 ^ Arg2."}
, { 'BITS_DIFF', 16#58, 3, false, 3, [a,a,a], bits_diff, "Arg0 := Arg1 - Arg2."}
, { 'ADDRESS', 16#59, 1, false, 3, [a], address, "Arg0 := The current contract address."}
, { 'BALANCE', 16#5a, 1, false, 3, [a], balance, "Arg0 := The current contract balance."}
, { 'ORIGIN', 16#5b, 1, false, 3, [a], origin, "Arg0 := Address of contract called by the call transaction."}
, { 'CALLER', 16#5c, 1, false, 3, [a], caller, "Arg0 := The address that signed the call transaction."}
, { 'GASPRICE', 16#5d, 1, false, 3, [a], gasprice, "Arg0 := The current gas price."}
, { 'BLOCKHASH', 16#5e, 2, false, 3, [a, a], blockhash, "Arg0 := The blockhash at height."}
, { 'BENEFICIARY', 16#5f, 1, false, 3, [a], beneficiary, "Arg0 := The address of the current beneficiary."}
, { 'TIMESTAMP', 16#60, 1, false, 3, [a], timestamp, "Arg0 := The current timestamp. Unrelaiable, don't use for anything."}
, { 'GENERATION', 16#61, 1, false, 3, [a], generation, "Arg0 := The block height of the cureent generation."}
, { 'MICROBLOCK', 16#62, 1, false, 3, [a], microblock, "Arg0 := The current micro block number."}
, { 'DIFFICULTY', 16#63, 1, false, 3, [a], difficulty, "Arg0 := The current difficulty."}
, { 'GASLIMIT', 16#64, 1, false, 3, [a], gaslimit, "Arg0 := The current gaslimit."}
, { 'GAS', 16#65, 1, false, 3, [a], gas, "Arg0 := The amount of gas left."}
%% Opname, Opcode, end_bb, in_auth offchain, gas, format, Constructor, ArgType, ResType, Documentation
[ { 'RETURN', 16#00, true, true, true, 2, [], 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, 2, [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, 4, [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, 8, [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, 4, [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."}
, { 'UNUSED_1', 16#05, false, false, true, 8, [], unused_1, {}, none, "Was CALL_TR."}
, { 'CALL_GR', 16#06, true, false, true, 8, [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."}
, { 'UNUSED_2', 16#07, false, false, true, 8, [], unused_2, {}, none, "Was CALL_GTR."}
, { 'JUMP', 16#08, true, true, true, 3, [ii], jump, {integer}, none, "Jump to a basic block. The basic block has to exist in the current function."}
, { 'JUMPIF', 16#09, true, true, true, 4, [a,ii], jumpif, {boolean, integer}, none, "Conditional jump to a basic block. If Arg0 then jump to Arg1."}
, { 'SWITCH_V2', 16#0a, true, true, true, 4, [a,ii,ii], switch, {variant, integer, ingeger}, none, "Conditional jump to a basic block on variant tag."}
, { 'SWITCH_V3', 16#0b, true, true, true, 4, [a,ii,ii,ii], switch, {variant, integer, integer, ingeger}, none, "Conditional jump to a basic block on variant tag."}
, { 'SWITCH_VN', 16#0c, true, true, true, 4, [a, li], switch, {variant, {list, integer}}, none, "Conditional jump to a basic block on variant tag."}
, { 'CALL_VALUE', 16#0d, false, true, true, 3, [a], call_value, {}, integer, "The value sent in the current remote call."}
, { 'PUSH', 16#0e, false, true, true, 2, [a], push, {any}, any, "Push argument to stack."}
, { 'DUPA', 16#0f, false, true, true, 3, [], dup, {any}, any, "Duplicate top of stack."}
, { 'DUP', 16#10, false, true, true, 3, [a], dup, {any}, any, "push Arg0 stack pos on top of stack."}
, { 'POP', 16#11, false, true, true, 3, [a], pop, {integer}, integer, "Arg0 := top of stack."}
, { 'INCA', 16#12, false, true, true, 2, [], inc, {integer}, integer, "Increment accumulator."}
, { 'INC', 16#13, false, true, true, 2, [a], inc, {integer}, integer, "Increment argument."}
, { 'DECA', 16#14, false, true, true, 2, [], dec, {integer}, integer, "Decrement accumulator."}
, { 'DEC', 16#15, false, true, true, 2, [a], dec, {integer}, integer, "Decrement argument."}
, { 'ADD', 16#16, false, true, true, 3, [a,a,a], add, {integer, integer}, integer, "Arg0 := Arg1 + Arg2."}
, { 'SUB', 16#17, false, true, true, 3, [a,a,a], sub, {integer, integer}, integer, "Arg0 := Arg1 - Arg2."}
, { 'MUL', 16#18, false, true, true, 3, [a,a,a], mul, {integer, integer}, integer, "Arg0 := Arg1 * Arg2."}
, { 'DIV', 16#19, false, true, true, 3, [a,a,a], divide, {integer, integer}, integer, "Arg0 := Arg1 / Arg2."}
, { 'MOD', 16#1a, false, true, true, 3, [a,a,a], modulo, {integer, integer}, integer, "Arg0 := Arg1 mod Arg2."}
, { 'POW', 16#1b, false, true, true, 3, [a,a,a], pow, {integer, integer}, integer, "Arg0 := Arg1 ^ Arg2."}
, { 'STORE', 16#1c, false, true, true, 3, [a,a], store, {any}, any, "Arg0 := Arg1."}
, { 'SHA3', 16#1d, false, true, true, 30, [a,a], sha3, {any}, hash, "Arg0 := sha3(Arg1)."}
, { 'SHA256', 16#1e, false, true, true, 30, [a,a], sha256, {any}, hash, "Arg0 := sha256(Arg1)."}
, { 'BLAKE2B', 16#1f, false, true, true, 30, [a,a], blake2b, {any}, hash, "Arg0 := blake2b(Arg1)."}
, { 'LT', 16#20, false, true, true, 3, [a,a,a], lt, {integer, integer}, boolean, "Arg0 := Arg1 < Arg2."}
, { 'GT', 16#21, false, true, true, 3, [a,a,a], gt, {integer, integer}, boolean, "Arg0 := Arg1 > Arg2."}
, { 'EQ', 16#22, false, true, true, 3, [a,a,a], eq, {integer, integer}, boolean, "Arg0 := Arg1 = Arg2."}
, { 'ELT', 16#23, false, true, true, 3, [a,a,a], elt, {integer, integer}, boolean, "Arg0 := Arg1 =< Arg2."}
, { 'EGT', 16#24, false, true, true, 3, [a,a,a], egt, {integer, integer}, boolean, "Arg0 := Arg1 >= Arg2."}
, { 'NEQ', 16#25, false, true, true, 3, [a,a,a], neq, {integer, integer}, boolean, "Arg0 := Arg1 /= Arg2."}
, { 'AND', 16#26, false, true, true, 3, [a,a,a], and_op, {boolean, boolean}, boolean, "Arg0 := Arg1 and Arg2."}
, { 'OR', 16#27, false, true, true, 3, [a,a,a], or_op, {boolean, boolean}, boolean, "Arg0 := Arg1 or Arg2."}
, { 'NOT', 16#28, false, true, true, 3, [a,a], not_op, {boolean}, boolean, "Arg0 := not Arg1."}
, { 'TUPLE', 16#29, false, true, true, 3, [a,ii], tuple, {integer}, tuple, "Arg0 := tuple of size = Arg1. Elements on stack."}
, { 'ELEMENT', 16#2a, false, true, true, 3, [a,a,a], element_op, {integer, tuple}, any, "Arg1 := element(Arg2, Arg3)."}
, { 'SETELEMENT', 16#2b, false, true, true, 3, [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#2c, false, true, true, 3, [a], map_empty, {}, map, "Arg0 := #{}."}
, { 'MAP_LOOKUP', 16#2d, false, true, true, 3, [a,a,a], map_lookup, {map, any}, any, "Arg0 := lookup key Arg2 in map Arg1."}
, { 'MAP_LOOKUPD', 16#2e, false, true, true, 3, [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#2f, false, true, true, 3, [a,a,a,a], map_update, {map, any, any}, map, "Arg0 := update key Arg2 in map Arg1 with value Arg3."}
, { 'MAP_DELETE', 16#30, false, true, true, 3, [a,a,a], map_delete, {map, any}, map, "Arg0 := delete key Arg2 from map Arg1."}
, { 'MAP_MEMBER', 16#31, false, true, true, 3, [a,a,a], map_member, {map, any}, boolean, "Arg0 := true if key Arg2 is in map Arg1."}
, { 'MAP_FROM_LIST', 16#32, false, true, true, 3, [a,a], map_from_list, {{list, {tuple, [any, any]}}}, map, "Arg0 := make a map from (key, value) list in Arg1."}
, { 'IS_NIL', 16#33, false, true, true, 3, [a,a], is_nil, {list}, boolean, "Arg0 := true if Arg1 == []."}
, { 'CONS', 16#34, false, true, true, 3, [a,a,a], cons, {any, list}, list, "Arg0 := [Arg1|Arg2]."}
, { 'HD', 16#35, false, true, true, 3, [a,a], hd, {list}, any, "Arg0 := head of list Arg1."}
, { 'TL', 16#36, false, true, true, 3, [a,a], tl, {list}, list, "Arg0 := tail of list Arg1."}
, { 'LENGTH', 16#37, false, true, true, 3, [a,a], length, {list}, integer, "Arg0 := length of list Arg1."}
, { 'NIL', 16#38, false, true, true, 3, [a], nil, {}, list, "Arg0 := []."}
, { 'STR_JOIN', 16#39, false, true, true, 3, [a,a,a], str_join, {string, string}, string, "Arg0 := string Arg1 followed by string Arg2."}
, { 'INT_TO_STR', 16#3a, false, true, true, 3, [a,a], int_to_str, {integer}, string, "Arg0 := turn integer Arg1 into a string."}
, { 'ADDR_TO_STR', 16#3b, false, true, true, 3, [a,a], addr_to_str, {address}, string, "Arg0 := turn address Arg1 into a string."}
, { 'STR_REVERSE', 16#3c, false, true, true, 3, [a,a], str_reverse, {string}, string, "Arg0 := the reverse of string Arg1."}
, { 'APPEND', 16#3d, false, true, true, 3, [a,a,a], append, {list, list}, list, "Arg0 := Arg1 ++ Arg2."}
, { 'INT_TO_ADDR', 16#3e, false, true, true, 3, [a,a], int_to_addr, {integer}, address, "Arg0 := turn integer Arg1 into an address."}
, { 'VARIANT', 16#3f, false, true, true, 3, [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#40, false, true, true, 3, [a,a,a], variant_test, {variant, integer}, boolean, "Arg0 := true if variant Arg1 has the tag Arg2."}
, { 'VARIANT_ELEMENT', 16#41, false, true, true, 3, [a,a,a], variant_element, {variant, integer}, any, "Arg0 := element number Arg2 from variant Arg1."}
, { 'BITS_NONEA', 16#42, false, true, true, 3, [], bits_none, {}, bits, "push an empty bitmap on the stack."}
, { 'BITS_NONE', 16#43, false, true, true, 3, [a], bits_none, {}, bits, "Arg0 := empty bitmap."}
, { 'BITS_ALLA', 16#44, false, true, true, 3, [], bits_all, {}, bits, "push a full bitmap on the stack."}
, { 'BITS_ALL', 16#45, false, true, true, 3, [a], bits_all, {}, bits, "Arg0 := full bitmap."}
, { 'BITS_ALL_N', 16#46, false, true, true, 3, [a,a], bits_all_n, {integer}, bits, "Arg0 := bitmap with Arg1 bits set."}
, { 'BITS_SET', 16#47, false, true, true, 3, [a,a,a], bits_set, {bits, integer}, bits, "Arg0 := set bit Arg2 of bitmap Arg1."}
, { 'BITS_CLEAR', 16#48, false, true, true, 3, [a,a,a], bits_clear, {bits, integer}, bits, "Arg0 := clear bit Arg2 of bitmap Arg1."}
, { 'BITS_TEST', 16#49, false, true, true, 3, [a,a,a], bits_test, {bits, integer}, boolean, "Arg0 := true if bit Arg2 of bitmap Arg1 is set."}
, { 'BITS_SUM', 16#4a, false, true, true, 3, [a,a], bits_sum, {bits}, integer, "Arg0 := sum of set bits in bitmap Arg1. Exception if infinit bitmap."}
, { 'BITS_OR', 16#4b, false, true, true, 3, [a,a,a], bits_or, {bits, bits}, bits, "Arg0 := Arg1 v Arg2."}
, { 'BITS_AND', 16#4c, false, true, true, 3, [a,a,a], bits_and, {bits, bits}, bits, "Arg0 := Arg1 ^ Arg2."}
, { 'BITS_DIFF', 16#4d, false, true, true, 3, [a,a,a], bits_diff, {bits, bits}, bits, "Arg0 := Arg1 - Arg2."}
, { 'BALANCE', 16#4e, false, true, true, 3, [a], balance, {}, integer, "Arg0 := The current contract balance."}
, { 'ORIGIN', 16#4f, false, true, true, 3, [a], origin, {}, address, "Arg0 := Address of contract called by the call transaction."}
, { 'CALLER', 16#50, false, true, true, 3, [a], caller, {}, address, "Arg0 := The address that signed the call transaction."}
, { 'GASPRICE', 16#51, false, true, true, 3, [a], gasprice, {}, integer, "Arg0 := The current gas price."}
, { 'BLOCKHASH', 16#52, false, true, true, 3, [a,a], blockhash, {integer}, hash, "Arg0 := The blockhash at height."}
, { 'BENEFICIARY', 16#53, false, true, true, 3, [a], beneficiary, {}, address, "Arg0 := The address of the current beneficiary."}
, { 'TIMESTAMP', 16#54, false, true, true, 3, [a], timestamp, {}, integer, "Arg0 := The current timestamp. Unrelaiable, don't use for anything."}
, { 'GENERATION', 16#55, false, true, true, 3, [a], generation, {}, integer, "Arg0 := The block height of the cureent generation."}
, { 'MICROBLOCK', 16#56, false, true, true, 3, [a], microblock, {}, integer, "Arg0 := The current micro block number."}
, { 'DIFFICULTY', 16#57, false, true, true, 3, [a], difficulty, {}, integer, "Arg0 := The current difficulty."}
, { 'GASLIMIT', 16#58, false, true, true, 3, [a], gaslimit, {}, integer, "Arg0 := The current gaslimit."}
, { 'GAS', 16#59, false, true, true, 3, [a], gas, {}, integer, "Arg0 := The amount of gas left."}
, { 'ADDRESS', 16#5a, false, true, true, 3, [a], address, {}, address, "Arg0 := The current contract address."}
, { 'LOG0', 16#66, 2, false, 3, [a,a], log, "Create a log message in the call object."}
, { 'LOG1', 16#67, 3, false, 3, [a,a,a], log, "Create a log message with one topic in the call object."}
, { 'LOG2', 16#68, 4, false, 3, [a,a,a,a], log, "Create a log message with two topics in the call object."}
, { 'LOG3', 16#69, 5, false, 3, [a,a,a,a,a], log, "Create a log message with three topics in the call object."}
, { 'LOG4', 16#6a, 6, false, 3, [a,a,a,a,a,a], log, "Create a log message with four topics in the call object."}
, { 'DEACTIVATE', 16#6b, 0, false, 3, atomic, deactivate, "Mark the current contract for deactication."}
, { 'LOG0', 16#5b, false, true, true, 3, [a], log, {string}, none, "Create a log message in the call object."}
, { 'LOG1', 16#5c, false, true, true, 3, [a,a], log, {integer, string}, none, "Create a log message with one topic in the call object."}
, { 'LOG2', 16#5d, false, true, true, 3, [a,a,a], log, {integer, integer, string}, none, "Create a log message with two topics in the call object."}
, { 'LOG3', 16#5e, false, true, true, 3, [a,a,a,a], log, {integer, integer, integer, string}, none, "Create a log message with three topics in the call object."}
, { 'LOG4', 16#5f, false, true, true, 3, [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#6c, 2, false,3, [a,a], spend, "Transfer Arg0 tokens to account Arg1. (If the contract account has at least that many tokens."}
, { 'ORACLE_REGISTER', 16#6d, 6, false,3, [a,a,a,a,a,a], oracle_register, "Mark the current contract for deactication."}
%% TODO:
, { 'ORACLE_QUERY', 16#6e, 0, false,3, atomic, oracle_query, ""}
, { 'ORACLE_RESPOND', 16#6f, 0, false,3, atomic, oracle_respond, ""}
, { 'ORACLE_EXTEND', 16#70, 0, false,3, atomic, oracle_extend, ""}
, { 'ORACLE_GET_ANSWER', 16#71, 0, false,3, atomic, oracle_get_answer, ""}
, { 'ORACLE_GET_QUESTION', 16#72, 0, false,3, atomic,oracle_get_question, ""}
, { 'ORACLE_QUERY_FEE', 16#73, 0, false,3, atomic, oracle_query_fee, ""}
, { 'AENS_RESOLVE', 16#74, 0, false,3, atomic, aens_resolve, ""}
, { 'AENS_PRECLAIM', 16#75, 0, false,3, atomic, aens_preclaim, ""}
, { 'AENS_CLAIM', 16#76, 0, false,3, atomic, aens_claim, ""}
, { 'AENS_UPDATE', 16#77, 0, false,3, atomic, aend_update, ""}
, { 'AENS_TRANSFER', 16#78, 0, false,3, atomic, aens_transfer, ""}
, { 'AENS_REVOKE', 16#79, 0, false,3, atomic, aens_revoke, ""}
, { 'ECVERIFY', 16#7a, 0, false,3, atomic, ecverify, ""}
, { 'SHA3', 16#7b, 0, false,3, atomic, sha3, ""}
, { 'SHA256', 16#7c, 0, false,3, atomic, sha256, ""}
, { 'BLAKE2B', 16#7d, 0, false,3, atomic, blake2b, ""}
, { 'BALANCE_OTHER', 16#7e, 2, false,3, [a,a], balance_other, "Arg0 := The balance of address Arg1."}
, { 'SETELEMENT', 16#7f, 4, false,3, [a,a,a,a], setelement, "Arg0 := a new tuple similar to Arg2, but with element number Arg1 replaced by Arg3."}
, { 'SPEND', 16#60, false, false, true, 3, [a,a], spend, {address, integer}, none, "Transfer Arg1 tokens to account Arg0. (If the contract account has at least that many tokens."}
, { 'ORACLE_REGISTER', 16#61, false, false, false, 3, [a,a,a,a,a,a,a], oracle_register, {signature, address, integer, variant, typerep, typerep}, oracle, "Arg0 := New oracle with address Arg2, query fee Arg3, TTL Arg4, query type Arg5 and response type Arg6. Arg0 contains delegation signature."}
, { 'ORACLE_QUERY', 16#62, false, false, false, 3, [a,a,a,a,a,a,a,a], oracle_query, {oracle, any, integer, variant, variant, typerep, typerep}, oracle_query, "Arg0 := New oracle query for oracle Arg1, question in Arg2, query fee in Arg3, query TTL in Arg4, response TTL in Arg5. Typereps for checking oracle type is in Arg6 and Arg7."}
, { 'ORACLE_RESPOND', 16#63, false, false, false, 3, [a,a,a,a,a,a], oracle_respond, {signature, oracle, oracle_query,any, typerep, typerep}, none, "Respond as oracle Arg1 to query in Arg2 with response Arg3. Arg0 contains delegation signature. Typereps for checking oracle type is in Arg4 and Arg5."}
, { 'ORACLE_EXTEND', 16#64, false, false, false, 3, [a,a,a], oracle_extend, {signature, oracle, variant}, none, "Extend oracle in Arg1 with TTL in Arg2. Arg0 contains delegation signature."}
, { 'ORACLE_GET_ANSWER', 16#65, false, false, true, 3, [a,a,a,a,a], oracle_get_answer, {oracle, oracle_query, typerep, typerep}, any, "Arg0 := option variant with answer (if any) from oracle query in Arg1 given by oracle Arg0. Typereps for checking oracle type is in Arg3 and Arg4."}
, { 'ORACLE_GET_QUESTION', 16#66, false, false, true, 3, [a,a,a,a,a], oracle_get_question, {oracle, oracle_query, typerep, typerep}, any, "Arg0 := question in oracle query Arg2 given to oracle Arg1. Typereps for checking oracle type is in Arg3 and Arg4."}
, { 'ORACLE_QUERY_FEE', 16#67, false, false, true, 3, [a,a], oracle_query_fee, {oracle}, integer, "Arg0 := query fee for oracle Arg1"}
, { 'AENS_RESOLVE', 16#68, false, false, true, 3, [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#69, false, false, false, 3, [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#6a, false, false, false, 3, [a,a,a,a], aens_claim, {signature, address, string, integer}, none, "Claim the name in Arg2 for address in Arg1. Arg3 contains the salt used to hash the preclaim. Arg0 contains delegation signature."}
, { 'AENS_UPDATE', 16#6b, false, false, false, 3, [], aens_update, {}, none, "NYI"}
, { 'AENS_TRANSFER', 16#6c, false, false, false, 3, [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#6d, false, false, false, 3, [a,a,a], aens_revoke, {signature, address, string}, none, "Revoke the name in Arg2 from owner Arg1. Arg0 contains delegation signature."}
, { 'BALANCE_OTHER', 16#6e, false, true, true, 3, [a,a], balance_other, {address}, integer, "Arg0 := The balance of address Arg1."}
%% TODO: Reorder these before documenting the specification
, { 'MAP_SIZE', 16#6f, false, true, true, 3, [a,a], map_size_, {map}, integer, "Arg0 := The size of the map Arg1."}
, { 'MAP_TO_LIST', 16#70, false, true, true, 3, [a,a], map_to_list, {map}, list, "Arg0 := The tuple list representation of the map Arg1."}
, { 'STR_LENGTH', 16#71, false, true, true, 3, [a,a], str_length, {string}, integer, "Arg0 := The length of the string Arg1."}
, { 'VERIFY_SIG', 16#72, false, true, true, 1300, [a,a,a,a], verify_sig, {bytes, address, bytes}, boolean, "Arg0 := verify_sig(Hash, PubKey, Signature)"}
, { 'VERIFY_SIG_SECP256K1', 16#73, false, true, true, 1300, [a,a,a,a], verify_sig_secp256k1, {bytes, bytes, bytes}, boolean, "Arg0 := verify_sig_secp256k1(Hash, PubKey, Signature)"}
, { 'DUMMY7ARG', 16#f9, 7, false,3, [a,a,a,a,a,a,a], dummyarg, "Temporary dummy instruction to test 7 args."}
, { 'DUMMY8ARG', 16#fa, 8, false,3, [a,a,a,a,a,a,a,a],dummyarg, "Temporary dummy instruction to test 8 args."}
, { 'ABORT', 16#fb, 1, false, 3, [a], abort, "Abort execution (dont use all gas) with error message in Arg0."}
, { 'EXIT', 16#fc, 1, false, 3, [a], exit, "Abort execution (use upp all gas) with error message in Arg0."}
, { 'NOP', 16#fd, 0, false, 1, atomic, nop, "The no op. does nothing."}
, { 'CONTRACT_TO_ADDRESS', 16#74, false, true, true, 3, [a,a], contract_to_address, {contract}, address, "Arg0 := Arg1 - A no-op type conversion"}
, { 'AUTH_TX_HASH', 16#75, false, true, true, 3, [a], auth_tx_hash, {}, variant, "If in GA authentication context return Some(TxHash) otherwise None."}
, { 'BYTES_TO_INT', 16#76, false, true, true, 3, [a,a], bytes_to_int, {bytes}, integer, "Arg0 := bytes_to_int(Arg1)"}
, { 'BYTES_TO_STR', 16#77, false, true, true, 3, [a,a], bytes_to_str, {bytes}, string, "Arg0 := bytes_to_str(Arg1)"}
, { 'ORACLE_CHECK', 16#78, false, false, true, 3, [a,a,a,a], oracle_check, {oracle, typerep, typerep}, bool, "Arg0 := is Arg1 an oracle with the given query (Arg2) and response (Arg3) types"}
, { 'ORACLE_CHECK_QUERY', 16#79, false, false, true, 3, [a,a,a,a,a], oracle_check_query, {oracle, oracle_query, typerep, typerep}, bool, "Arg0 := is Arg2 a query for the oracle Arg1 with the given types (Arg3, Arg4)"}
, { 'IS_ORACLE', 16#7a, false, false, true, 3, [a,a], is_oracle, {address}, bool, "Arg0 := is Arg1 an oracle"}
, { 'IS_CONTRACT', 16#7b, false, false, true, 3, [a,a], is_contract, {address}, bool, "Arg0 := is Arg1 a contract"}
, { 'IS_PAYABLE', 16#7c, false, false, true, 3, [a,a], is_payable, {address}, bool, "Arg0 := is Arg1 a payable address"}
, { 'CREATOR', 16#7d, false, true, true, 3, [a], contract_creator, {}, address, "Arg0 := contract creator"}
, { 'ECVERIFY_SECP256K1', 16#7e, false, true, true, 1300, [a,a,a,a], ecverify_secp256k1, {bytes, bytes, bytes}, bytes, "Arg0 := ecverify_secp256k1(Hash, Addr, Signature)"}
, { 'ECRECOVER_SECP256K1', 16#7f, false, true, true, 1300, [a,a,a], ecrecover_secp256k1, {bytes, bytes}, bytes, "Arg0 := ecrecover_secp256k1(Hash, Signature)"}
, { 'AENS_SUBNAME', 16#80, false, false, false, 3, [a,a,a,a], aens_subname, {signature, address, string, map}, none, "Claim subbnames of name in Arg2 from owner Arg1. Arg0 contains delegation signature. Arg3 is map describing subname prefixes to claim and their pointers."}
, { 'DEACTIVATE', 16#fa, false, true, true, 3, [], deactivate, {}, none, "Mark the current contract for deactivation."}
, { 'ABORT', 16#fb, true, true, true, 3, [a], abort, {string}, none, "Abort execution (dont use all gas) with error message in Arg0."}
, { 'EXIT', 16#fc, true, true, true, 3, [a], exit, {string}, none, "Abort execution (use upp all gas) with error message in Arg0."}
, { 'NOP', 16#fd, false, true, true, 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."
].
@@ -171,11 +208,15 @@ generate_opcodes_ops(Modulename, HrlFile, SrcDir, 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]),
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"
])]),
@@ -192,6 +233,12 @@ generate_opcodes_ops(Modulename, HrlFile, SrcDir, Ops) ->
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", []),
file:close(File).
generate_code_ops(Modulename, SrcDir, Ops) ->
@@ -215,7 +262,7 @@ generate_code_ops(Modulename, SrcDir, Ops) ->
"-type fate_arg_immediate(T) :: {immediate, T}.\n"
"-type fate_arg_var() :: {var, integer()}.\n"
"-type fate_arg_arg() :: {arg, integer()}.\n"
"-type fate_arg_stack() :: {stack, integer()}.\n"
"-type fate_arg_stack() :: {stack, 0}.\n"
"-type fate_arg() :: fate_arg_immediate()\n"
" | fate_arg_var()\n"
" | fate_arg_arg()\n"
@@ -234,7 +281,7 @@ generate_code_ops(Modulename, SrcDir, Ops) ->
file:close(File).
gen_type(#{type_name := TypeName, type := Type}) ->
lists:flatten(io_lib:format("-type ~-26s :: ~s.\n",
lists:flatten(io_lib:format("-type ~-29s :: ~s.\n",
[TypeName, Type])).
gen_fate_code_type(#{type_name := TypeName}) ->
@@ -246,7 +293,7 @@ gen_type_exports(#{type_name := TypeName}) ->
gen_constructor_exports(#{constructor_type := Function}) ->
lists:flatten(io_lib:format(" , ~s\n", [Function])).
gen_constructors(#{constructor := Function, format := atomic,
gen_constructors(#{constructor := Function, format := [],
type_name := Type, opname := Name}) ->
lists:flatten(io_lib:format("-spec ~s() -> ~s.\n"
"~s() ->\n"
@@ -287,7 +334,7 @@ gen_arg_uses(_, []) ->
gen_arg_uses(N, [a]) -> io_lib:format("Arg~w", [N]);
gen_arg_uses(N, [is]) -> io_lib:format("{immediate, Arg~w}", [N]);
gen_arg_uses(N, [ii]) -> io_lib:format("{immediate, Arg~w}", [N]);
gen_arg_uses(N, [li]) -> io_lib:format("[{immediate, I} || I <- Arg~w]", [N]);
gen_arg_uses(N, [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);
@@ -312,22 +359,28 @@ ops_exports(Module, HrlFile, Exports) ->
[Module, Exports])).
gen_mnemonic(#{opname := Name, macro := Macro}) ->
lists:flatten(io_lib:format("mnemonic(~21s) -> ~21w ;\n",
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(~21w) -> ~21s ;\n",
lists:flatten(io_lib:format("m_to_op(~24w) -> ~24s ;\n",
[Name, Macro])).
gen_args(#{macro := Macro, args := Args}) ->
lists:flatten(io_lib:format("args(~21s) -> ~2w ;\n",
[Macro, Args])).
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(~21s) -> ~w ;\n",
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])).
prelude(Doc) ->
"%%%-------------------------------------------------------------------\n"
@@ -343,26 +396,29 @@ prelude(Doc) ->
gen_defines(#{opname := Name, opcode := OpCode}) ->
lists:flatten(io_lib:format("-define(~-26w, 16#~2.16.0b).\n", [Name, OpCode])).
lists:flatten(io_lib:format("-define(~-29w, 16#~2.16.0b).\n", [Name, OpCode])).
gen([]) ->
[];
gen([{OpName, OpCode, Args, EndBB, Gas, FateFormat, Constructor, Doc} | Rest]) ->
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
atomic -> io_lib:format("~w", [OpName]);
ArgTypes ->
io_lib:format("{~w, ~s}", [OpName, expand_types(ArgTypes)])
[] -> 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", [Args]),
ConstructorType = atom_to_list(Constructor) ++ "/" ++ io_lib:format("~w", [Arity]),
[#{ opname => OpName
, opcode => OpCode
, args => Args
, arity => Arity
, end_bb => EndBB
, in_auth => InAuth
, offchain => AllowedOffchain
, format => FateFormat
, macro => Macro
, type_name => TypeName
@@ -371,6 +427,8 @@ gen([{OpName, OpCode, Args, EndBB, Gas, FateFormat, Constructor, Doc} | Rest]) -
, type => Type
, constructor => Constructor
, constructor_type => ConstructorType
, arg_types => ArgTypes
, res_type => ResType
}| gen(Rest)].
@@ -381,7 +439,7 @@ expand_types([T|Ts]) ->expand_type(T) ++ ", " ++ expand_types(Ts).
expand_type(a) -> "fate_arg()";
expand_type(is) -> "fate_arg_immediate(aeb_fate_data:fate_string())";
expand_type(ii) -> "fate_arg_immediate(aeb_fate_data:fate_integer())";
expand_type(li) -> "[fate_arg_immediate(aeb_fate_data:fate_integer())]";
expand_type(li) -> "fate_arg_immediate([aeb_fate_data:fate_integer()])";
expand_type(t) -> "aeb_fate_data:fate_type_type()".
generate_scanner(TemplateFile, Outfile, Path, Ops) ->
@@ -423,28 +481,48 @@ gen_asm_pp(Module, Path, Ops) ->
"format_arg(_, {immediate, I}) ->\n"
" aeb_fate_data:format(I);\n"
"format_arg(a, {arg, N}) -> io_lib:format(\"arg~~p\", [N]);\n"
"format_arg(a, {var, N}) 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"
"format_arg(a, {stack, N}) -> io_lib:format(\"a~~p\", [N]).\n\n"
"format_arg(a, {stack, 0}) -> \"a\".\n\n"
"lookup(Name, Symbols) ->\n"
" maps:get(Name, Symbols, io_lib:format(\"~~w\",[Name])).\n\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 ('CALL' =:= Name) or (Name =:= 'CALL_T') ->
io_lib:format("format_op({~w, {immediate, Function}}, Symbols) ->\n"
"[\"~s \", lookup(Function, Symbols)];",
[Name, atom_to_list(Name)]);
gen_format(#{opname := Name}) when (Name =:= 'CALL_R') or (Name =:= 'CALL_TR') ->
io_lib:format("format_op({~w, {immediate, Contract}, {immediate, Function}}, Symbols) ->\n"
"[\"~s \", lookup(Contract, Symbols), \".\", lookup(Function, Symbols)];\n"
"format_op({~w, Contract, {immediate, Function}}, Symbols) ->\n"
"[\"~s \", format_arg(a, Contract), \".\", lookup(Function, Symbols)];",
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, format := atomic}) ->
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),
@@ -533,7 +611,7 @@ test_asm_generator(Filename) ->
file:close(File).
gen_instruction(#{opname := Name, format := atomic}) ->
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])),
@@ -549,7 +627,7 @@ gen_arg(t) -> "integer".
any_arg() ->
element(rand:uniform(5), {"a", stack_arg(), var_arg(), arg_arg(), imm_arg()}).
stack_arg() -> "a" ++ integer_to_list(rand:uniform(255)-1).
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() ->
@@ -640,7 +718,7 @@ gen_variant() ->
end.
%% TODO: add gas cost.
%% TODO: add gas cost and end_bb/in_auth?
generate_documentation(Filename) ->
{ok, File} = file:open(Filename, [write]),
Instructions = lists:flatten([gen_doc(Op)++"\n" || Op <- get_ops()]),
@@ -655,7 +733,7 @@ generate_documentation(Filename) ->
gen_doc(#{ opname := Name
, opcode := OpCode
, args := _Args
, arity := _Arity
, end_bb := _EndBB
, format := FateFormat
, macro := _Macro
@@ -668,7 +746,7 @@ gen_doc(#{ opname := Name
}) ->
Arguments =
case FateFormat of
atomic -> "";
[] -> "";
_ -> lists:join(" ",
[format_arg_doc(A) ||
A <-
@@ -686,4 +764,3 @@ format_arg_doc({is,_N}) -> "Identifier";
format_arg_doc({ii,_N}) -> "Integer";
format_arg_doc({li,_N}) -> "[Integers]";
format_arg_doc({t,_N}) -> "Type".
+214
View File
@@ -0,0 +1,214 @@
%%%-------------------------------------------------------------------
%%% @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(aeb_fate_maps).
-include("aeb_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!
-define(STORE_MAP_THRESHOLD, 500).
-type fate_value() :: aeb_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() => aeb_fate_data:fate_map() | aeb_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(aeb_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()) -> aeb_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]).
+3
View File
@@ -51,6 +51,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 +192,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 +334,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 ;
+9 -12
View File
@@ -25,12 +25,7 @@ read_file(File) ->
Asm.
assemble(Asm) ->
{Env, BC} = aeb_fate_asm:asm_to_bytecode(Asm, []),
{Env, BC}.
disassemble(BC) ->
aeb_fate_asm:bytecode_to_fate_code(BC, []).
aeb_fate_asm:asm_to_bytecode(Asm, []).
asm_disasm_idenity_test() ->
check_roundtrip(identity).
@@ -52,18 +47,20 @@ sources() ->
, "tuple"
, "mapofmap"
, "immediates"
, "all_instructions"
, "names"
, "oracles"
%% , "all_instructions"
].
check_roundtrip(File) ->
AssemblerCode = read_file(File),
{_Env, ByteCode} = assemble(AssemblerCode),
FateCode = disassemble(ByteCode),
FateCode = aeb_fate_code:deserialize(ByteCode),
DissasmCode = aeb_fate_asm:to_asm(FateCode),
io:format("~s~n", [AssemblerCode]),
io:format("~s~n", [DissasmCode]),
{_Env2, ByteCode2} = assemble(DissasmCode),
ByteCode3 = aeb_fate_code:serialize(FateCode),
Code1 = aeb_fate_asm:strip(ByteCode),
Code2 = aeb_fate_asm:strip(ByteCode2),
io:format("~s~n", [aeb_fate_asm:to_asm(disassemble(ByteCode2))]),
?assertEqual(Code1, Code2).
Code3 = aeb_fate_asm:strip(ByteCode3),
?assertEqual(Code1, Code2),
?assertEqual(Code1, Code3).
-1
View File
@@ -50,7 +50,6 @@ sources() ->
aeb_fate_data:make_hash(<<1,2,3,4,5>>),
aeb_fate_data:make_signature(<<1,2,3,4,5>>),
aeb_fate_data:make_contract(<<1,2,3,4,5>>),
aeb_fate_data:make_name(<<1,2,3,4,5>>),
aeb_fate_data:make_channel(<<1,2,3,4,5>>),
aeb_fate_data:make_list([True]),
aeb_fate_data:make_address(
+74 -50
View File
@@ -1,26 +1,30 @@
;; CONTRACT all_instructions
;; Dont expect this contract to typecheck or run.
;; Just used to check assembler rountrip of all instruction.
;; Just used to check assembler roundtrip of all instructions.
FUNCTION foo () : {tuple, []}
RETURN
RETURNR a13
RETURNR a
CALL foo
CALL "foo"
CALL_R arg125 foo
CALL_R arg125 foo 0
CALL_T foo
CALL_T "foo"
CALL_TR arg245 foo
CALL_TR arg245 foo 4711
CALL_GTR arg245 foo 0 100
CALL_GR arg245 foo 0 4711
JUMP 5514251025295783441695716053282666408426
JUMPIF arg196 0x12c651665
SWITCH_V2 a27 63 33
SWITCH_V2 a 63 33
SWITCH_V3 var4 0x1d61723dd 79 7
@@ -32,29 +36,29 @@ FUNCTION foo () : {tuple, []}
DUP a
POP a107
POP a
STORE arg183 var225
INCA
INC a25
INC a
DECA
DEC a
ADD a217 a a
ADD a a a
SUB arg35 arg165 var74
MUL 44 35 "foo"
DIV 263838340369912686645632650718169038811 a24 a
DIV 263838340369912686645632650718169038811 a a
MOD var113 arg80 arg207
POW a176 a a123
POW a a a
LT a 78 var81
@@ -62,11 +66,11 @@ FUNCTION foo () : {tuple, []}
EQ 85 a arg164
ELT a161 arg226 a168
ELT a arg226 a
EGT a131 1 var250
EGT a 1 var250
NEQ a85 a a83
NEQ a a a
AND var255 0x294a24f6 var189
@@ -74,37 +78,41 @@ FUNCTION foo () : {tuple, []}
NOT arg124 a
TUPLE 5019186157739257888756115213149493826410
TUPLE var999 5019186157739257888756115213149493826410
ELEMENT arg148 var25 a219
ELEMENT arg148 var25 a
MAP_EMPTY a135
MAP_EMPTY a
MAP_LOOKUP a82 a a143
MAP_LOOKUP a a a
MAP_LOOKUPD var112 arg35 a163 var112
MAP_LOOKUPD var112 arg35 a var112
MAP_UPDATE false a0 a56 a
MAP_UPDATE false a a a
MAP_DELETE arg180 a var1
MAP_MEMBER a { true => 4} 94
MAP_FROM_LIST () a159
MAP_FROM_LIST () a
MAP_TO_LIST a { true => 4 }
MAP_SIZE a { true => 42 }
NIL arg91
IS_NIL a121 var6
IS_NIL a var6
CONS arg185 "foo" a114
CONS arg185 "foo" a
HD a150 var124
HD a var124
TL arg223 a
LENGTH var216 a143
LENGTH var216 a
STR_EQ { 203961992615221001243597889146034217896 => 0x1f53a1843} 281217554184165828643225535776787296845 a177
APPEND { 203961992615221001243597889146034217896 => 0x1f53a1843} 281217554184165828643225535776787296845 a
STR_JOIN a a 7144184027126178769820155907121270843348
@@ -112,15 +120,17 @@ FUNCTION foo () : {tuple, []}
ADDR_TO_STR a arg216
STR_REVERSE a174 @ak_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
STR_REVERSE a @ak_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
STR_LENGTH a "foo"
INT_TO_ADDR arg127 var207
VARIANT a a 0x1f7b72200 a
VARIANT_TEST a26 arg217 a
VARIANT_TEST a arg217 a
VARIANT_ELEMENT a86 arg103 arg108
VARIANT_ELEMENT a arg103 arg108
BITS_NONEA
@@ -128,36 +138,38 @@ FUNCTION foo () : {tuple, []}
BITS_ALLA
BITS_ALL a164
BITS_ALL a
BITS_ALL_N a221 arg135
BITS_ALL_N a arg135
BITS_SET arg150 a48 { 0x1a715e2a6 => 3}
BITS_SET arg150 a { 0x1a715e2a6 => 3}
BITS_CLEAR arg98 a arg164
BITS_TEST a a242 (| [0,0,3] | 2 | (1, "foo", ()) |)
BITS_TEST a a (| [0,0,3] | 2 | (1, "foo", ()) |)
BITS_SUM a244 a71
BITS_SUM a a
BITS_OR var20 var186 a
BITS_AND a187 4 arg203
BITS_AND a 4 arg203
BITS_DIFF var200 arg247 var20
ADDRESS a237
ADDRESS a
BALANCE a231
BALANCE a
ORIGIN arg216
CALLER a27
CALLER a
GASPRICE arg119
BLOCKHASH a arg110
CALL_VALUE a
BENEFICIARY var163
TIMESTAMP a
@@ -172,15 +184,15 @@ FUNCTION foo () : {tuple, []}
GAS var35
LOG0 a a85
LOG0 a
LOG1 arg94 arg86 arg208
LOG1 arg86 arg208
LOG2 a113 (| [0,1,3] | 2 | (1, "foo", ()) |) arg238 var108
LOG2 a a (| [0,1,3] | 2 | (1, "foo", ()) |)
LOG3 arg255 arg15 arg211 var139 arg44
LOG3 arg15 arg211 var139 arg44
LOG4 @ak_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv a247 a 9 a38 a
LOG4 a a 9 a a
DEACTIVATE
@@ -212,17 +224,19 @@ FUNCTION foo () : {tuple, []}
AENS_REVOKE
ECVERIFY
ECRECOVER_SECP256K1
SHA3
VERIFY_SIG
SHA256
VERIFY_SIG_SECP256K1
BLAKE2B
ECVERIFY_SECP256K1
DUMMY7ARG a a 7607708484837907159893701471377343595877 (| [2,1] | 0 | ( [], [ 45, { 1 => 3441201581501946066216994494994943246334} ] ) |) a0 var56 "foo"
SHA3 a
DUMMY8ARG 3673679924816289365509492271980889822579 a69 arg242 var237 a175 arg106 () var255
SHA256 a
BLAKE2B a
ABORT a
@@ -235,3 +249,13 @@ FUNCTION foo () : {tuple, []}
BALANCE_OTHER a arg0
SETELEMENT a 2 (1, "two", 3) 2
AUTH_TX_HASH
CONTRACT_TO_ADDRESS @ct_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
IS_ORACLE @ak_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
IS_CONTRACT @ak_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
IS_PAYABLE @ak_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
+9
View File
@@ -68,6 +68,15 @@ FUNCTION tuple() : {tuple, [integer, boolean, string, {tuple, [integer, integer]
FUNCTION address() : address
RETURNR @ak_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
FUNCTION oracle() : oracle
RETURNR @ok_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 | () |)
+1 -1
View File
@@ -2,7 +2,7 @@
FUNCTION call(integer):integer
STORE var1 arg0
PUSH 0
CALL write
CALL "write"
PUSH var1
RETURN
+23
View File
@@ -0,0 +1,23 @@
;; CONTRACT names
FUNCTION preclaim(address, {bytes, 32}) : {tuple, []}
AENS_PRECLAIM #AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== arg0 arg1
RETURNR {}
FUNCTION claim(address, string, integer) : {tuple, []}
AENS_CLAIM #AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== arg0 arg1 arg2
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
+32
View File
@@ -0,0 +1,32 @@
;; CONTRACT oracles
FUNCTION register (address, integer, {variant, [{tuple, [integer]}, {tuple, [integer]}]}) : oracle
ORACLE_REGISTER a #AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== arg0 arg1 arg2 'string '{variant, [{tuple, []}, {tuple, [integer]}]}
RETURN
FUNCTION query (oracle, integer, string) : oracle_query
ORACLE_QUERY a arg0 arg1 arg2 (| [1,1] | 0 | (100) |) (| [1,1] | 0 | (100) |) 'string '{variant, [{tuple, []}, {tuple, [integer]}]}
RETURN
FUNCTION bogus_query () : oracle_query
RETURNR @oq_nv5B93FPzRHrGNmMdTDfGdd5xGZvep3MVSpJqzcQmMp59bBCv
FUNCTION respond (oracle, integer, string) : {tuple, []}
ORACLE_RESPOND #AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== arg0 arg1 arg2 'string '{variant, [{tuple, []}, {tuple, [integer]}]}
RETURNR {}
FUNCTION extend (oracle, {variant, [{tuple, [integer]}, {tuple, [integer]}]}) : {tuple, []}
ORACLE_EXTEND #AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== arg0 arg1
RETURNR {}
FUNCTION get_question (oracle, oracle_query) : string
ORACLE_GET_QUESTION a arg0 arg1 'string '{variant, [{tuple, []}, {tuple, [integer]}]}
RETURN
FUNCTION get_answer (oracle, oracle_query) : {variant, [{tuple, []}, {tuple, [string]}]}
ORACLE_GET_ANSWER a arg1 arg2 'string '{variant, [{tuple, []}, {tuple, [integer]}]}
RETURN
FUNCTION query_fee (oracle) : integer
ORACLE_QUERY_FEE a arg0
RETURN
+7 -11
View File
@@ -19,24 +19,20 @@ 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
+4 -4
View File
@@ -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,14 +18,14 @@ 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
TUPLE a 2
ELEMENT a 1 a
RETURN