Compare commits

..

95 Commits

Author SHA1 Message Date
Hans Svensson dcae96ed21 Merge pull request #70 from aeternity/PT-166147620-prepare_v3
PT-166147620 Prepare for v3.0.0
2019-05-21 10:37:41 +02:00
Hans Svensson 94689dd0e9 Prepare for v3.0.0 2019-05-21 09:55:17 +02:00
Hans Svensson be7c0e1bd4 Remove escript aesophia 2019-05-21 09:54:42 +02:00
Ulf Norell 8eaafa736c Merge pull request #69 from aeternity/PT-162578475-stateful
Check stateful annotations
2019-05-15 15:44:37 +02:00
Ulf Norell cf5a8aeb5f changelog 2019-05-14 10:10:53 +02:00
Ulf Norell 9e555a3121 Fix type definition 2019-05-14 09:42:27 +02:00
Ulf Norell d051fa6c89 Remove bad code 2019-05-14 09:42:17 +02:00
Tobias Lindahl d4238c0bdc Merge pull request #68 from aeternity/PT-165964555-make-remote-calls-to-contract-objects
Keep the contract type in fate code
2019-05-14 09:41:31 +02:00
Ulf Norell 389072fb12 Add stateful to __call 2019-05-14 09:32:52 +02:00
Tobias Lindahl 1760593170 Keep the contract type in fate code 2019-05-14 09:29:52 +02:00
Ulf Norell d8dd6b900f Remove unused test contract 2019-05-13 17:51:56 +02:00
Ulf Norell 74d4048d9f Check that init doesn't read or write the state 2019-05-13 17:51:47 +02:00
Ulf Norell 6bd2b7c483 Remember source location when computing used names 2019-05-13 17:50:34 +02:00
Ulf Norell 5aed8b3ef5 Check stateful annotations
Functions must be annotated as `stateful` in order to
- Update the contract state (using `put`)
- Call `Chain.spend` or other primitive functions that cost tokens
- Call an Oracle or AENS function that requires a signature
- Make a remote call with a non-zero value
- Construct a lambda calling a stateful function

It does not need to be stateful to
- Read the contract state
- Call another contract with value=0, even when the remote function is stateful
2019-05-13 13:39:17 +02:00
Ulf Norell e1a798aef4 Check for repeated argument names to functions
(PT-159592825)
2019-05-10 15:51:42 +02:00
Ulf Norell 23cc8e1132 Letrec and letfun (#65)
* Type check and compile letfuns

* Minor code simplification

* Remove let rec from Sophia
2019-05-10 13:27:57 +02:00
Tobias Lindahl 691ae72fbb Merge pull request #66 from aeternity/PT-165857097-add-call-value-instruction
Add call value instruction in fate
2019-05-10 13:15:22 +02:00
Tobias Lindahl 8830095c7e Add call value instruction in fate 2019-05-10 12:38:58 +02:00
Tobias Lindahl 251b876495 Add value and gas to remote calls (#64)
* Add value and gas to remote calls
2019-05-10 09:00:36 +02:00
Hans Svensson f8ee8f7129 Merge pull request #63 from aeternity/PT-165440601-165713319-sophia_addons
Add Contract.creator and address checking primitives
2019-05-09 10:34:45 +02:00
Hans Svensson 192ec207a7 Add Contract.creator and address checking primitives 2019-05-09 09:54:04 +02:00
Ulf Norell 0aa1c89556 Fate compiler (#62)
* Update to changes in icode format

* Start on new intermediate code for FATE

* Compile `let` to FATE

* Fix and improve broken bytecode optimisations

* Basic tuple patterns

* Compile shallow matching on tuples

* Liveness analysis for local variables

* Fix minor bug

* Use RETURNR when possible

* Nicer debug printing

* Refactor optimization rules

* Compile tuple construction

* Improve instruction analysis and generalize some optimizations

* Compile nested pattern matching to case trees

(Only tuple and variable patterns so far)

* Reannotate and repeat optimization pass once it done

Could try hard to keep annotations more precise, but would be more error prone

* Get rid of unnecessary STORE instructions

* Keep better track of liveness annotations when swapping instructions

* Limit the number of iterations for the optimization loop

Should finish in one iteration, but we shouldn't loop if there are bugs
or corner cases where it doesn't.

* Pattern matching on booleans

* wip: rewrite case tree compiler to handle catch-alls

still with debug printing, and can't compile it yet

* Add missing case in renaming

* Compile case trees all the way to Fate assembly

* Simplify variables bindings in environment

* Shortcut let x = y in ...

* compile list literals

* Fix various bugs in pattern match compilation

* Pretty printer for fcode

* Fix renaming bug

* Another renaming bug

* Handle switch_body in optimizations

* Remove optimization for if-then-else

* Tag instructions in annotated scode

* Remove 'if' from fcode

* Fix dialyzer things

* Remove unused argument

* Compile pattern matching on integer literals

* Compile list patterns

* Use op_view in more places

* allow leaving out fields from record patterns

* compile records (patterns and construction)

* Compile record update

* Use SETELEMENT instruction

* Compile variants

* Remove incorrect push for tuple switches

* Optimize matching on single constructors datatypes

* Use the fact that SWITCH and JUMPIF can use args and vars

* string literals and pattern matching on the same

* Compile character literals

* Minor refactoring of op instruction handling

* compile address literals

* Get rid of unit in AST

* Unary operators

* Compile function calls

(to fully saturated top-level functions only)

* fix breakage after unary operators

* variables are now lists of names in fcode

* pretty printing for function calls

* use STORE ?a instead of PUSH during optimizations

* no-op fcode optimization pass

* some constant propagation optimizations

* Case on constructor optimization

* fix minor bugs

* Compile all the operators

* Compile maps

* Simplify JUMPIF on true/false

* Fixed left-over reference to STR_EQ

* Add compile-time evaluation for more operators

* Distinguish local vars and top-level names already in fcode

* Compile builtins

* Compile bytes(N)

Compile to FATE strings for now

* Improve inlining of PUSH

* Fix name resolution bug

* Change map_get/set to operators in fcode

* Compile lambdas and higher-order functions

* Optimize single variable closure envs

* Handle unapplied builtins and top-level functions

* Missing case in fcode pretty printer

* Fix variable binding bug in fcode compiler

* Compiler side of state updates

No support in FATE yet though

* Compile statements

* Compile events

But no FATE support for events yet

* Compile remote calls

* Clearer distinction between applied and unapplied top-level things (def/builtin) in fcode

* Tag for literals in fcode to make code cleaner

* We now have block hash at height in FATE

* Update aebytecode commit

* Get rid of catchall todos

* Jump some hoops to please Dialyzer
2019-05-07 15:48:47 +02:00
Ulf Norell 71b97cba62 Merge pull request #61 from aeternity/PT-165597438-equality-on-bytes
Support equality on bytes(N)
2019-04-26 08:47:14 +02:00
Ulf Norell 8a381e5ef1 Support equality on bytes(N) 2019-04-25 16:06:50 +02:00
Hans Svensson 386419f112 Merge pull request #60 from aeternity/PT-164629541-generic_hash_and_signature
Revert bytes(N) from icode/vm-types
2019-04-24 08:58:43 +02:00
Hans Svensson 45a62f0807 Simplify ast_typerep 2019-04-24 08:44:05 +02:00
Hans Svensson 3255c62e0e Revert bytes(N) from icode/vm-types 2019-04-23 17:47:50 +02:00
Hans Svensson 51b63f9559 Merge pull request #59 from aeternity/PT-164629541-generic_hash_and_signature
Add bytes(int), add address_literalsm add ecverify_secp256k1
2019-04-23 11:23:32 +02:00
Hans Svensson 5e6af18c7b Address review comment 2019-04-23 11:10:56 +02:00
Hans Svensson 4324bfd49e Add bytes(int), add address_literalsm add ecverify_secp25k1
hash -> bytes(32)
signature -> bytes(64)
address literals
2019-04-23 10:40:02 +02:00
Erik Stenman faa0ef9772 Merge pull request #57 from aeternity/PT-165312102-setelement
Point to latest aebytecode with setelement instruction.
2019-04-15 10:57:10 +02:00
Erik Stenman f07954f62c Point to latest aebytecode with setelement instruction. 2019-04-15 08:54:02 +02:00
Hans Svensson ef761a4c57 Merge pull request #56 from aeternity/prepare_2.1.0
Preparing v2.1.0
2019-04-11 14:22:54 +02:00
Hans Svensson 330d8929fd Preparing v2.1.0 2019-04-11 13:42:41 +02:00
Thomas Arts 491b1211d1 Merge pull request #55 from aeternity/PT-165246396-prepare-remove-dependency
Update commit hash aebytecode
2019-04-11 10:30:41 +02:00
Thomas Arts e460b84bd0 Update commit hash aebytecode 2019-04-11 09:24:43 +02:00
Hans Svensson 9109712826 Merge pull request #53 from aeternity/generalized_accounts
Add Auth.tx_hash - namespace + primop
2019-04-08 14:45:56 +02:00
Hans Svensson d6a55e144e Test Auth.tx_hash compilation 2019-04-08 11:57:07 +02:00
Hans Svensson db64978d2e Add Auth.tx_hash 2019-04-08 11:57:07 +02:00
Hans Svensson 2ed9d17ce5 Switch to generalized_accounts branch of aebytecode 2019-04-08 11:57:07 +02:00
Robert Virding 7bf7cb0b8f Merge pull request #52 from aeternity/new-aci-generator
PT-163022973 Make a new aci generator
2019-04-05 15:14:17 +02:00
Robert Virding 4a01c852c9 Add more test cases
And some trivial code cleanup.
2019-04-03 22:58:54 +02:00
Robert Virding df00c3958b First version of final aci
Should have more test cases and code cleanup
2019-04-03 17:53:16 +02:00
Robert Virding 12cb37245b First version of new aci generator
We also include some updated simple tests which are only run by eunit.
2019-04-03 17:53:16 +02:00
Tino Breddin 562ad5ee87 Merge pull request #51 from aeternity/PT-165081160-update-rebar3
Upgrade rebar3 to 3.9.1-aeternity.2
2019-04-03 11:00:26 +02:00
Tino Breddin 4e78756b90 Upgrade rebar3 to 3.9.1-aeternity.2
Reference: https://github.com/aeternity/rebar3/releases/tag/3.9.1-aeternity.2
2019-04-03 10:23:02 +02:00
Robert Virding 9f32fb1925 Merge pull request #50 from aeternity/PT-164597852-move-aesophia-heap
PT-164597852 Move aesophia heap handling into aebytecode
2019-04-02 17:53:28 +02:00
Robert Virding 549a0c2201 Move TYPEREP definitions to aebytecode 2019-04-02 16:03:50 +02:00
Robert Virding 9f5f8d4444 Change function references from aeso_sophia to aeb_aevm_data 2019-04-02 16:00:10 +02:00
Robert Virding fd0dbdf207 Change references from aeso_memory to aeb_memory 2019-04-02 15:59:12 +02:00
Robert Virding 0d8b7c7c79 First commit fixinng references from aeso_ to aeb_
Also remove local copies of modules moved to aebytecode.
2019-04-02 15:59:12 +02:00
Erik Stenman 3271d6fba4 Merge pull request #49 from aeternity/PT-164597736-variant-types
Pt 164597736 variant types
2019-04-02 13:59:47 +02:00
Erik Stenman 30fbcc50c5 Longer ref. 2019-04-02 13:53:54 +02:00
Erik Stenman 27bc5474cb Upgrade aebytecode to new variant type representation. 2019-04-02 12:44:51 +02:00
Hans Svensson efeb391805 Merge pull request #48 from aeternity/cleanup_blake2
Remove aeso_blake2, use eblake2
2019-04-02 10:32:41 +02:00
Hans Svensson 15ca37342c Remove aeso_blake2, use eblake2 2019-04-02 09:00:20 +02:00
Luca Favatella 8b7e4db490 Merge pull request #47 from aeternity/PT-164834227-too-verbose-build
Fix build warning
2019-04-01 12:13:28 +01:00
Luca Favatella d89fd134b5 Fix build warning
Symptom:
```
src/aeso_aci.erl:18: Warning: record namespace is unused
```
2019-03-29 15:25:21 +00:00
Luca Favatella 37dfbf78ac Remove fragile file-based versioning (#46) 2019-03-15 10:12:41 +00:00
Tobias Lindahl 188916c61f Merge pull request #45 from aeternity/PT-164655050-fail-on-removed-VERSION
Check that VERSION file exists
2019-03-15 10:15:04 +01:00
Tobias Lindahl f395649419 Check that VERSION file exists 2019-03-15 09:50:39 +01:00
Tobias Lindahl 36395b597a Merge pull request #44 from aeternity/PT-164626753-latest-aebytecode
Upgrade to latest aebytecode (and by transitivity aeserialization)
2019-03-14 11:32:29 +01:00
Tobias Lindahl 177e32c117 Upgrade to latest aebytecode (and by transitivity aeserialization) 2019-03-14 10:59:15 +01:00
Hans Svensson 4d61ee65df Merge pull request #43 from aeternity/add_decode_calldata
add aeso_compiler:decode_calldata/3
2019-03-14 09:42:27 +01:00
Hans Svensson a089af555f More better type specs 2019-03-13 19:57:27 +01:00
Hans Svensson cd116b23d7 add aeso_compiler:decode_calldata/3 2019-03-13 16:15:48 +01:00
Tobias Lindahl fba6609c3a Merge pull request #42 from aeternity/fortuna2
Merge Fortuna to master
2019-03-13 13:44:48 +01:00
Tobias Lindahl e7c477d4de Merge pull request #41 from aeternity/PT-164595944-use-latest-aebytecode
Use latest aebytecode
2019-03-13 11:34:42 +01:00
Tobias Lindahl a44b787735 Use latest aebytecode 2019-03-13 11:17:33 +01:00
Erik Stenman b6a789bbbc Merge pull request #39 from aeternity/merge_master_to_fortuna
Merge master to fortuna
2019-03-05 14:51:13 +01:00
Erik Stenman ad34363673 Merge branch 'fortuna' into merge_master_to_fortuna 2019-03-05 14:35:10 +01:00
Erik Stenman 257de08100 Upgrade rebar.lock. 2019-03-05 14:32:15 +01:00
Erik Stenman 7ae4a98360 Use latest aebytecode. (#36)
* Use latest aebytecode.

* Fix argument to dup instruction.
2019-03-05 14:27:01 +01:00
Erik Stenman 5e6e607fa4 Use v2.0.1 of aebytecode including new sub dependencies. 2019-03-05 14:27:01 +01:00
Erik Stenman a69056c35e Change aebytecode branch ref to a tag. 2019-03-05 14:26:23 +01:00
Erik Stenman 8cfa611b20 Use right name for inc/1. Remove dead code. 2019-03-05 14:24:46 +01:00
Erik Stenman e9bdd59def Use local (patched) rebar3 in CI builds. 2019-03-05 14:24:46 +01:00
Erik Stenman 0da7376d11 Add patched rebar3 that dont run post hooks in al deps. 2019-03-05 14:24:46 +01:00
Erik Stenman af4f2ad795 Clean up. 2019-03-05 14:24:46 +01:00
Erik Stenman abae4a7602 Upgrade to latest aebytecode. 2019-03-05 14:24:22 +01:00
Erik Stenman 1cfd4c6f24 Add release target. 2019-03-05 14:24:22 +01:00
Ulf Norell f07a49c91d Skeleton for Fate backend 2019-03-05 14:23:19 +01:00
Erik Stenman e33e4cf2cd Use fortuna version of aebytecode in fortuna version of aesophia. 2019-03-05 14:22:59 +01:00
Erik Stenman 85a014958d Use latest aebytecode. (#36)
* Use latest aebytecode.

* Fix argument to dup instruction.
2019-03-04 14:22:36 +01:00
Erik Stenman fadf3378b4 Merge pull request #27 from aeternity/fate-compiler2
Skeleton for Fate backend
2019-02-28 09:03:09 +01:00
Erik Stenman 54edba3164 Use v2.0.1 of aebytecode including new sub dependencies. 2019-02-26 09:43:14 +01:00
Erik Stenman fc64ca572d Change aebytecode branch ref to a tag. 2019-02-25 16:42:32 +01:00
Erik Stenman 53b85ce6f4 Use right name for inc/1. Remove dead code. 2019-02-25 13:25:36 +01:00
Erik Stenman a7af62c089 Use local (patched) rebar3 in CI builds. 2019-02-25 13:18:13 +01:00
Erik Stenman abc70ba288 Add patched rebar3 that dont run post hooks in al deps. 2019-02-25 13:12:42 +01:00
Erik Stenman 6342cd6a08 Clean up. 2019-02-25 13:10:04 +01:00
Erik Stenman 123d1d2fa2 Upgrade to latest aebytecode. 2019-02-25 09:07:52 +01:00
Erik Stenman b7b54b38a8 Add release target. 2019-02-20 17:34:49 +01:00
Ulf Norell ae3f292f03 Skeleton for Fate backend 2019-02-20 09:44:32 +01:00
Erik Stenman bcdf311096 Use fortuna version of aebytecode in fortuna version of aesophia. 2019-02-15 12:42:49 +01:00
59 changed files with 3834 additions and 1356 deletions
+4 -4
View File
@@ -19,16 +19,16 @@ jobs:
- dialyzer-cache-v2-
- run:
name: Build
command: rebar3 compile
command: ./rebar3 compile
- run:
name: Static Analysis
command: rebar3 dialyzer
command: ./rebar3 dialyzer
- run:
name: Eunit
command: rebar3 eunit
command: ./rebar3 eunit
- run:
name: Common Tests
command: rebar3 ct
command: ./rebar3 ct
- save_cache:
key: dialyzer-cache-v2-{{ .Branch }}-{{ .Revision }}
paths:
+61
View File
@@ -0,0 +1,61 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
### Changed
### Removed
## [3.0.0] - 2019-05-21
### Added
- `stateful` annotations are now properly enforced. Functions must be marked stateful
in order to update the state or spend tokens.
- Primitives `Contract.creator`, `Address.is_contract`, `Address.is_oracle`,
`Oracle.check` and `Oracle.check_query` has been added to Sophia.
- A byte array type `bytes(N)` has been added to generalize `hash (== bytes(32))` and
`signature (== bytes(64))` and allow for byte arrays of arbitrary fixed length.
- `Crypto.ecverify_secp256k1` has been added.
### Changed
- Address literals (+ Oracle, Oracle query and remote contracts) have been changed
from `#<hex>` to address as `ak_<base58check>`, oracle `ok_<base58check>`,
oracle query `oq_<base58check>` and remote contract `ct_<base58check>`.
- The compilation and typechecking of `letfun` (e.g. `let m(f, xs) = map(f, xs)`) was
not working properly and has been fixed.
### Removed
- `let rec` has been removed from the language, it has never worked.
- The standalone CLI compiler is served in the repo `aeternity/aesophia_cli` and has
been completely removed from `aesophia`.
## [2.1.0] - 2019-04-11
### Added
- Stubs (not yet wired up) for compilation to FATE
- Add functions specific for Calldata decoding
- Support for `Auth.tx_hash`, not available in AEVM until Fortuna release
### Changed
- Improvements to the ACI generator
## [2.0.0] - 2019-03-11
### Added
- Add `Crypto.ecverify` to the compiler.
- Add `Crypto.sha3`, `Crypto.blake2`, `Crypto.sha256`, `String.blake2` and
`String.sha256` to the compiler.
- Add the `bits` type for working with bit fields in Sophia.
- Add Namespaces to Sophia in order to simplify using library contracts, etc.
- Add a missig type check on the `init` function - detects programmer errors earlier.
- Add the ACI (Aeternity Contract Interface) generator.
### Changed
- Use native bit shift operations in builtin functions, reducing gas cost.
- Improve type checking of `record` fields - generates more understandable error messages.
- Improved, more coherent, error messages.
- Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v3.0.0...HEAD
[3.0.0]: https://github.com/aeternity/aesophia/compare/v2.1.0...v3.0.0
[2.1.0]: https://github.com/aeternity/aesophia/compare/v2.0.0...v2.1.0
[2.0.0]: https://github.com/aeternity/aesophia/tag/v2.0.0
-1
View File
@@ -1 +0,0 @@
2.0.0
-16
View File
@@ -1,16 +0,0 @@
# About this release
This is the `aesophia` compiler version 2.0.0. The main changes compared to version 1.2.0 are:
* Add `Crypto.ecverify` to the compiler.
* Add `Crypto.sha3`, `Crypto.blake2`, `Crypto.sha256`, `String.blake2` and
`String.sha256` to the compiler.
* Add the `bits` type for working with bit fields in Sophia.
* Use native bit shift operations in builtin functions, reducing gas cost.
* Add Namespaces to Sophia in order to simplify using library contracts, etc.
* Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string.
* Add a missig type check on the `init` function - detects programmer errors earlier.
* Improve type checking of `record` fields - generates more understandable error messages.
* Improved, more coherent, error messages.
* Add the ACI (Aeternity Contract Interface) generator.
-15
View File
@@ -1,15 +0,0 @@
-record(pmap, {key_t :: aeso_sophia:type(),
val_t :: aeso_sophia:type(),
parent :: none | non_neg_integer(),
size = 0 :: non_neg_integer(),
data :: #{aeso_heap:binary_value() => aeso_heap:binary_value() | tombstone}
| stored}).
-record(maps, { maps = #{} :: #{ non_neg_integer() => #pmap{} }
, next_id = 0 :: non_neg_integer() }).
-record(heap, { maps :: #maps{},
offset :: aeso_heap:offset(),
heap :: binary() | #{non_neg_integer() => non_neg_integer()} }).
+9 -16
View File
@@ -3,29 +3,22 @@
{erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
{ref,"720510a"}}}
{ref, "2f4e188"}}}
, {getopt, "1.0.1"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
{tag, "2.8.0"}}}
]}.
{escript_incl_apps, [aesophia, aebytecode, getopt]}.
{escript_main_app, aesophia}.
{escript_name, aesophia}.
{escript_emu_args, "%%! +sbtu +A0\n"}.
{provider_hooks, [{post, [{compile, escriptize}]}]}.
{post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)",
escriptize,
"cp \"$REBAR_BUILD_DIR/bin/aesophia\" ./aesophia"},
{"win32",
escriptize,
"robocopy \"%REBAR_BUILD_DIR%/bin/\" ./ aesophia* "
"/njs /njh /nfl /ndl & exit /b 0"} % silence things
]}.
{dialyzer, [
{warnings, [unknown]},
{plt_apps, all_deps},
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}.
{relx, [{release, {aesophia, "3.0.0"},
[aesophia, aebytecode, getopt]},
{dev_mode, true},
{include_erts, false},
{extended_start_script, true}]}.
+11 -1
View File
@@ -1,8 +1,17 @@
{"1.1.0",
[{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git",
{ref,"720510a24de32c9bad6486f34ca7babde124bf1e"}},
{ref,"2f4e1888c241a7347ffec855ab6761c2c2972f37"}},
0},
{<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git",
{ref,"816bf994ffb5cee218c3f22dc5fea296c9e0882e"}},
1},
{<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git",
{ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}},
2},
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},1},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
{<<"jsx">>,
{git,"https://github.com/talentdeficit/jsx.git",
@@ -10,5 +19,6 @@
0}]}.
[
{pkg_hash,[
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
].
Executable
BIN
View File
Binary file not shown.
-209
View File
@@ -1,209 +0,0 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% Encode and decode data and function calls according to
%%% Sophia-AEVM-ABI.
%%% @end
%%% Created : 25 Jan 2018
%%%
%%%-------------------------------------------------------------------
-module(aeso_abi).
-define(HASH_SIZE, 32).
-export([ old_create_calldata/3
, create_calldata/4
, check_calldata/2
, function_type_info/3
, 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
]).
-type hash() :: <<_:256>>. %% 256 = ?HASH_SIZE * 8.
-type function_name() :: binary(). %% String
-type typerep() :: aeso_sophia:type().
-type function_type_info() :: { FunctionHash :: hash()
, FunctionName :: function_name()
, ArgType :: binary() %% binary typerep
, OutType :: binary() %% binary typerep
}.
-type type_info() :: [function_type_info()].
%%%===================================================================
%%% API
%%%===================================================================
%%%===================================================================
%%% Handle calldata
create_calldata(FunName, Args, ArgTypes0, RetType) ->
ArgTypes = {tuple, ArgTypes0},
<<TypeHashInt:?HASH_SIZE/unit:8>> =
function_type_hash(list_to_binary(FunName), ArgTypes, RetType),
Data = aeso_heap:to_binary({TypeHashInt, list_to_tuple(Args)}),
{ok, Data, {tuple, [word, ArgTypes]}, RetType}.
get_type_info_and_hash(#{type_info := TypeInfo}, FunName) ->
FunBin = list_to_binary(FunName),
case type_hash_from_function_name(FunBin, TypeInfo) of
{ok, <<TypeHashInt:?HASH_SIZE/unit:8>>} -> {ok, TypeInfo, TypeHashInt};
{ok, _} -> {error, bad_type_hash};
{error, _} = Err -> Err
end.
-spec check_calldata(binary(), type_info()) ->
{'ok', typerep(), typerep()} | {'error', atom()}.
check_calldata(CallData, TypeInfo) ->
%% The first element of the CallData should be the function name
case get_function_hash_from_calldata(CallData) of
{ok, Hash} ->
case typereps_from_type_hash(Hash, TypeInfo) of
{ok, ArgType, OutType} ->
try aeso_heap:from_binary({tuple, [word, ArgType]}, CallData) of
{ok, _Something} ->
{ok, {tuple, [word, ArgType]}, OutType};
{error, _} ->
{error, bad_call_data}
catch
_T:_E ->
{error, bad_call_data}
end;
{error, _} ->
{error, unknown_function}
end;
{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) ->
case aeso_heap:from_binary({tuple, [word]}, CallData) of
{ok, {HashInt}} -> {ok, <<HashInt:?HASH_SIZE/unit:8>>};
{error, _} = Error -> Error
end.
%%%===================================================================
%%% Handle type info from contract meta data
-spec function_type_info(function_name(), [typerep()], typerep()) ->
function_type_info().
function_type_info(Name, ArgTypes, OutType) ->
ArgType = {tuple, ArgTypes},
{ function_type_hash(Name, ArgType, OutType)
, Name
, aeso_heap:to_binary(ArgType)
, aeso_heap:to_binary(OutType)
}.
-spec function_type_hash(function_name(), typerep(), typerep()) -> hash().
function_type_hash(Name, ArgType, OutType) when is_binary(Name) ->
Bin = iolist_to_binary([ Name
, aeso_heap:to_binary(ArgType)
, aeso_heap:to_binary(OutType)
]),
%% Calculate a 256 bit digest BLAKE2b hash value of a binary
{ok, Hash} = aeso_blake2:blake2b(?HASH_SIZE, Bin),
Hash.
-spec arg_typerep_from_function(function_name(), type_info()) ->
{'ok', typerep()} | {'error', 'bad_type_data' | 'unknown_function'}.
arg_typerep_from_function(Function, TypeInfo) ->
case lists:keyfind(Function, 2, TypeInfo) of
{_TypeHash, Function, ArgTypeBin,_OutTypeBin} ->
case aeso_heap:from_binary(typerep, ArgTypeBin) of
{ok, ArgType} -> {ok, ArgType};
{error,_} -> {error, bad_type_data}
end;
false ->
{error, unknown_function}
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 {aeso_heap:from_binary(typerep, ArgTypeBin),
aeso_heap:from_binary(typerep, OutTypeBin)} of
{{ok, ArgType}, {ok, OutType}} -> {ok, ArgType, OutType};
{_, _} -> {error, bad_type_data}
end;
false ->
{error, unknown_function}
end.
-spec function_name_from_type_hash(hash(), type_info()) ->
{'ok', function_name()}
| {'error', 'unknown_function'}.
function_name_from_type_hash(TypeHash, TypeInfo) ->
case lists:keyfind(TypeHash, 1, TypeInfo) of
{TypeHash, Function,_ArgTypeBin,_OutTypeBin} ->
{ok, Function};
false ->
{error, unknown_function}
end.
-spec type_hash_from_function_name(function_name(), type_info()) ->
{'ok', hash()}
| {'error', 'unknown_function'}.
type_hash_from_function_name(Name, TypeInfo) ->
case lists:keyfind(Name, 2, TypeInfo) of
{TypeHash, Name,_ArgTypeBin,_OutTypeBin} ->
{ok, TypeHash};
false ->
{error, unknown_function}
end.
%% -- Old calldata creation. Kept for backwards compatibility. ---------------
old_create_calldata(Contract, Function, Argument) when is_map(Contract) ->
case aeso_constants:string(Argument) of
{ok, {tuple, _, _} = Tuple} ->
old_encode_call(Contract, Function, Tuple);
{ok, {unit, _} = Tuple} ->
old_encode_call(Contract, Function, Tuple);
{ok, ParsedArgument} ->
%% The Sophia compiler does not parse a singleton tuple (42) as a tuple,
%% Wrap it in a tuple.
old_encode_call(Contract, Function, {tuple, [], [ParsedArgument]});
{error, _} ->
{error, argument_syntax_error}
end.
%% Call takes one arument.
%% Use a tuple to pass multiple arguments.
old_encode_call(Contract, Function, ArgumentAst) ->
Argument = old_ast_to_erlang(ArgumentAst),
case get_type_info_and_hash(Contract, Function) of
{ok, TypeInfo, TypeHashInt} ->
Data = aeso_heap:to_binary({TypeHashInt, Argument}),
case check_calldata(Data, TypeInfo) of
{ok, CallDataType, OutType} ->
{ok, Data, CallDataType, OutType};
{error, _} = Err ->
Err
end;
{error, _} = Err -> Err
end.
old_ast_to_erlang({int, _, N}) -> N;
old_ast_to_erlang({hash, _, <<N:?HASH_SIZE/unit:8>>}) -> N;
old_ast_to_erlang({hash, _, <<Hi:256, Lo:256>>}) -> {Hi, Lo}; %% signature
old_ast_to_erlang({bool, _, true}) -> 1;
old_ast_to_erlang({bool, _, false}) -> 0;
old_ast_to_erlang({string, _, Bin}) -> Bin;
old_ast_to_erlang({unit, _}) -> {};
old_ast_to_erlang({con, _, "None"}) -> none;
old_ast_to_erlang({app, _, {con, _, "Some"}, [A]}) -> {some, old_ast_to_erlang(A)};
old_ast_to_erlang({tuple, _, Elems}) ->
list_to_tuple(lists:map(fun old_ast_to_erlang/1, Elems));
old_ast_to_erlang({list, _, Elems}) ->
lists:map(fun old_ast_to_erlang/1, Elems);
old_ast_to_erlang({map, _, Elems}) ->
maps:from_list([ {old_ast_to_erlang(element(1, Elem)), old_ast_to_erlang(element(2, Elem))}
|| Elem <- Elems ]).
+168 -48
View File
@@ -1,26 +1,30 @@
%%%-------------------------------------------------------------------
%%% @author Robert Virding
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% ACI interface
%%% @end
%%% Created : 12 Dec 2017
%%% Created : 12 Jan 2019
%%%-------------------------------------------------------------------
-module(aeso_aci).
-export([encode/1,encode/2,decode/1]).
-export([encode_type/1,encode_stmt/1,encode_expr/1]).
%% Define records for the various typed syntactic forms. These make
%% the code easier but don't seem to exist elsewhere.
%% Top-level
-record(contract, {ann,con,decls}).
-record(namespace, {ann,con,decls}).
%% -record(namespace, {ann,con,decls}).
-record(letfun, {ann,id,args,type,body}).
-record(type_def, {ann,id,vars,typedef}).
%% Types
-record(app_t, {ann,id,fields}).
-record(tuple_t, {ann,args}).
-record(bytes_t, {ann,len}).
-record(record_t, {fields}).
-record(field_t, {ann,id,type}).
-record(alias_t, {type}).
@@ -28,6 +32,7 @@
-record(constr_t, {ann,con,args}).
-record(fun_t, {ann,named,args,type}).
%% Tokens
-record(arg, {ann,id,type}).
-record(id, {ann,name}).
-record(con, {ann,name}).
@@ -35,6 +40,16 @@
-record(qcon, {ann,names}).
-record(tvar, {ann,name}).
%% Expressions
-record(bool, {ann,bool}).
-record(int, {ann,value}).
-record(string, {ann,bin}).
-record(bytes, {ann,bin}).
-record(tuple, {ann,args}).
-record(list, {ann,args}).
-record(app, {ann,func,args}).
-record(typed, {ann,expr,type}).
%% encode(ContractString) -> {ok,JSON} | {error,String}.
%% encode(ContractString, Options) -> {ok,JSON} | {error,String}.
%% Build a JSON structure with lists and tuples, not maps, as this
@@ -47,7 +62,7 @@ encode(ContractString, Options) when is_binary(ContractString) ->
encode(ContractString, Options) ->
try
Ast = parse(ContractString, Options),
%%io:format("~p\n", [Ast]),
%% io:format("~p\n", [Ast]),
%% aeso_ast:pp(Ast),
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
%% io:format("~p\n", [TypedAst]),
@@ -59,7 +74,7 @@ encode(ContractString, Options) ->
T <- sort_decls(contract_types(Contract)) ],
Fdefs = [ encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
not is_private_func(F) ],
Jmap = [{<<"contract">>, [{<<"name">>, list_to_binary(Cname)},
Jmap = [{<<"contract">>, [{<<"name">>, encode_name(Cname)},
{<<"type_defs">>, Tdefs},
{<<"functions">>, Fdefs}]}],
%% io:format("~p\n", [Jmap]),
@@ -80,74 +95,137 @@ join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
%% encode_func(Function) -> JSON
%% Encode a function definition. Currently we are only interested in
%% the interface and type.
encode_func(Fdef) ->
Name = function_name(Fdef),
Args = function_args(Fdef),
Type = function_type(Fdef),
[{<<"name">>, list_to_binary(Name)},
[{<<"name">>, encode_name(Name)},
{<<"arguments">>, encode_args(Args)},
{<<"type">>, list_to_binary(encode_type(Type))},
{<<"returns">>, encode_type(Type)},
{<<"stateful">>, is_stateful_func(Fdef)}].
%% encode_args(Args) -> [JSON].
%% encode_arg(Args) -> JSON.
encode_args(Args) ->
[ encode_arg(A) || A <- Args ].
encode_arg(#arg{id=Id,type=T}) ->
[{<<"name">>,list_to_binary(encode_type(Id))},
{<<"type">>,list_to_binary(encode_type(T))}].
[{<<"name">>,encode_type(Id)},
{<<"type">>,[encode_type(T)]}].
%% encode_types(Types) -> [JSON].
%% encode_type(Type) -> JSON.
encode_types(Types) ->
[ encode_type(T) || T <- Types ].
encode_type(#tvar{name=N}) -> N;
encode_type(#id{name=N}) -> N;
encode_type(#con{name=N}) -> N;
encode_type(#tvar{name=N}) -> encode_name(N);
encode_type(#id{name=N}) -> encode_name(N);
encode_type(#con{name=N}) -> encode_name(N);
encode_type(#qid{names=Ns}) ->
lists:join(".", Ns);
encode_name(lists:join(".", Ns));
encode_type(#qcon{names=Ns}) ->
lists:join(".", Ns); %?
encode_name(lists:join(".", Ns)); %?
encode_type(#tuple_t{args=As}) ->
Eas = encode_types(As),
[$(,lists:join(",", Eas),$)];
[{<<"tuple">>,Eas}];
encode_type(#bytes_t{len=Len}) ->
{<<"bytes">>, Len};
encode_type(#record_t{fields=Fs}) ->
Efs = encode_types(Fs),
[${,lists:join(",", Efs),$}];
Efs = encode_fields(Fs),
[{<<"record">>,Efs}];
encode_type(#app_t{id=Id,fields=Fs}) ->
Name = encode_type(Id),
Efs = encode_types(Fs),
[Name,"(",lists:join(",", Efs),")"];
encode_type(#field_t{id=Id,type=T}) ->
[encode_type(Id)," : ",encode_type(T)];
[{Name,Efs}];
encode_type(#variant_t{cons=Cs}) ->
Ecs = encode_types(Cs),
lists:join(" | ", Ecs);
[{<<"variant">>,Ecs}];
encode_type(#constr_t{con=C,args=As}) ->
Ec = encode_type(C),
Eas = encode_types(As),
[Ec,$(,lists:join(", ", Eas),$)];
[{Ec,Eas}];
encode_type(#fun_t{args=As,type=T}) ->
Eas = encode_types(As),
Et = encode_type(T),
[$(,lists:join(", ", Eas),") => ",Et].
[{<<"function">>,[{<<"arguments">>,Eas},{<<"returns">>,Et}]}].
encode_name(Name) ->
list_to_binary(Name).
%% encode_fields(Fields) -> [JSON].
%% encode_field(Field) -> JSON.
%% Encode a record field.
encode_fields(Fs) ->
[ encode_field(F) || F <- Fs ].
encode_field(#field_t{id=Id,type=T}) ->
[{<<"name">>,encode_type(Id)},
{<<"type">>,[encode_type(T)]}].
%% encode_typedef(TypeDef) -> JSON.
encode_typedef(Type) ->
Name = typedef_name(Type),
Vars = typedef_vars(Type),
Def = typedef_def(Type),
[{<<"name">>, list_to_binary(Name)},
[{<<"name">>, encode_name(Name)},
{<<"vars">>, encode_tvars(Vars)},
{<<"typedef">>, list_to_binary(encode_alias(Def))}].
{<<"typedef">>, encode_alias(Def)}].
encode_tvars(Vars) ->
[ encode_tvar(V) || V <- Vars ].
encode_tvar(#tvar{name=N}) ->
[{<<"name">>, list_to_binary(N)}].
[{<<"name">>, encode_name(N)}].
encode_alias(#alias_t{type=T}) ->
encode_type(T);
encode_alias(A) -> encode_type(A).
%% encode_stmt(Stmt) -> JSON.
encode_stmt(E) ->
encode_expr(E).
%% encode_exprs(Exprs) -> [JSON].
%% encode_expr(Expr) -> JSON.
encode_exprs(Es) ->
[ encode_expr(E) || E <- Es ].
encode_expr(#id{name=N}) -> encode_name(N);
encode_expr(#con{name=N}) -> encode_name(N);
encode_expr(#qid{names=Ns}) ->
encode_name(lists:join(".", Ns));
encode_expr(#qcon{names=Ns}) ->
encode_name(lists:join(".", Ns)); %?
encode_expr(#typed{expr=E}) ->
encode_expr(E);
encode_expr(#bool{bool=B}) -> B;
encode_expr(#int{value=V}) -> V;
encode_expr(#string{bin=B}) -> B;
encode_expr(#bytes{bin=B}) -> B;
encode_expr(#tuple{args=As}) ->
Eas = encode_exprs(As),
[{<<"tuple">>,Eas}];
encode_expr(#list{args=As}) ->
Eas = encode_exprs(As),
[{<<"list">>,Eas}];
encode_expr(#app{func=F,args=As}) ->
Ef = encode_expr(F),
Eas = encode_exprs(As),
[{<<"apply">>,[{<<"function">>,Ef},
{<<"arguments">>,Eas}]}];
encode_expr({Op,_Ann}) ->
list_to_binary(atom_to_list(Op)).
%% decode(JSON) -> ContractString.
%% Decode a JSON string and generate a suitable contract string which
%% can be included in a contract definition. We decode into a map
@@ -160,42 +238,81 @@ decode(Json) ->
list_to_binary(decode_contract(C)).
decode_contract(#{<<"name">> := Name,
<<"type_defs">> := _Ts,
<<"type_defs">> := Ts,
<<"functions">> := Fs}) ->
["contract"," ",io_lib:format("~s", [Name])," =\n",
[], %Don't include types yet.
%% decode_tdefs(Ts),
decode_tdefs(Ts),
decode_funcs(Fs)].
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
decode_func(#{<<"name">> := <<"init">>}) -> [];
decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"type">> := T}) ->
decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
[" function"," ",io_lib:format("~s", [Name])," : ",
decode_args(As)," => ",decode_type(T),$\n].
decode_type(T) -> io_lib:format("~s", [T]).
decode_args(As) ->
Das = [ decode_arg(A) || A <- As ],
[$(,lists:join(", ", Das),$)].
decode_arg(#{<<"type">> := T}) -> decode_type(T).
decode_arg(#{<<"type">> := [T]}) -> decode_type(T).
decode_types(Ets) ->
[ decode_type(Et) || Et <- Ets ].
decode_type(#{<<"tuple">> := Ets}) ->
Ts = decode_types(Ets),
[$(,lists:join(",", Ts),$)];
decode_type(#{<<"record">> := Efs}) ->
Fs = decode_fields(Efs),
[${,lists:join(",", Fs),$}];
decode_type(#{<<"list">> := [Et]}) ->
T = decode_type(Et),
["list",$(,T,$)];
decode_type(#{<<"map">> := Ets}) ->
Ts = decode_types(Ets),
["map",$(,lists:join(",", Ts),$)];
decode_type(#{<<"variant">> := Ets}) ->
Ts = decode_types(Ets),
lists:join(" | ", Ts);
decode_type(Econs) when is_map(Econs) -> %General constructor
[{Ec,Ets}] = maps:to_list(Econs),
C = decode_name(Ec),
Ts = decode_types(Ets),
[C,$(,lists:join(",", Ts),$)];
decode_type(T) -> %Just raw names.
decode_name(T).
decode_name(En) ->
binary_to_list(En).
decode_fields(Efs) ->
[ decode_field(Ef) || Ef <- Efs ].
decode_field(#{<<"name">> := En,<<"type">> := [Et]}) ->
Name = decode_name(En),
Type = decode_type(Et),
[Name," : ",Type].
%% decode_tdefs(Json) -> [TypeString].
%% Here we are only interested in the type definitions and ignore the
%% aliases. We find them as they always have variants.
decode_tdefs(Ts) -> [ decode_tdef(T) ||
#{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts
].
decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
[" datatype"," ",decode_name(Name),decode_tvars(Vs),
" = ",decode_type(T),$\n].
decode_tvars([]) -> []; %No tvars, no parentheses
decode_tvars(Vs) ->
Dvs = [ decode_tvar(V) || V <- Vs ],
[$(,lists:join(", ", Dvs),$)].
decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
%% To keep dialyzer happy and quiet.
%% decode_tdefs(Ts) -> [ decode_tdef(T) || T <- Ts ].
%%
%% decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
%% [" type"," ",io_lib:format("~s", [Name]),decode_tvars(Vs),
%% " = ",decode_type(T),$\n].
%%
%% decode_tvars([]) -> []; %No tvars, no parentheses
%% decode_tvars(Vs) ->
%% Dvs = [ decode_tvar(V) || V <- Vs ],
%% [$(,lists:join(", ", Dvs),$)].
%%
%% decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
%%
%% #contract{Ann, Con, [Declarations]}.
contract_name(#contract{con=#con{name=N}}) -> N.
@@ -242,6 +359,9 @@ typedef_vars(#type_def{vars=Vars}) -> Vars.
typedef_def(#type_def{typedef=Def}) -> Def.
%% parse(ContractString, Options) -> {ok,AST}.
%% Signal errors, the sophia compiler way. Sigh!
parse(Text, Options) ->
%% Try and return something sensible here!
case aeso_parser:string(Text, Options) of
@@ -264,7 +384,7 @@ parse(Text, Options) ->
end.
parse_error(Pos, ErrorString) ->
io:format("Error ~p ~p\n", [Pos,ErrorString]),
%% io:format("Error ~p ~p\n", [Pos,ErrorString]),
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
error({parse_errors, [Error]}).
+188 -58
View File
@@ -56,7 +56,12 @@
, fields :: [aeso_syntax:id()]
, context :: why_record() }).
-type field_constraint() :: #field_constraint{} | #record_create_constraint{}.
-record(is_contract_constraint,
{ contract_t :: utype(),
context :: aeso_syntax:expr() %% The address literal
}).
-type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}.
-record(field_info,
{ ann :: aeso_syntax:ann()
@@ -98,6 +103,9 @@
, typevars = unrestricted :: unrestricted | [name()]
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
, namespace = [] :: qname()
, in_pattern = false :: boolean()
, stateful = false :: boolean()
, current_function = none :: none | aeso_syntax:id()
}).
-type env() :: #env{}.
@@ -191,7 +199,7 @@ bind_state(Env) ->
false -> {id, Ann, "event"} %% will cause type error if used(?)
end,
Env1 = bind_funs([{"state", State},
{"put", {fun_t, Ann, [], [State], Unit}}], Env),
{"put", {type_sig, [stateful | Ann], [], [State], Unit}}], Env),
%% We bind Chain.event in a local 'Chain' namespace.
pop_scope(
@@ -341,6 +349,7 @@ global_env() ->
Address = {id, Ann, "address"},
Hash = {id, Ann, "hash"},
Bits = {id, Ann, "bits"},
Bytes = fun(Len) -> {bytes_t, Ann, Len} end,
Oracle = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle"}, [Q, R]} end,
Query = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle_query"}, [Q, R]} end,
Unit = {tuple_t, Ann, []},
@@ -350,11 +359,12 @@ global_env() ->
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
Fun = fun(Ts, T) -> {type_sig, Ann, [], Ts, T} end,
Fun1 = fun(S, T) -> Fun([S], T) end,
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [], Ts, T} end,
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
SignId = {id, Ann, "signature"},
SignDef = {tuple, Ann, [{int, Ann, 0}, {int, Ann, 0}]},
SignDef = {bytes, Ann, <<0:64/unit:8>>},
Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}},
SignFun = fun(Ts, T) -> {type_sig, Ann, [Signature], Ts, T} end,
SignFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [Signature], Ts, T} end,
TTL = {qid, Ann, ["Chain", "ttl"]},
Fee = Int,
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
@@ -372,8 +382,10 @@ global_env() ->
%% Abort
{"abort", Fun1(String, A)}])
, types = MkDefs(
[{"int", 0}, {"bool", 0}, {"string", 0}, {"address", 0},
{"hash", 0}, {"signature", 0}, {"bits", 0},
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
{"hash", {[], {alias_t, Bytes(32)}}},
{"signature", {[], {alias_t, Bytes(64)}}},
{"bits", 0},
{"option", 1}, {"list", 1}, {"map", 2},
{"oracle", 2}, {"oracle_query", 2}
]) },
@@ -381,7 +393,7 @@ global_env() ->
ChainScope = #scope
{ funs = MkDefs(
%% Spend transaction.
[{"spend", Fun([Address, Int], Unit)},
[{"spend", StateFun([Address, Int], Unit)},
%% Chain environment
{"balance", Fun1(Address, Int)},
{"block_hash", Fun1(Int, Int)},
@@ -395,7 +407,7 @@ global_env() ->
ContractScope = #scope
{ funs = MkDefs(
[{"address", Address},
%% {"owner", Int}, %% Not in EVM
{"creator", Address},
{"balance", Int}]) },
CallScope = #scope
@@ -411,11 +423,13 @@ global_env() ->
{ funs = MkDefs(
[{"register", SignFun([Address, Fee, TTL], Oracle(Q, R))},
{"query_fee", Fun([Oracle(Q, R)], Fee)},
{"query", Fun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))},
{"query", StateFun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))},
{"get_question", Fun([Oracle(Q, R), Query(Q, R)], Q)},
{"respond", SignFun([Oracle(Q, R), Query(Q, R), R], Unit)},
{"extend", SignFun([Oracle(Q, R), TTL], Unit)},
{"get_answer", Fun([Oracle(Q, R), Query(Q, R)], option_t(Ann, R))}]) },
{"get_answer", Fun([Oracle(Q, R), Query(Q, R)], option_t(Ann, R))},
{"check", Fun([Oracle(Q, R)], Bool)},
{"check_query", Fun([Oracle(Q,R), Query(Q, R)], Bool)}]) },
AENSScope = #scope
{ funs = MkDefs(
@@ -439,10 +453,16 @@ global_env() ->
CryptoScope = #scope
{ funs = MkDefs(
[{"ecverify", Fun([Hash, Address, SignId], Bool)},
{"ecverify_secp256k1", Fun([Hash, Bytes(64), Bytes(64)], Bool)},
{"sha3", Fun1(A, Hash)},
{"sha256", Fun1(A, Hash)},
{"blake2b", Fun1(A, Hash)}]) },
%% Authentication
AuthScope = #scope
{ funs = MkDefs(
[{"tx_hash", Option(Hash)}]) },
%% Strings
StringScope = #scope
{ funs = MkDefs(
@@ -467,7 +487,9 @@ global_env() ->
%% Conversion
IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) },
AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}]) },
AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)},
{"is_oracle", Fun1(Address, Bool)},
{"is_contract", Fun1(Address, Bool)}]) },
#env{ scopes =
#{ [] => TopScope
@@ -477,6 +499,7 @@ global_env() ->
, ["Oracle"] => OracleScope
, ["AENS"] => AENSScope
, ["Map"] => MapScope
, ["Auth"] => AuthScope
, ["Crypto"] => CryptoScope
, ["String"] => StringScope
, ["Bits"] => BitsScope
@@ -491,7 +514,7 @@ map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
infer(Contracts) ->
infer(Contracts, []).
-type option() :: permissive_address_literals | return_env.
-type option() :: return_env.
-spec init_env(list(option())) -> env().
init_env(_Options) -> global_env().
@@ -582,6 +605,8 @@ infer_contract(Env, What, Defs) ->
SCCs = aeso_utils:scc(DepGraph),
%% io:format("Dependency sorted functions:\n ~p\n", [SCCs]),
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
%% Check that `init` doesn't read or write the state
check_state_dependencies(Env4, Defs1),
destroy_and_report_type_errors(Env4),
{Env4, TypeDefs ++ Decls ++ Defs1}.
@@ -669,6 +694,9 @@ check_type(Env, X = {Tag, _, _}, Arity) when Tag == con; Tag == qcon; Tag == id;
check_type(Env, Type = {tuple_t, Ann, Types}, Arity) ->
ensure_base_type(Type, Arity),
{tuple_t, Ann, [ check_type(Env, T, 0) || T <- Types ]};
check_type(_Env, Type = {bytes_t, _Ann, _Len}, Arity) ->
ensure_base_type(Type, Arity),
Type;
check_type(Env, {app_t, Ann, Type, Types}, Arity) ->
Types1 = [ check_type(Env, T, 0) || T <- Types ],
Type1 = check_type(Env, Type, Arity + length(Types)),
@@ -775,7 +803,7 @@ check_sccs(Env = #env{}, Funs, [{acyclic, X} | SCCs], Acc) ->
end;
check_sccs(Env = #env{}, Funs, [{cyclic, Xs} | SCCs], Acc) ->
Defs = [ maps:get(X, Funs) || X <- Xs ],
{TypeSigs, {letrec, _, Defs1}} = infer_letrec(Env, {letrec, [], Defs}),
{TypeSigs, Defs1} = infer_letrec(Env, Defs),
Env1 = bind_funs(TypeSigs, Env),
check_sccs(Env1, Funs, SCCs, Defs1 ++ Acc).
@@ -817,7 +845,7 @@ check_special_funs(_, _) -> ok.
typesig_to_fun_t({type_sig, Ann, Named, Args, Res}) -> {fun_t, Ann, Named, Args, Res}.
infer_letrec(Env, {letrec, Attrs, Defs}) ->
infer_letrec(Env, Defs) ->
create_constraints(),
Funs = [{Name, fresh_uvar(A)}
|| {letfun, _, {id, A, Name}, _, _, _} <- Defs],
@@ -837,9 +865,12 @@ infer_letrec(Env, {letrec, Attrs, Defs}) ->
TypeSigs = instantiate([Sig || {Sig, _} <- Inferred]),
NewDefs = instantiate([D || {_, D} <- Inferred]),
[print_typesig(S) || S <- TypeSigs],
{TypeSigs, {letrec, Attrs, NewDefs}}.
{TypeSigs, NewDefs}.
infer_letfun(Env, {letfun, Attrib, {id, NameAttrib, Name}, Args, What, Body}) ->
infer_letfun(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Body}) ->
Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false),
current_function = Fun },
check_unique_arg_names(Fun, Args),
ArgTypes = [{ArgName, check_type(Env, arg_type(T))} || {arg, _, ArgName, T} <- Args],
ExpectedType = check_type(Env, arg_type(What)),
NewBody={typed, _, _, ResultType} = check_expr(bind_vars(ArgTypes, Env), Body, ExpectedType),
@@ -850,6 +881,13 @@ infer_letfun(Env, {letfun, Attrib, {id, NameAttrib, Name}, Args, What, Body}) ->
{{Name, TypeSig},
{letfun, Attrib, {id, NameAttrib, Name}, NewArgs, ResultType, NewBody}}.
check_unique_arg_names(Fun, Args) ->
Name = fun({arg, _, {id, _, X}, _}) -> X end,
Names = lists:map(Name, Args),
Dups = lists:usort(Names -- lists:usort(Names)),
[ type_error({repeated_arg, Fun, Arg}) || Arg <- Dups ],
ok.
print_typesig({Name, TypeSig}) ->
?PRINT_TYPES("Inferred ~s : ~s\n", [Name, pp(TypeSig)]).
@@ -873,6 +911,7 @@ lookup_name(Env, As, Id, Options) ->
{Id, fresh_uvar(As)};
{QId, {_, Ty}} ->
Freshen = proplists:get_value(freshen, Options, false),
check_stateful(Env, Id, Ty),
Ty1 = case Ty of
{type_sig, _, _, _, _} -> freshen_type(typesig_to_fun_t(Ty));
_ when Freshen -> freshen_type(Ty);
@@ -881,6 +920,61 @@ lookup_name(Env, As, Id, Options) ->
{set_qname(QId, Id), Ty1}
end.
check_stateful(#env{ stateful = false, current_function = Fun }, Id, Type = {type_sig, _, _, _, _}) ->
case aeso_syntax:get_ann(stateful, Type, false) of
false -> ok;
true ->
type_error({stateful_not_allowed, Id, Fun})
end;
check_stateful(_Env, _Id, _Type) -> ok.
%% Hack: don't allow passing the 'value' named arg if not stateful. This only
%% works since the user can't create functions with named arguments.
check_stateful_named_arg(#env{ stateful = false, current_function = Fun }, {id, _, "value"}, Default) ->
case Default of
{int, _, 0} -> ok;
_ -> type_error({value_arg_not_allowed, Default, Fun})
end;
check_stateful_named_arg(_, _, _) -> ok.
%% Check that `init` doesn't read or write the state
check_state_dependencies(Env, Defs) ->
Top = Env#env.namespace,
GetState = Top ++ ["state"],
SetState = Top ++ ["put"],
Init = Top ++ ["init"],
UsedNames = fun(X) -> [{Xs, Ann} || {{term, Xs}, Ann} <- aeso_syntax_utils:used(X)] end,
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _Body} <- Defs ],
Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]),
case maps:get(Init, Deps, false) of
false -> ok; %% No init, so nothing to check
_ ->
[ type_error({init_depends_on_state, state, Chain})
|| Chain <- get_call_chains(Deps, Init, GetState) ],
[ type_error({init_depends_on_state, put, Chain})
|| Chain <- get_call_chains(Deps, Init, SetState) ],
ok
end.
%% Compute all paths (not sharing intermediate nodes) from Start to Stop in Graph.
get_call_chains(Graph, Start, Stop) ->
get_call_chains(Graph, #{}, queue:from_list([{Start, [], []}]), Stop, []).
get_call_chains(Graph, Visited, Queue, Stop, Acc) ->
case queue:out(Queue) of
{empty, _} -> lists:reverse(Acc);
{{value, {Stop, Ann, Path}}, Queue1} ->
get_call_chains(Graph, Visited, Queue1, Stop, [lists:reverse([{Stop, Ann} | Path]) | Acc]);
{{value, {Node, Ann, Path}}, Queue1} ->
case maps:is_key(Node, Visited) of
true -> get_call_chains(Graph, Visited, Queue1, Stop, Acc);
false ->
Calls = maps:get(Node, Graph, []),
NewQ = queue:from_list([{New, Ann1, [{Node, Ann} | Path]} || {New, Ann1} <- Calls]),
get_call_chains(Graph, Visited#{Node => true}, queue:join(Queue1, NewQ), Stop, Acc)
end
end.
check_expr(Env, Expr, Type) ->
E = {typed, _, _, Type1} = infer_expr(Env, Expr),
unify(Env, Type1, Type, {check_expr, Expr, Type1, Type}),
@@ -890,26 +984,35 @@ infer_expr(_Env, Body={bool, As, _}) ->
{typed, As, Body, {id, As, "bool"}};
infer_expr(_Env, Body={int, As, _}) ->
{typed, As, Body, {id, As, "int"}};
infer_expr(_Env, Body={char, As, _}) ->
{typed, As, Body, {id, As, "char"}};
infer_expr(_Env, Body={string, As, _}) ->
{typed, As, Body, {id, As, "string"}};
infer_expr(_Env, Body={hash, As, Hash}) ->
case byte_size(Hash) of
32 -> {typed, As, Body, {id, As, "address"}};
64 -> {typed, As, Body, {id, As, "signature"}}
end;
infer_expr(_Env, Body={bytes, As, Bin}) ->
{typed, As, Body, {bytes_t, As, byte_size(Bin)}};
infer_expr(_Env, Body={account_pubkey, As, _}) ->
{typed, As, Body, {id, As, "address"}};
infer_expr(_Env, Body={oracle_pubkey, As, _}) ->
Q = fresh_uvar(As),
R = fresh_uvar(As),
{typed, As, Body, {app_t, As, {id, As, "oracle"}, [Q, R]}};
infer_expr(_Env, Body={oracle_query_id, As, _}) ->
Q = fresh_uvar(As),
R = fresh_uvar(As),
{typed, As, Body, {app_t, As, {id, As, "oracle_query"}, [Q, R]}};
infer_expr(_Env, Body={contract_pubkey, As, _}) ->
Con = fresh_uvar(As),
constrain([#is_contract_constraint{ contract_t = Con,
context = Body }]),
{typed, As, Body, Con};
infer_expr(_Env, Body={id, As, "_"}) ->
{typed, As, Body, fresh_uvar(As)};
infer_expr(Env, Id = {id, As, _}) ->
infer_expr(Env, Id = {Tag, As, _}) when Tag == id; Tag == qid ->
{QName, Type} = lookup_name(Env, As, Id),
{typed, As, QName, Type};
infer_expr(Env, Id = {qid, As, _}) ->
{QName, Type} = lookup_name(Env, As, Id),
{typed, As, QName, Type};
infer_expr(Env, Id = {con, As, _}) ->
infer_expr(Env, Id = {Tag, As, _}) when Tag == con; Tag == qcon ->
{QName, Type} = lookup_name(Env, As, Id, [freshen]),
{typed, As, QName, Type};
infer_expr(Env, {unit, As}) ->
infer_expr(Env, {tuple, As, []});
infer_expr(Env, {tuple, As, Cpts}) ->
NewCpts = [infer_expr(Env, C) || C <- Cpts],
CptTypes = [T || {typed, _, _, T} <- NewCpts],
@@ -963,7 +1066,7 @@ infer_expr(Env, {record, Attrs, Fields}) ->
constrain([ #record_create_constraint{
record_t = RecordType1,
fields = [ FieldName || {field, _, [{proj, _, FieldName}], _} <- Fields ],
context = Attrs } ] ++
context = Attrs } || not Env#env.in_pattern ] ++
[begin
[{proj, _, FieldName}] = LV,
#field_constraint{
@@ -1032,6 +1135,7 @@ infer_expr(Env, {lam, Attrs, Args, Body}) ->
infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) ->
CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E),
check_stateful_named_arg(Env, Id, E),
add_named_argument_constraint(
#named_argument_constraint{
args = NamedArgs,
@@ -1092,7 +1196,7 @@ infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
[] -> ok;
Nonlinear -> type_error({non_linear_pattern, Pattern, lists:usort(Nonlinear)})
end,
NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env),
NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env#env{ in_pattern = true }),
NewPattern = {typed, _, _, PatType} = infer_expr(NewEnv, Pattern),
NewBranch = check_expr(NewEnv, Branch, SwitchType),
unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}),
@@ -1101,12 +1205,11 @@ infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
%% NewStmts = infer_block(Env, Attrs, Stmts, BlockType)
infer_block(_Env, Attrs, [], BlockType) ->
error({impossible, empty_block, Attrs, BlockType});
infer_block(Env, Attrs, [Def={letfun, _, _, _, _, _}|Rest], BlockType) ->
NewDef = infer_letfun(Env, Def),
[NewDef|infer_block(Env, Attrs, Rest, BlockType)];
infer_block(Env, Attrs, [Def={letrec, _, _}|Rest], BlockType) ->
NewDef = infer_letrec(Env, Def),
[NewDef|infer_block(Env, Attrs, Rest, BlockType)];
infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) ->
{{Name, TypeSig}, LetFun} = infer_letfun(Env, Def),
FunT = freshen_type(typesig_to_fun_t(TypeSig)),
NewE = bind_var({id, Ann, Name}, FunT, Env),
[LetFun|infer_block(NewE, Attrs, Rest, BlockType)];
infer_block(Env, _, [{letval, Attrs, Pattern, Type, E}|Rest], BlockType) ->
NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, Attrs, E, arg_type(Type)}),
{'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} =
@@ -1151,6 +1254,8 @@ infer_prefix({IntOp,As}) when IntOp =:= '-' ->
free_vars({int, _, _}) ->
[];
free_vars({char, _, _}) ->
[];
free_vars({string, _, _}) ->
[];
free_vars({bool, _, _}) ->
@@ -1342,6 +1447,16 @@ check_record_create_constraints(Env, [C | Cs]) ->
end,
check_record_create_constraints(Env, Cs).
check_is_contract_constraints(_Env, []) -> ok;
check_is_contract_constraints(Env, [C | Cs]) ->
#is_contract_constraint{ contract_t = Type, context = Lit } = C,
Type1 = unfold_types_in_type(Env, instantiate(Type)),
case lookup_type(Env, record_type_name(Type1)) of
{_, {_Ann, {[], {contract_t, _}}}} -> ok;
_ -> type_error({not_a_contract_type, Type1, Lit})
end,
check_is_contract_constraints(Env, Cs).
-spec solve_field_constraints(env(), [field_constraint()]) -> ok.
solve_field_constraints(Env, Constraints) ->
%% First look for record fields that appear in only one type definition
@@ -1440,9 +1555,12 @@ solve_known_record_types(Env, Constraints) ->
DerefConstraints--SolvedConstraints.
destroy_and_report_unsolved_field_constraints(Env) ->
{FieldCs, CreateCs} =
{FieldCs, OtherCs} =
lists:partition(fun(#field_constraint{}) -> true; (_) -> false end,
get_field_constraints()),
{CreateCs, ContractCs} =
lists:partition(fun(#record_create_constraint{}) -> true; (_) -> false end,
OtherCs),
Unknown = solve_known_record_types(Env, FieldCs),
if Unknown == [] -> ok;
true ->
@@ -1452,6 +1570,7 @@ destroy_and_report_unsolved_field_constraints(Env) ->
end
end,
check_record_create_constraints(Env, CreateCs),
check_is_contract_constraints(Env, ContractCs),
destroy_field_constraints(),
ok.
@@ -1577,6 +1696,8 @@ unfold_types_in_type(Env, {field_t, Attr, Name, Type}, Options) ->
{field_t, Attr, Name, unfold_types_in_type(Env, Type, Options)};
unfold_types_in_type(Env, {constr_t, Ann, Con, Types}, Options) ->
{constr_t, Ann, Con, unfold_types_in_type(Env, Types, Options)};
unfold_types_in_type(Env, {named_arg_t, Ann, Con, Types, Default}, Options) ->
{named_arg_t, Ann, Con, unfold_types_in_type(Env, Types, Options), Default};
unfold_types_in_type(Env, T, Options) when is_tuple(T) ->
list_to_tuple(unfold_types_in_type(Env, tuple_to_list(T), Options));
unfold_types_in_type(Env, [H|T], Options) ->
@@ -1632,6 +1753,8 @@ unify1(_Env, {qid, _, Name}, {qid, _, Name}, _When) ->
true;
unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _When) ->
true;
unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) ->
true;
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) ->
unify(Env, Named1, Named2, When) andalso
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
@@ -1649,26 +1772,8 @@ unify1(Env, {app_t, _, T, []}, B, When) ->
unify1(Env, A, {app_t, _, T, []}, When) ->
unify(Env, A, T, When);
unify1(_Env, A, B, When) ->
Ok =
case get_option(permissive_address_literals, false) of
true ->
Kind = fun({qcon, _, _}) -> con;
({con, _, _}) -> con;
({id, _, "address"}) -> addr;
({id, _, "hash"}) -> hash;
({app_t, _, {id, _, "oracle"}, _}) -> oracle;
({app_t, _, {id, _, "oracle_query"}, _}) -> query;
(_) -> other end,
%% If permissive_address_literals we allow unifying adresses
%% with contract types or oracles/oracle queries
case lists:usort([Kind(A), Kind(B)]) of
[addr, K] -> K /= other;
_ -> false
end;
false -> false
end,
[ cannot_unify(A, B, When) || not Ok ],
Ok.
cannot_unify(A, B, When),
false.
dereference(T = {uvar, _, R}) ->
case ets_lookup(type_vars, R) of
@@ -1697,6 +1802,7 @@ occurs_check1(_, {con, _, _}) -> false;
occurs_check1(_, {qid, _, _}) -> false;
occurs_check1(_, {qcon, _, _}) -> false;
occurs_check1(_, {tvar, _, _}) -> false;
occurs_check1(_, {bytes_t, _, _}) -> false;
occurs_check1(R, {fun_t, _, Named, Args, Res}) ->
occurs_check(R, [Res, Named | Args]);
occurs_check1(R, {app_t, _, T, Ts}) ->
@@ -1779,7 +1885,7 @@ create_type_errors() ->
ets_new(type_errors, [bag]).
destroy_and_report_type_errors(Env) ->
Errors = ets_tab2list(type_errors),
Errors = lists:reverse(ets_tab2list(type_errors)),
%% io:format("Type errors now: ~p\n", [Errors]),
PPErrors = [ pp_error(unqualify(Env, Err)) || Err <- Errors ],
ets_delete(type_errors),
@@ -1813,6 +1919,11 @@ pp_error({undefined_field, Id}) ->
io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]);
pp_error({not_a_record_type, Type, Why}) ->
io_lib:format("~s\n~s\n", [pp_type("Not a record type: ", Type), pp_why_record(Why)]);
pp_error({not_a_contract_type, Type, Lit}) ->
io_lib:format("The type ~s is not a contract type\n"
"when checking that the contract literal at ~s\n~s\n"
"has the type\n~s\n",
[pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]);
pp_error({non_linear_pattern, Pattern, Nonlinear}) ->
Plural = [ $s || length(Nonlinear) > 1 ],
io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n",
@@ -1890,6 +2001,21 @@ pp_error({include, {string, Pos, Name}}) ->
pp_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n",
[Name, pp_loc(Pos)]);
pp_error({repeated_arg, Fun, Arg}) ->
io_lib:format("Repeated argument ~s to function ~s (at ~s).\n",
[Arg, pp(Fun), pp_loc(Fun)]);
pp_error({stateful_not_allowed, Id, Fun}) ->
io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
[pp(Id), pp_loc(Id), pp(Fun)]);
pp_error({value_arg_not_allowed, Value, Fun}) ->
io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
[pp_expr("", Value), pp_loc(Value), pp(Fun)]);
pp_error({init_depends_on_state, Which, [_Init | Chain]}) ->
WhichCalls = fun("put") -> ""; ("state") -> ""; (_) -> ", which calls" end,
io_lib:format("The init function should return the initial state as its result and cannot ~s the state,\nbut it calls\n~s",
[if Which == put -> "write"; true -> "read" end,
[ io_lib:format(" - ~s (at ~s)~s\n", [Fun, pp_loc(Ann), WhichCalls(Fun)])
|| {[_, Fun], Ann} <- Chain]]);
pp_error(Err) ->
io_lib:format("Unknown error: ~p\n", [Err]).
@@ -2037,6 +2163,8 @@ pp({qid, _, Name}) ->
string:join(Name, ".");
pp({con, _, Name}) ->
Name;
pp({qcon, _, Name}) ->
string:join(Name, ".");
pp({uvar, _, Ref}) ->
%% Show some unique representation
["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ];
@@ -2044,6 +2172,8 @@ pp({tvar, _, Name}) ->
Name;
pp({tuple_t, _, Cpts}) ->
["(", pp(Cpts), ")"];
pp({bytes_t, _, Len}) ->
["bytes(", integer_to_list(Len), ")"];
pp({app_t, _, T, []}) ->
pp(T);
pp({app_t, _, Type, Args}) ->
File diff suppressed because it is too large Load Diff
+66 -23
View File
@@ -102,13 +102,6 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest]
QName = aeso_icode:qualify(Name, Icode),
NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode),
contract_to_icode(Rest, NewIcode);
contract_to_icode([{letrec,_,Defs}|Rest], Icode) ->
%% OBS! This code ignores the letrec structure of the source,
%% because the back end treats ALL declarations as recursive! We
%% need to decide whether to (a) modify the back end to respect
%% the letrec structure, or (b) (preferably) modify the front end
%% just to parse a list of (mutually recursive) definitions.
contract_to_icode(Defs++Rest, Icode);
contract_to_icode([], Icode) -> Icode;
contract_to_icode([{fun_decl, _, _, _} | Code], Icode) ->
contract_to_icode(Code, Icode);
@@ -152,6 +145,7 @@ ast_body(?qid_app(["Chain", "block_hash"], [Height], _, _), Icode) ->
ast_body(?qid_app(["Call", "gas_left"], [], _, _), _Icode) ->
prim_gas_left;
ast_body({qid, _, ["Contract", "address"]}, _Icode) -> prim_contract_address;
ast_body({qid, _, ["Contract", "creator"]}, _Icode) -> prim_contract_creator;
ast_body({qid, _, ["Contract", "balance"]}, _Icode) -> #prim_balance{ address = prim_contract_address };
ast_body({qid, _, ["Call", "origin"]}, _Icode) -> prim_call_origin;
ast_body({qid, _, ["Call", "caller"]}, _Icode) -> prim_caller;
@@ -182,6 +176,11 @@ ast_body(?id_app("abort", [String], _, _), Icode) ->
#funcall{ function = #var_ref{ name = {builtin, abort} },
args = [ast_body(String, Icode)] };
%% Authentication
ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) ->
prim_call(?PRIM_CALL_AUTH_TX_HASH, #integer{value = 0},
[], [], aeso_icode:option_typerep(word));
%% Oracles
ast_body(?qid_app(["Oracle", "register"], Args, _, ?oracle_t(QType, RType)), Icode) ->
{Sign, [Acct, QFee, TTL]} = get_signature_arg(Args),
@@ -219,6 +218,17 @@ ast_body(?qid_app(["Oracle", "get_answer"], [Oracle, Q], [_, ?query_t(_, RType)]
prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode)));
ast_body(?qid_app(["Oracle", "check"], [Oracle], [?oracle_t(Q, R)], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_CHECK, #integer{value = 0},
[ast_body(Oracle, Icode), ast_type_value(Q, Icode), ast_type_value(R, Icode)],
[word, typerep, typerep], word);
ast_body(?qid_app(["Oracle", "check_query"], [Oracle, Query], [_, ?query_t(Q, R)], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_CHECK_QUERY, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Query, Icode),
ast_type_value(Q, Icode), ast_type_value(R, Icode)],
[word, typerep, typerep], word);
ast_body({qid, _, ["Oracle", "register"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.register'});
ast_body({qid, _, ["Oracle", "query"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query'});
ast_body({qid, _, ["Oracle", "extend"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.extend'});
@@ -341,6 +351,11 @@ ast_body(?qid_app(["Crypto", "ecverify"], [Msg, PK, Sig], _, _), Icode) ->
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[word, word, sign_t()], word);
ast_body(?qid_app(["Crypto", "ecverify_secp256k1"], [Msg, PK, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[bytes_t(32), bytes_t(64), bytes_t(64)], word);
ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode);
ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) ->
@@ -400,6 +415,12 @@ ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) ->
ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) ->
builtin_call(addr_to_str, [ast_body(Addr, Icode)]);
ast_body(?qid_app(["Address", "is_oracle"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_ORACLE, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
ast_body(?qid_app(["Address", "is_contract"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
%% Other terms
ast_body({id, _, Name}, _Icode) ->
@@ -411,16 +432,19 @@ ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints
#integer{value = Value};
ast_body({int, _, Value}, _Icode) ->
#integer{value = Value};
ast_body({hash, _, Hash}, _Icode) ->
case Hash of
<<Value:32/unit:8>> -> %% address
#integer{value = Value};
<<Hi:32/unit:8, Lo:32/unit:8>> -> %% signature
#tuple{cpts = [#integer{value = Hi},
#integer{value = Lo}]}
ast_body({bytes, _, Bin}, _Icode) ->
case aeb_memory:binary_to_words(Bin) of
[Word] -> #integer{value = Word};
Words -> #tuple{cpts = [#integer{value = W} || W <- Words]}
end;
ast_body({Key, _, Bin}, _Icode) when Key == account_pubkey;
Key == contract_pubkey;
Key == oracle_pubkey;
Key == oracle_query_id ->
<<Value:32/unit:8>> = Bin,
#integer{value = Value};
ast_body({string,_,Bin}, _Icode) ->
Cpts = [size(Bin) | aeso_memory:binary_to_words(Bin)],
Cpts = [size(Bin) | aeb_memory:binary_to_words(Bin)],
#tuple{cpts = [#integer{value=X} || X <- Cpts]};
ast_body({tuple,_,Args}, Icode) ->
#tuple{cpts = [ast_body(A, Icode) || A <- Args]};
@@ -444,7 +468,7 @@ ast_body({app, _, {typed, _, {proj, _, {typed, _, Addr, {con, _, Contract}}, {id
Gas = proplists:get_value("gas", ArgOpts ++ Defaults),
Value = proplists:get_value("value", ArgOpts ++ Defaults),
OutType = ast_typerep(OutT, Icode),
<<TypeHash:256>> = aeso_abi:function_type_hash(list_to_binary(FunName), ArgType, OutType),
<<TypeHash:256>> = aeb_abi:function_type_hash(list_to_binary(FunName), ArgType, OutType),
%% The function is represented by its type hash (which includes the name)
Fun = #integer{value = TypeHash},
#prim_call_contract{
@@ -499,6 +523,8 @@ ast_body({switch,_,A,Cases}, Icode) ->
ast_body({block,As,[{letval,_,Pat,_,E}|Rest]}, Icode) ->
#switch{expr=ast_body(E, Icode),
cases=[{ast_body(Pat, Icode),ast_body({block,As,Rest}, Icode)}]};
ast_body({block, As, [{letfun, Ann, F, Args, _Type, Expr} | Rest]}, Icode) ->
ast_body({block, As, [{letval, Ann, F, unused, {lam, Ann, Args, Expr}} | Rest]}, Icode);
ast_body({block,_,[]}, _Icode) ->
#tuple{cpts=[]};
ast_body({block,_,[E]}, Icode) ->
@@ -566,16 +592,30 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode)
_ when not Monomorphic ->
gen_error({cant_compare_polymorphic_type, Ann, Op, Type});
word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)};
string ->
OtherType ->
Neg = case Op of
'==' -> fun(X) -> X end;
'!=' -> fun(X) -> #unop{ op = '!', rand = X } end;
_ -> gen_error({cant_compare, Ann, Op, Type})
end,
Neg(#funcall{ function = #var_ref{name = {builtin, str_equal}},
args = [ast_body(A, Icode), ast_body(B, Icode)] });
Args = [ast_body(A, Icode), ast_body(B, Icode)],
Builtin =
case OtherType of
string ->
#funcall{ function = #var_ref{name = {builtin, str_equal}},
args = Args };
{tuple, Types} ->
case lists:usort(Types) of
[word] ->
#funcall{ function = #var_ref{name = {builtin, str_equal_p}},
args = [ #integer{value = 32 * length(Types)} | Args] };
_ -> gen_error({cant_compare, Ann, Op, Type})
end;
_ ->
gen_error({cant_compare, Ann, Op, Type})
end,
Neg(Builtin)
end;
ast_binop('++', _, A, B, Icode) ->
#funcall{ function = #var_ref{ name = {builtin, list_concat} },
args = [ast_body(A, Icode), ast_body(B, Icode)] };
@@ -645,7 +685,7 @@ prim_call(Prim, Amount, Args, ArgTypes, OutType) ->
true ->
PrimBin = binary:encode_unsigned(Prim),
ArgType = {tuple, ArgTypes},
<<TH:256>> = aeso_abi:function_type_hash(PrimBin, ArgType, OutType),
<<TH:256>> = aeb_abi:function_type_hash(PrimBin, ArgType, OutType),
TH;
false ->
0
@@ -674,7 +714,7 @@ make_type_def(Args, Def, Icode = #{ type_vars := TypeEnv }) ->
ast_typerep(Def, Icode#{ type_vars := maps:merge(TypeEnv, TypeEnv1) })
end.
-spec ast_typerep(aeso_syntax:type()) -> aeso_sophia:type().
-spec ast_typerep(aeso_syntax:type()) -> aeb_aevm_data:type().
ast_typerep(Type) -> ast_typerep(Type, aeso_icode:new([])).
ast_typerep({id, _, Name}, Icode) ->
@@ -683,6 +723,8 @@ ast_typerep({qid, _, Name}, Icode) ->
lookup_type_id(Name, [], Icode);
ast_typerep({con, _, _}, _) ->
word; %% Contract type
ast_typerep({bytes_t, _, Len}, _) ->
bytes_t(Len);
ast_typerep({app_t, _, {id, _, Name}, Args}, Icode) ->
ArgReps = [ ast_typerep(Arg, Icode) || Arg <- Args ],
lookup_type_id(Name, ArgReps, Icode);
@@ -710,8 +752,9 @@ ast_typerep({variant_t, Cons}, Icode) ->
ttl_t(Icode) ->
ast_typerep({qid, [], ["Chain", "ttl"]}, Icode).
sign_t() ->
{tuple, [word, word]}.
sign_t() -> bytes_t(64).
bytes_t(Len) when Len =< 32 -> word;
bytes_t(Len) -> {tuple, lists:duplicate((31 + Len) div 32, word)}.
get_signature_arg(Args0) ->
NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0],
-149
View File
@@ -1,149 +0,0 @@
%%%=============================================================================
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% BLAKE2b implementation in Erlang - for details see: https://blake2.net
%%% @end
%%%=============================================================================
-module(aeso_blake2).
-export([ blake2b/2
, blake2b/3
]).
-define(MAX_64BIT, 16#ffffffffffffffff).
-spec blake2b(HashLen :: integer(), Msg :: binary()) -> {ok, binary()}.
blake2b(HashLen, Msg) ->
blake2b(HashLen, Msg, <<>>).
-spec blake2b(HashLen :: integer(), Msg :: binary(), Key :: binary()) -> {ok, binary()}.
blake2b(HashLen, Msg0, Key) ->
%% If message should be keyed, prepend message with padded key.
Msg = <<(pad(128, Key))/binary, Msg0/binary>>,
%% Set up the initial state
Init = (16#01010000 + (byte_size(Key) bsl 8) + HashLen),
<<H0:64, H1_7/binary>> = blake_iv(),
H = <<(H0 bxor Init):64, H1_7/binary>>,
%% Perform the compression - message will be chopped into 128-byte chunks.
State = blake2b_compress(H, Msg, 0),
%% Just return the requested part of the hash
{ok, binary_part(to_little_endian(State), {0, HashLen})}.
blake2b_compress(H, <<Chunk:(128*8), Rest/binary>>, BCompr) when Rest /= <<>> ->
H1 = blake2b_compress(H, <<Chunk:(128*8)>>, BCompr + 128, false),
blake2b_compress(H1, Rest, BCompr + 128);
blake2b_compress(H, SmallChunk, BCompr) ->
Size = byte_size(SmallChunk),
FillSize = (128 - Size) * 8,
blake2b_compress(H, <<SmallChunk/binary, 0:FillSize>>, BCompr + Size, true).
blake2b_compress(H, Chunk0, BCompr, Last) ->
Chunk = to_big_endian(Chunk0),
<<V0_11:(12*64), V12:64, V13:64, V14:64, V15:64>> = <<H/binary, (blake_iv())/binary>>,
V12_ = V12 bxor (BCompr band ?MAX_64BIT),
V13_ = V13 bxor ((BCompr bsr 64) band ?MAX_64BIT),
V14_ = case Last of
false -> V14;
true -> V14 bxor ?MAX_64BIT
end,
V = <<V0_11:(12*64), V12_:64, V13_:64, V14_:64, V15:64>>,
<<VLow:(8*64), VHigh:(8*64)>> =
lists:foldl(fun(Round, Vx) -> blake2b_mix(Round, Chunk, Vx) end, V, lists:seq(0, 11)),
<<HInt:(8*64)>> = H,
<<((HInt bxor VLow) bxor VHigh):(8*64)>>.
blake2b_mix(Rnd, Chunk, V) ->
<<V0:64, V1:64, V2:64, V3:64, V4:64, V5:64, V6:64, V7:64, V8:64,
V9:64, V10:64, V11:64, V12:64, V13:64, V14:64, V15:64>> = V,
<<M0:64, M1:64, M2:64, M3:64, M4:64, M5:64, M6:64, M7:64, M8:64,
M9:64, M10:64, M11:64, M12:64, M13:64, M14:64, M15:64>> = Chunk,
Ms = {M0, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15},
M = fun(Ix) -> element(Ix+1, Ms) end,
[S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15] = sigma(Rnd rem 10),
{Vx0, Vx4, Vx8, Vx12} = blake2b_mix(V0, V4, V8, V12, M(S0), M(S1)),
{Vx1, Vx5, Vx9, Vx13} = blake2b_mix(V1, V5, V9, V13, M(S2), M(S3)),
{Vx2, Vx6, Vx10, Vx14} = blake2b_mix(V2, V6, V10, V14, M(S4), M(S5)),
{Vx3, Vx7, Vx11, Vx15} = blake2b_mix(V3, V7, V11, V15, M(S6), M(S7)),
{Vy0, Vy5, Vy10, Vy15} = blake2b_mix(Vx0, Vx5, Vx10, Vx15, M(S8), M(S9)),
{Vy1, Vy6, Vy11, Vy12} = blake2b_mix(Vx1, Vx6, Vx11, Vx12, M(S10), M(S11)),
{Vy2, Vy7, Vy8, Vy13} = blake2b_mix(Vx2, Vx7, Vx8, Vx13, M(S12), M(S13)),
{Vy3, Vy4, Vy9, Vy14} = blake2b_mix(Vx3, Vx4, Vx9, Vx14, M(S14), M(S15)),
<<Vy0:64, Vy1:64, Vy2:64, Vy3:64, Vy4:64, Vy5:64, Vy6:64, Vy7:64, Vy8:64,
Vy9:64, Vy10:64, Vy11:64, Vy12:64, Vy13:64, Vy14:64, Vy15:64>>.
blake2b_mix(Va, Vb, Vc, Vd, X, Y) ->
Va1 = (Va + Vb + X) band ?MAX_64BIT,
Vd1 = rotr64(32, Vd bxor Va1),
Vc1 = (Vc + Vd1) band ?MAX_64BIT,
Vb1 = rotr64(24, Vb bxor Vc1),
Va2 = (Va1 + Vb1 + Y) band ?MAX_64BIT,
Vd2 = rotr64(16, Va2 bxor Vd1),
Vc2 = (Vc1 + Vd2) band ?MAX_64BIT,
Vb2 = rotr64(63, Vb1 bxor Vc2),
{Va2, Vb2, Vc2, Vd2}.
blake_iv() ->
IV0 = 16#6A09E667F3BCC908,
IV1 = 16#BB67AE8584CAA73B,
IV2 = 16#3C6EF372FE94F82B,
IV3 = 16#A54FF53A5F1D36F1,
IV4 = 16#510E527FADE682D1,
IV5 = 16#9B05688C2B3E6C1F,
IV6 = 16#1F83D9ABFB41BD6B,
IV7 = 16#5BE0CD19137E2179,
<<IV0:64, IV1:64, IV2:64, IV3:64, IV4:64, IV5:64, IV6:64, IV7:64>>.
sigma(N) ->
{_, Row} = lists:keyfind(N, 1, sigma()), Row.
sigma() ->
[{0, [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]},
{1, [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3]},
{2, [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4]},
{3, [ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8]},
{4, [ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13]},
{5, [ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9]},
{6, [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11]},
{7, [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10]},
{8, [ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5]},
{9, [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]}].
rotr64(N, I64) ->
<<I64rot:64>> = rotr641(N, <<I64:64>>),
I64rot.
rotr641(16, <<X:(64-16), Y:16>>) -> <<Y:16, X:(64-16)>>;
rotr641(24, <<X:(64-24), Y:24>>) -> <<Y:24, X:(64-24)>>;
rotr641(32, <<X:(64-32), Y:32>>) -> <<Y:32, X:(64-32)>>;
rotr641(63, <<X:(64-63), Y:63>>) -> <<Y:63, X:(64-63)>>.
pad(N, Bin) ->
case (N - (byte_size(Bin) rem N)) rem N of
0 -> Bin;
Pad -> <<Bin/binary, 0:(Pad *8)>>
end.
to_big_endian(Bin) -> to_big_endian(Bin, <<>>).
to_big_endian(<<>>, Acc) -> Acc;
to_big_endian(<<UInt64:1/little-unsigned-integer-unit:64, Rest/binary>>, Acc) ->
to_big_endian(Rest, <<Acc/binary, UInt64:1/big-unsigned-integer-unit:64>>).
to_little_endian(Bin) -> to_little_endian(Bin, <<>>).
to_little_endian(<<>>, Acc) -> Acc;
to_little_endian(<<UInt64:1/big-unsigned-integer-unit:64, Rest/binary>>, Acc) ->
to_little_endian(Rest, <<Acc/binary, UInt64:1/little-unsigned-integer-unit:64>>).
+1 -1
View File
@@ -94,7 +94,7 @@ operand(T) -> T.
str_to_icode(String) when is_list(String) ->
str_to_icode(list_to_binary(String));
str_to_icode(BinStr) ->
Cpts = [size(BinStr) | aeso_memory:binary_to_words(BinStr)],
Cpts = [size(BinStr) | aeb_memory:binary_to_words(BinStr)],
#tuple{ cpts = [ #integer{value = X} || X <- Cpts ] }.
check_event_type(Icode) ->
+71 -25
View File
@@ -17,6 +17,7 @@
, sophia_type_to_typerep/1
, to_sophia_value/4
, to_sophia_value/5
, decode_calldata/3
]).
-include_lib("aebytecode/include/aeb_opcodes.hrl").
@@ -103,13 +104,12 @@ from_string(ContractString, Options) ->
%% General programming errors in the compiler just signal error.
end.
-spec string_to_icode(string(), [option() | permissive_address_literals]) -> map().
string_to_icode(ContractString, Options0) ->
{InferOptions, Options} = lists:partition(fun(Opt) -> Opt == permissive_address_literals end, Options0),
-spec string_to_icode(string(), [option()]) -> map().
string_to_icode(ContractString, Options) ->
Ast = parse(ContractString, Options),
pp_sophia_code(Ast, Options),
pp_ast(Ast, Options),
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | InferOptions]),
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]),
pp_typed_ast(TypedAst, Options),
Icode = ast_to_icode(TypedAst, Options),
pp_icode(Icode, Options),
@@ -150,11 +150,11 @@ check_call(Source, FunName, Args, Options) ->
check_call(ContractString0, FunName, Args, Options, PatchFun) ->
try
%% First check the contract without the __call function and no permissive literals
%% First check the contract without the __call function
#{} = string_to_icode(ContractString0, Options),
ContractString = insert_call_function(ContractString0, FunName, Args, Options),
#{typed_ast := TypedAst,
icode := Icode} = string_to_icode(ContractString, [permissive_address_literals | Options]),
icode := Icode} = string_to_icode(ContractString, Options),
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
RetVMType = case RetType of
@@ -188,7 +188,7 @@ insert_call_function(Code, FunName, Args, Options) ->
[ Code,
"\n\n",
lists:duplicate(Ind, " "),
"function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
"stateful function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
]).
-spec insert_init_function(string(), options()) -> string().
@@ -207,7 +207,7 @@ last_contract_indent(Decls) ->
_ -> 0
end.
-spec to_sophia_value(string(), string(), ok | error | revert, aeso_sophia:data()) ->
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
{ok, aeso_syntax:expr()} | {error, term()}.
to_sophia_value(ContractString, Fun, ResType, Data) ->
to_sophia_value(ContractString, Fun, ResType, Data, []).
@@ -217,7 +217,7 @@ to_sophia_value(ContractString, Fun, ResType, Data) ->
to_sophia_value(_, _, error, Err, _Options) ->
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
to_sophia_value(_, _, revert, Data, _Options) ->
case aeso_heap:from_binary(string, Data) of
case aeb_heap:from_binary(string, Data) of
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
{error, _} = Err -> Err
end;
@@ -226,10 +226,10 @@ to_sophia_value(ContractString, FunName, ok, Data, Options) ->
#{ typed_ast := TypedAst,
type_env := TypeEnv,
icode := Icode } = string_to_icode(ContractString, Options),
{ok, Type0} = get_decode_type(FunName, TypedAst),
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeso_heap:from_binary(VmType, Data) of
case aeb_heap:from_binary(VmType, Data) of
{ok, VmValue} ->
try
{ok, translate_vm_value(VmType, Type, VmValue)}
@@ -256,18 +256,21 @@ to_sophia_value(ContractString, FunName, ok, Data, Options) ->
fun (E) -> io_lib:format("~p", [E]) end)}
end.
address_literal(N) -> {hash, [], <<N:256>>}. % TODO
address_literal(Type, N) -> {Type, [], <<N:256>>}.
%% TODO: somewhere else
translate_vm_value(word, {id, _, "address"}, N) -> address_literal(N);
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(N);
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(N);
translate_vm_value(word, {id, _, "hash"}, N) -> {hash, [], <<N:256>>};
-spec translate_vm_value(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
translate_vm_value(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
translate_vm_value(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
translate_vm_value(word, {id, _, "int"}, N) -> {int, [], N};
translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, N});
translate_vm_value(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
translate_vm_value({tuple, [word, word]}, {id, _, "signature"}, {tuple, [Hi, Lo]}) ->
{hash, [], <<Hi:256, Lo:256>>};
translate_vm_value(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
{bytes, [], <<Val:Len/unit:8>>};
translate_vm_value({tuple, _}, {bytes_t, _, Len}, Val) ->
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
translate_vm_value(string, {id, _, "string"}, S) -> {string, [], S};
translate_vm_value({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
{list, [], [translate_vm_value(VmType, Type, X) || X <- List]};
@@ -311,15 +314,58 @@ translate_vm_value(_VmType, _Type, _Data) ->
throw(cannot_translate_to_sophia).
-spec create_calldata(string(), string(), [string()]) ->
{ok, binary(), aeso_sophia:type(), aeso_sophia:type()}
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
| {error, term()}.
create_calldata(Code, Fun, Args) ->
case check_call(Code, Fun, Args, []) of
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
aeso_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
aeb_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
{error, _} = Err -> Err
end.
-spec decode_calldata(string(), string(), binary()) ->
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
| {error, term()}.
decode_calldata(ContractString, FunName, Calldata) ->
try
#{ typed_ast := TypedAst,
type_env := TypeEnv,
icode := Icode } = string_to_icode(ContractString, []),
{ok, Args, _} = get_decode_type(FunName, TypedAst),
DropArg = fun({arg, _, _, T}) -> T; (T) -> T end,
ArgTypes = lists:map(DropArg, Args),
Type0 = {tuple_t, [], ArgTypes},
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
{ok, {_, VmValue}} ->
try
{tuple, [], Values} = translate_vm_value(VmType, Type, VmValue),
{ok, ArgTypes, Values}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[VmValue, VmType, Type0Str]))],
fun (E) -> E end)}
end;
{error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
fun(E) -> E end)}
end
catch
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
error:{badmatch, {error, missing_function}} ->
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
fun (E) -> E end)};
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
end.
get_arg_icode(Funs) ->
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
[Args] -> Args;
@@ -340,11 +386,11 @@ get_call_type([_ | Contracts]) ->
get_call_type(Contracts).
get_decode_type(FunName, [{contract, _, _, Defs}]) ->
GetType = fun({letfun, _, {id, _, Name}, _, Ret, _}) when Name == FunName -> [Ret];
({fun_decl, _, {id, _, Name}, {fun_t, _, _, _, Ret}}) when Name == FunName -> [Ret];
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}];
({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
(_) -> [] end,
case lists:flatmap(GetType, Defs) of
[Type] -> {ok, Type};
[{Args, Ret}] -> {ok, Args, Ret};
[] -> {error, missing_function}
end;
get_decode_type(FunName, [_ | Contracts]) ->
@@ -352,7 +398,7 @@ get_decode_type(FunName, [_ | Contracts]) ->
get_decode_type(FunName, Contracts).
%% Translate an icode value (error if not value) to an Erlang term that can be
%% consumed by aeso_heap:to_binary().
%% consumed by aeb_heap:to_binary().
icode_to_term(word, {integer, N}) -> N;
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
@@ -400,7 +446,7 @@ to_bytecode([], _) -> [].
extract_type_info(#{functions := Functions} =_Icode) ->
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
TypeInfo = [aeso_abi:function_type_info(list_to_binary(lists:last(Name)),
TypeInfo = [aeb_abi:function_type_info(list_to_binary(lists:last(Name)),
ArgTypesOnly(Args), TypeRep)
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
not is_tuple(Name),
File diff suppressed because it is too large Load Diff
-301
View File
@@ -1,301 +0,0 @@
-module(aeso_heap).
-export([ to_binary/1
, to_binary/2
, from_heap/3
, from_binary/2
, from_binary/3
, maps_with_next_id/1
, set_next_id/2
, heap_fragment/3
, heap_value/3
, heap_value/4
, heap_value_pointer/1
, heap_value_maps/1
, heap_value_offset/1
, heap_value_heap/1
, heap_fragment_maps/1
, heap_fragment_offset/1
, heap_fragment_heap/1
]).
-export_type([binary_value/0, heap_value/0, offset/0, heap_fragment/0]).
-include("aeso_icode.hrl").
-include_lib("aesophia/include/aeso_heap.hrl").
-type word() :: non_neg_integer().
-type pointer() :: word().
-opaque heap_fragment() :: #heap{}.
-type offset() :: non_neg_integer().
-type binary_value() :: binary().
-type heap_value() :: {pointer(), heap_fragment()}.
-spec maps_with_next_id(heap_fragment()) -> #maps{}.
%% Create just a maps value, don't keep rest of Heap
maps_with_next_id(#heap{maps = #maps{next_id = N}}) ->
#maps{ next_id = N }.
-spec set_next_id(heap_fragment(), non_neg_integer()) -> heap_fragment().
set_next_id(Heap, N) ->
Heap#heap{ maps = Heap#heap.maps#maps{ next_id = N } }.
%% -- data type heap_fragment
-spec heap_fragment(binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_fragment().
heap_fragment(Heap) ->
heap_fragment(#maps{ next_id = 0 }, 0, Heap).
-spec heap_fragment(#maps{}, offset(),
binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_fragment().
heap_fragment(Maps, Offset, Heap) ->
#heap{maps = Maps, offset = Offset, heap = Heap}.
-spec heap_fragment_maps(heap_fragment()) -> #maps{}.
heap_fragment_maps(#heap{maps = Maps}) ->
Maps.
-spec heap_fragment_offset(heap_fragment()) -> offset().
heap_fragment_offset(#heap{offset = Offs}) ->
Offs.
-spec heap_fragment_heap(heap_fragment()) -> binary() | #{non_neg_integer() => non_neg_integer()}.
heap_fragment_heap(#heap{heap = Heap}) ->
Heap.
%% -- data type heap_value
-spec heap_value(#maps{}, pointer(),
binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_value().
heap_value(Maps, Ptr, Heap) ->
heap_value(Maps, Ptr, Heap, 0).
-spec heap_value(#maps{}, pointer(),
binary() | #{non_neg_integer() => non_neg_integer()}, offset()) -> heap_value().
heap_value(Maps, Ptr, Heap, Offs) ->
{Ptr, heap_fragment(Maps, Offs, Heap)}.
-spec heap_value_pointer(heap_value()) -> pointer().
heap_value_pointer({Ptr, _}) -> Ptr.
-spec heap_value_maps(heap_value()) -> #maps{}.
heap_value_maps({_, Heap}) -> Heap#heap.maps.
-spec heap_value_offset(heap_value()) -> offset().
heap_value_offset({_, Heap}) -> Heap#heap.offset.
-spec heap_value_heap(heap_value()) ->
binary() | #{non_neg_integer() => non_neg_integer()}.
heap_value_heap({_, Heap}) -> Heap#heap.heap.
%% -- Value to binary --------------------------------------------------------
-spec to_binary(aeso_sophia:data()) -> aeso_sophia:heap().
%% Encode the data as a heap where the first word is the value (for unboxed
%% types) or a pointer to the value (for boxed types).
to_binary(Data) ->
to_binary(Data, 0).
to_binary(Data, BaseAddress) ->
{Address, Memory} = to_binary1(Data, BaseAddress + 32),
R = <<Address:256, Memory/binary>>,
R.
%% Allocate the data in memory, from the given address. Return a pair
%% of memory contents from that address and the value representing the
%% data.
to_binary1(Data,_Address) when is_integer(Data) ->
{Data,<<>>};
to_binary1(Data, Address) when is_binary(Data) ->
%% a string
Words = aeso_memory:binary_to_words(Data),
{Address,<<(size(Data)):256, << <<W:256>> || W <- Words>>/binary>>};
to_binary1(none, Address) -> to_binary1({variant, 0, []}, Address);
to_binary1({some, Value}, Address) -> to_binary1({variant, 1, [Value]}, Address);
to_binary1(word, Address) -> to_binary1({?TYPEREP_WORD_TAG}, Address);
to_binary1(string, Address) -> to_binary1({?TYPEREP_STRING_TAG}, Address);
to_binary1(typerep, Address) -> to_binary1({?TYPEREP_TYPEREP_TAG}, Address);
to_binary1(function, Address) -> to_binary1({?TYPEREP_FUN_TAG}, Address);
to_binary1({list, T}, Address) -> to_binary1({?TYPEREP_LIST_TAG, T}, Address);
to_binary1({option, T}, Address) -> to_binary1({variant, [[], [T]]}, Address);
to_binary1({tuple, Ts}, Address) -> to_binary1({?TYPEREP_TUPLE_TAG, Ts}, Address);
to_binary1({variant, Cons}, Address) -> to_binary1({?TYPEREP_VARIANT_TAG, Cons}, Address);
to_binary1({map, K, V}, Address) -> to_binary1({?TYPEREP_MAP_TAG, K, V}, Address);
to_binary1({variant, Tag, Args}, Address) ->
to_binary1(list_to_tuple([Tag | Args]), Address);
to_binary1(Map, Address) when is_map(Map) ->
Size = maps:size(Map),
%% Sort according to binary ordering
KVs = lists:sort([ {to_binary(K), to_binary(V)} || {K, V} <- maps:to_list(Map) ]),
{Address, <<Size:256, << <<(byte_size(K)):256, K/binary,
(byte_size(V)):256, V/binary>> || {K, V} <- KVs >>/binary >>};
to_binary1({}, _Address) ->
{0, <<>>};
to_binary1(Data, Address) when is_tuple(Data) ->
{Elems,Memory} = to_binaries(tuple_to_list(Data),Address+32*size(Data)),
ElemsBin = << <<W:256>> || W <- Elems>>,
{Address,<< ElemsBin/binary, Memory/binary >>};
to_binary1([],_Address) ->
<<Nil:256>> = <<(-1):256>>,
{Nil,<<>>};
to_binary1([H|T],Address) ->
to_binary1({H,T},Address).
to_binaries([],_Address) ->
{[],<<>>};
to_binaries([H|T],Address) ->
{HRep,HMem} = to_binary1(H,Address),
{TRep,TMem} = to_binaries(T,Address+size(HMem)),
{[HRep|TRep],<<HMem/binary, TMem/binary>>}.
%% Interpret a return value (a binary) using a type rep.
-spec from_heap(Type :: ?Type(), Heap :: binary(), Ptr :: integer()) ->
{ok, term()} | {error, term()}.
from_heap(Type, Heap, Ptr) ->
try {ok, from_binary(#{}, Type, Heap, Ptr)}
catch _:Err ->
%% io:format("** Error: from_heap failed with ~p\n ~p\n", [Err, erlang:get_stacktrace()]),
{error, Err}
end.
%% Base address is the address of the first word of the given heap.
-spec from_binary(T :: ?Type(),
Heap :: binary(),
BaseAddr :: non_neg_integer()) ->
{ok, term()} | {error, term()}.
from_binary(T, Heap = <<V:256, _/binary>>, BaseAddr) ->
from_heap(T, <<0:BaseAddr/unit:8, Heap/binary>>, V);
from_binary(_, Bin, _BaseAddr) ->
{error, {binary_too_short, Bin}}.
-spec from_binary(?Type(), binary()) -> {ok, term()} | {error, term()}.
from_binary(T, Heap) ->
from_binary(T, Heap, 0).
from_binary(_, word, _, V) ->
V;
from_binary(_, signed_word, _, V) ->
<<N:256/signed>> = <<V:256>>,
N;
from_binary(_, bool, _, V) ->
case V of
0 -> false;
1 -> true
end;
from_binary(_, string, Heap, V) ->
StringSize = heap_word(Heap,V),
BitAddr = 8*(V+32),
<<_:BitAddr,Bytes:StringSize/binary,_/binary>> = Heap,
Bytes;
from_binary(_, {tuple, []}, _, _) ->
{};
from_binary(Visited, {tuple,Cpts}, Heap, V) ->
check_circular_refs(Visited, V),
NewVisited = Visited#{V => true},
ElementNums = lists:seq(0, length(Cpts)-1),
TypesAndPointers = lists:zip(Cpts, ElementNums),
ElementAddress = fun(Index) -> V + 32 * Index end,
Element = fun(Index) ->
heap_word(Heap, ElementAddress(Index))
end,
Convert = fun(Type, Index) ->
from_binary(NewVisited, Type, Heap, Element(Index))
end,
Elements = [Convert(T, I) || {T,I} <- TypesAndPointers],
list_to_tuple(Elements);
from_binary(Visited, {list, Elem}, Heap, V) ->
<<Nil:256>> = <<(-1):256>>,
if V==Nil ->
[];
true ->
{H,T} = from_binary(Visited, {tuple,[Elem,{list,Elem}]},Heap,V),
[H|T]
end;
from_binary(Visited, {option, A}, Heap, V) ->
from_binary(Visited, {variant_t, [{none, []}, {some, [A]}]}, Heap, V);
from_binary(Visited, {variant, Cons}, Heap, V) ->
Tag = heap_word(Heap, V),
Args = lists:nth(Tag + 1, Cons),
Visited1 = Visited#{V => true},
{variant, Tag, tuple_to_list(from_binary(Visited1, {tuple, Args}, Heap, V + 32))};
from_binary(Visited, {variant_t, TCons}, Heap, V) -> %% Tagged variants
{Tags, Cons} = lists:unzip(TCons),
{variant, I, Args} = from_binary(Visited, {variant, Cons}, Heap, V),
Tag = lists:nth(I + 1, Tags),
case Args of
[] -> Tag;
_ -> list_to_tuple([Tag | Args])
end;
from_binary(_Visited, {map, A, B}, Heap, Ptr) ->
%% FORMAT: [Size] [KeySize] Key [ValSize] Val .. [KeySize] Key [ValSize] Val
Size = heap_word(Heap, Ptr),
map_binary_to_value(A, B, Size, Heap, Ptr + 32);
from_binary(Visited, typerep, Heap, V) ->
check_circular_refs(Visited, V),
Tag = heap_word(Heap, V),
Arg1 = fun(T, I) -> from_binary(Visited#{V => true}, T, Heap, heap_word(Heap, V + 32 * I)) end,
Arg = fun(T) -> Arg1(T, 1) end,
case Tag of
?TYPEREP_WORD_TAG -> word;
?TYPEREP_STRING_TAG -> string;
?TYPEREP_TYPEREP_TAG -> typerep;
?TYPEREP_LIST_TAG -> {list, Arg(typerep)};
?TYPEREP_TUPLE_TAG -> {tuple, Arg({list, typerep})};
?TYPEREP_VARIANT_TAG -> {variant, Arg({list, {list, typerep}})};
?TYPEREP_MAP_TAG -> {map, Arg(typerep), Arg1(typerep, 2)};
?TYPEREP_FUN_TAG -> function
end.
map_binary_to_value(KeyType, ValType, N, Bin, Ptr) ->
%% Avoid looping on bogus sizes
MaxN = byte_size(Bin) div 64,
Heap = heap_fragment(Bin),
map_from_binary({value, KeyType, ValType}, min(N, MaxN), Heap, Ptr, #{}).
map_from_binary(_, 0, _, _, Map) -> Map;
map_from_binary({value, KeyType, ValType} = Output, I, Heap, Ptr, Map) ->
KeySize = get_word(Heap, Ptr),
KeyPtr = Ptr + 32,
KeyBin = get_chunk(Heap, KeyPtr, KeySize),
ValSize = get_word(Heap, KeyPtr + KeySize),
ValPtr = KeyPtr + KeySize + 32,
ValBin = get_chunk(Heap, ValPtr, ValSize),
%% Keys and values are self contained binaries
{ok, Key} = from_binary(KeyType, KeyBin),
{ok, Val} = from_binary(ValType, ValBin),
map_from_binary(Output, I - 1, Heap, ValPtr + ValSize, Map#{Key => Val}).
check_circular_refs(Visited, V) ->
case maps:is_key(V, Visited) of
true -> exit(circular_references);
false -> ok
end.
heap_word(Heap, Addr) when is_binary(Heap) ->
BitSize = 8*Addr,
<<_:BitSize,W:256,_/binary>> = Heap,
W;
heap_word(Heap, Addr) when is_map(Heap) ->
0 = Addr rem 32, %% Check that it's word aligned.
maps:get(Addr, Heap, 0).
get_word(#heap{offset = Offs, heap = Mem}, Addr) when Addr >= Offs ->
get_word(Mem, Addr - Offs);
get_word(Mem, Addr) when is_binary(Mem) ->
<<_:Addr/unit:8, Word:256, _/binary>> = Mem,
Word.
get_chunk(#heap{offset = Offs, heap = Mem}, Addr, Bytes) when Addr >= Offs ->
get_chunk(Mem, Addr - Offs, Bytes);
get_chunk(Mem, Addr, Bytes) when is_binary(Mem) ->
<<_:Addr/unit:8, Chunk:Bytes/binary, _/binary>> = Mem,
Chunk.
+5 -5
View File
@@ -25,14 +25,14 @@
-include("aeso_icode.hrl").
-type type_def() :: fun(([aeso_sophia:type()]) -> aeso_sophia:type()).
-type type_def() :: fun(([aeb_aevm_data:type()]) -> aeb_aevm_data:type()).
-type bindings() :: any().
-type fun_dec() :: { string()
, [modifier()]
, arg_list()
, expr()
, aeso_sophia:type()}.
, aeb_aevm_data:type()}.
-type modifier() :: private | stateful.
@@ -42,10 +42,10 @@
, functions => [fun_dec()]
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
, env => [bindings()]
, state_type => aeso_sophia:type()
, event_type => aeso_sophia:type()
, state_type => aeb_aevm_data:type()
, event_type => aeb_aevm_data:type()
, types => #{ type_name() => type_def() }
, type_vars => #{ string() => aeso_sophia:type() }
, type_vars => #{ string() => aeb_aevm_data:type() }
, constructors => #{ [string()] => integer() } %% name to tag
, options => [any()]
}.
+1 -10
View File
@@ -1,14 +1,5 @@
-define(Type(), aeso_sophia:type()).
-define(TYPEREP_WORD_TAG, 0).
-define(TYPEREP_STRING_TAG, 1).
-define(TYPEREP_LIST_TAG, 2).
-define(TYPEREP_TUPLE_TAG, 3).
-define(TYPEREP_VARIANT_TAG, 4).
-define(TYPEREP_TYPEREP_TAG, 5).
-define(TYPEREP_MAP_TAG, 6).
-define(TYPEREP_FUN_TAG, 7).
-include_lib("aebytecode/include/aeb_typerep_def.hrl").
-record(arg, {name::string(), type::?Type()}).
+3 -1
View File
@@ -105,7 +105,7 @@ make_args(Args) ->
fun_hash({FName, _, Args, _, TypeRep}) ->
ArgType = {tuple, [T || {_, T} <- Args]},
<<Hash:256>> = aeso_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
<<Hash:256>> = aeb_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
{integer, Hash}.
%% Expects two return addresses below N elements on the stack. Picks the top
@@ -343,6 +343,8 @@ assemble_expr(Funs, Stack, _Tail, #prim_put{ state = State }) ->
%% Environment primitives
assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) ->
[i(?ADDRESS)];
assemble_expr(_Funs, _Stack, _Tail, prim_contract_creator) ->
[i(?CREATOR)];
assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) ->
[i(?ORIGIN)];
assemble_expr(_Funs, _Stack, _Tail, prim_caller) ->
-19
View File
@@ -1,19 +0,0 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc
%%% Memory speifics that compiler and VM need to agree upon
%%% @end
%%% Created : 19 Dec 2018
%%%-------------------------------------------------------------------
-module(aeso_memory).
-export([binary_to_words/1]).
binary_to_words(<<>>) ->
[];
binary_to_words(<<N:256,Bin/binary>>) ->
[N|binary_to_words(Bin)];
binary_to_words(Bin) ->
binary_to_words(<<Bin/binary,0>>).
+31 -11
View File
@@ -99,9 +99,7 @@ con_arg() -> choice(type(), ?RULE(keyword(indexed), type(), set_ann(indexed,
%% -- Let declarations -------------------------------------------------------
letdecl() ->
choice(
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)),
?RULE(keyword('let'), tok(rec), sep1(letdef(), tok('and')), {letrec, _1, _3})).
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)).
letdef() -> choice(valdef(), fundef()).
@@ -136,11 +134,15 @@ type200() ->
type300() -> type400().
type400() ->
?RULE(typeAtom(), optional(type_args()),
choice(
[?RULE(typeAtom(), optional(type_args()),
case _2 of
none -> _1;
{ok, Args} -> {app_t, get_ann(_1), _1, Args}
end).
end),
?RULE(id("bytes"), parens(token(int)),
{bytes_t, get_ann(_1), element(3, _2)})
]).
typeAtom() ->
?LAZY_P(choice(
@@ -203,8 +205,8 @@ exprAtom() ->
?LAZY_P(begin
Expr = ?LAZY_P(expr()),
choice(
[ id(), con(), token(qid), token(qcon)
, token(hash), token(string), token(char)
[ id_or_addr(), con(), token(qid), token(qcon)
, token(bytes), token(string), token(char)
, token(int)
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
, {bool, keyword(true), true}
@@ -326,6 +328,26 @@ token(Tag) ->
{Tok, {Line, Col}, Val} -> {Tok, pos_ann(Line, Col), Val}
end).
id(Id) ->
?LET_P({id, A, X} = Y, id(),
if X == Id -> Y;
true -> fail({A, "expected 'bytes'"})
end).
id_or_addr() ->
?RULE(id(), parse_addr_literal(_1)).
parse_addr_literal(Id = {id, Ann, Name}) ->
case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_"]) of
false -> Id;
true ->
try aeser_api_encoder:decode(list_to_binary(Name)) of
{Type, Bin} -> {Type, Ann, Bin}
catch _:_ ->
Id
end
end.
%% -- Helpers ----------------------------------------------------------------
keyword(K) -> ann(tok(K)).
@@ -417,7 +439,7 @@ build_if(Ann, Cond, Then, [{elif, Ann1, Cond1, Then1} | Elses]) ->
build_if(Ann, Cond, Then, [{else, _Ann, Else}]) ->
{'if', Ann, Cond, Then, Else};
build_if(Ann, Cond, Then, []) ->
{'if', Ann, Cond, Then, {unit, [{origin, system}]}}.
{'if', Ann, Cond, Then, {tuple, [{origin, system}], []}}.
else_branches([Elif = {elif, _, _, _} | Stmts], Acc) ->
else_branches(Stmts, [Elif | Acc]);
@@ -433,7 +455,6 @@ fun_t(Domains, Type) ->
lists:foldr(fun({Dom, Ann}, T) -> {fun_t, Ann, [], Dom, T} end,
Type, Domains).
tuple_e(Ann, []) -> {unit, Ann};
tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple
tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
@@ -454,10 +475,9 @@ parse_pattern({record, Ann, Fs}) ->
{record, Ann, lists:map(fun parse_field_pattern/1, Fs)};
parse_pattern(E = {con, _, _}) -> E;
parse_pattern(E = {id, _, _}) -> E;
parse_pattern(E = {unit, _}) -> E;
parse_pattern(E = {int, _, _}) -> E;
parse_pattern(E = {bool, _, _}) -> E;
parse_pattern(E = {hash, _, _}) -> E;
parse_pattern(E = {bytes, _, _}) -> E;
parse_pattern(E = {string, _, _}) -> E;
parse_pattern(E = {char, _, _}) -> E;
parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
+14 -8
View File
@@ -160,8 +160,7 @@ decl(D = {letfun, Attrs, _, _, _, _}) ->
text(atom_to_list(Mod));
(_) -> empty() end,
hsep(lists:map(Mod, Attrs) ++ [letdecl("function", D)]);
decl(D = {letval, _, _, _, _}) -> letdecl("let", D);
decl(D = {letrec, _, _}) -> letdecl("let", D).
decl(D = {letval, _, _, _, _}) -> letdecl("let", D).
-spec expr(aeso_syntax:expr(), options()) -> doc().
expr(E, Options) ->
@@ -184,9 +183,7 @@ name({typed, _, Name, _}) -> name(Name).
letdecl(Let, {letval, _, F, T, E}) ->
block_expr(0, hsep([text(Let), typed(name(F), T), text("=")]), E);
letdecl(Let, {letfun, _, F, Args, T, E}) ->
block_expr(0, hsep([text(Let), typed(beside(name(F), args(Args)), T), text("=")]), E);
letdecl(Let, {letrec, _, [D | Ds]}) ->
hsep(text(Let), above([ letdecl("rec", D) | [ letdecl("and", D1) || D1 <- Ds ] ])).
block_expr(0, hsep([text(Let), typed(beside(name(F), args(Args)), T), text("=")]), E).
-spec args([aeso_syntax:arg()]) -> doc().
args(Args) ->
@@ -236,6 +233,8 @@ type({app_t, _, Type, Args}) ->
beside(type(Type), tuple_type(Args));
type({tuple_t, _, Args}) ->
tuple_type(Args);
type({bytes_t, _, Len}) ->
text(lists:concat(["bytes(", Len, ")"]));
type({named_arg_t, _, Name, Type, _Default}) ->
%% Drop the default value
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
@@ -319,9 +318,17 @@ expr_p(_, E = {int, _, N}) ->
end,
text(S);
expr_p(_, {bool, _, B}) -> text(atom_to_list(B));
expr_p(_, {hash, _, <<N:256>>}) -> text("#" ++ integer_to_list(N, 16));
expr_p(_, {bytes, _, Bin}) ->
Digits = byte_size(Bin),
<<N:Digits/unit:8>> = Bin,
text(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N])));
expr_p(_, {hash, _, <<N:512>>}) -> text("#" ++ integer_to_list(N, 16));
expr_p(_, {unit, _}) -> text("()");
expr_p(_, {Type, _, Bin})
when Type == account_pubkey;
Type == contract_pubkey;
Type == oracle_pubkey;
Type == oracle_query_id ->
text(binary_to_list(aeser_api_encoder:encode(Type, Bin)));
expr_p(_, {string, _, S}) -> term(binary_to_list(S));
expr_p(_, {char, _, C}) ->
case C of
@@ -425,7 +432,6 @@ statements(Stmts) ->
statement(S = {letval, _, _, _, _}) -> letdecl("let", S);
statement(S = {letfun, _, _, _, _, _}) -> letdecl("let", S);
statement(S = {letrec, _, _}) -> letdecl("let", S);
statement(E) -> expr(E).
get_elifs(Expr) -> get_elifs(Expr, []).
+7 -9
View File
@@ -20,7 +20,7 @@ lexer() ->
CON = [UPPER, "[a-zA-Z0-9_]*"],
INT = [DIGIT, "+"],
HEX = ["0x", HEXDIGIT, "+"],
HASH = ["#", HEXDIGIT, "+"],
BYTES = ["#", HEXDIGIT, "+"],
WS = "[\\000-\\ ]+",
ID = [LOWER, "[a-zA-Z0-9_']*"],
TVAR = ["'", ID],
@@ -36,8 +36,8 @@ lexer() ->
, {"\\*/", pop(skip())}
, {"[^/*]+|[/*]", skip()} ],
Keywords = ["contract", "include", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "namespace"],
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "true", "false", "mod", "public", "private", "indexed", "internal", "namespace"],
KW = string:join(Keywords, "|"),
Rules =
@@ -54,7 +54,7 @@ lexer() ->
, {STRING, token(string, fun parse_string/1)}
, {HEX, token(hex, fun parse_hex/1)}
, {INT, token(int, fun list_to_integer/1)}
, {HASH, token(hash, fun parse_hash/1)}
, {BYTES, token(bytes, fun parse_bytes/1)}
%% Identifiers (qualified first!)
, {QID, token(qid, fun(S) -> string:tokens(S, ".") end)}
@@ -117,10 +117,8 @@ unescape([C | Chars], Acc) ->
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
parse_hash("#" ++ Chars) ->
parse_bytes("#" ++ Chars) ->
N = list_to_integer(Chars, 16),
case length(Chars) > 64 of %% 64 hex digits = 32 bytes
true -> <<N:64/unit:8>>; %% signature
false -> <<N:32/unit:8>> %% address
end.
Digits = (length(Chars) + 1) div 2,
<<N:Digits/unit:8>>.
-30
View File
@@ -1,30 +0,0 @@
-module(aeso_sophia).
-export_type([data/0,
type/0,
heap/0]).
-type type() :: word | signed_word | string | typerep | function
| {list, type()}
| {option, type()}
| {tuple, [type()]}
| {variant, [[type()]]}.
-type data() :: none
| {some, data()}
| {option, data()}
| word
| string
| {list, data()}
| {tuple, [data()]}
| {variant, integer(), [data()]}
| integer()
| binary()
| [data()]
| {}
| {data()}
| {data(), data()}.
-type heap() :: binary().
+7 -4
View File
@@ -25,7 +25,7 @@
-type ann_origin() :: system | user.
-type ann_format() :: '?:' | hex | infix | prefix | elif.
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}].
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} | stateful | private].
-type name() :: string().
-type id() :: {id, ann(), name()}.
@@ -43,8 +43,7 @@
-type letbind()
:: {letval, ann(), id(), type(), expr()}
| {letfun, ann(), id(), [arg()], type(), expr()}
| {letrec, ann(), [letbind()]}.
| {letfun, ann(), id(), [arg()], type(), expr()}.
-type arg() :: {arg, ann(), id(), type()}.
@@ -60,6 +59,7 @@
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
| {app_t, ann(), type(), [type()]}
| {tuple_t, ann(), [type()]}
| {bytes_t, ann(), integer()}
| id() | qid()
| con() | qcon() %% contracts
| tvar().
@@ -70,7 +70,10 @@
:: {int, ann(), integer()}
| {bool, ann(), true | false}
| {hash, ann(), binary()}
| {unit, ann()}
| {account_pubkey, binary()}
| {contract_pubkey, binary()}
| {oracle_pubkey, binary()}
| {oracle_query_id, binary()}
| {string, ann(), binary()}
| {char, ann(), integer()}.
+18 -22
View File
@@ -39,11 +39,6 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
Top = Fun(K, X),
Bound = fun LB ({letval, _, Y, _, _}) -> BindExpr(Y);
LB ({letfun, _, F, _, _, _}) -> BindExpr(F);
LB ({letrec, _, Ds}) -> Sum(lists:map(LB, Ds));
LB (_) -> Zero
end,
Rec = case X of
%% lists (bound things in head scope over tail)
[A | As] -> Scoped(Same(A), Same(As));
@@ -55,7 +50,6 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{fun_decl, _, _, T} -> Type(T);
{letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]);
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Scoped(BindExpr(Xs), Expr(E))]);
{letrec, _, Ds} -> Plus(Bound(Ds), Decl(Ds));
%% typedef()
{alias_t, T} -> Type(T);
{record_t, Fs} -> Type(Fs);
@@ -104,29 +98,31 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
%% Name dependencies
used_ids(E) ->
[ X || {term, [X]} <- used(E) ].
[ X || {{term, [X]}, _} <- used(E) ].
used_types(T) ->
[ X || {type, [X]} <- used(T) ].
[ X || {{type, [X]}, _} <- used(T) ].
-type entity() :: {term, [string()]}
| {type, [string()]}
| {namespace, [string()]}.
-spec entity_alg() -> alg([entity()]).
-spec entity_alg() -> alg(#{entity() => aeso_syntax:ann()}).
entity_alg() ->
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
Unbind = fun(bound_term) -> term; (bound_type) -> type end,
Remove = fun(Keys, Map) -> lists:foldl(fun maps:remove/2, Map, Keys) end,
Scoped = fun(Xs, Ys) ->
{Bound, Others} = lists:partition(IsBound, Ys),
Bound = [E || E <- maps:keys(Ys), IsBound(E)],
Others = Remove(Bound, Ys),
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
lists:umerge(Xs -- Bound1, Others)
maps:merge(Remove(Bound1, Xs), Others)
end,
#alg{ zero = []
, plus = fun lists:umerge/2
#alg{ zero = #{}
, plus = fun maps:merge/2
, scoped = Scoped }.
-spec used(_) -> [entity()].
-spec used(_) -> [{entity(), aeso_syntax:ann()}].
used(D) ->
Kind = fun(expr) -> term;
(bind_expr) -> bound_term;
@@ -134,14 +130,14 @@ used(D) ->
(bind_type) -> bound_type
end,
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
NotBound = fun({Tag, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
NotBound = fun({{Tag, _}, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
Xs =
fold(entity_alg(),
fun(K, {id, _, X}) -> [{Kind(K), [X]}];
(K, {qid, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(K, {con, _, X}) -> [{Kind(K), [X]}];
(K, {qcon, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(_, _) -> []
end, decl, D),
maps:to_list(fold(entity_alg(),
fun(K, {id, Ann, X}) -> #{{Kind(K), [X]} => Ann};
(K, {qid, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
(K, {con, Ann, X}) -> #{{Kind(K), [X]} => Ann};
(K, {qcon, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
(_, _) -> #{}
end, decl, D)),
lists:filter(NotBound, Xs).
+2 -2
View File
@@ -1,6 +1,6 @@
{application, aesophia,
[{description, "Contract Language for Aethernity"},
{vsn, {cmd, "cat VERSION | tr -d '[:space:]'"}},
[{description, "Contract Language for aeternity"},
{vsn, "3.0.0"},
{registered, []},
{applications,
[kernel,
-78
View File
@@ -1,78 +0,0 @@
-module(aesophia).
-export([main/1]).
-define(OPT_SPEC,
[ {src_file, undefined, undefined, string, "Sophia source code file"}
, {version, $V, "version", undefined, "Print compiler version"}
, {verbose, $v, "verbose", undefined, "Verbose output"}
, {help, $h, "help", undefined, "Show this message"}
, {outfile, $o, "out", string, "Output file (experimental)"} ]).
usage() ->
getopt:usage(?OPT_SPEC, "aesophia").
main(Args) ->
case getopt:parse(?OPT_SPEC, Args) of
{ok, {Opts, []}} ->
case Opts of
[version] ->
print_vsn();
[help] ->
usage();
_ ->
compile(Opts)
end;
{ok, {_, NonOpts}} ->
io:format("Can't understand ~p\n\n", [NonOpts]),
usage();
{error, {Reason, Data}} ->
io:format("Error: ~s ~p\n\n", [Reason, Data]),
usage()
end.
compile(Opts) ->
case proplists:get_value(src_file, Opts, undefined) of
undefined ->
io:format("Error: no input source file\n\n"),
usage();
File ->
compile(File, Opts)
end.
compile(File, Opts) ->
Verbose = proplists:get_value(verbose, Opts, false),
OutFile = proplists:get_value(outfile, Opts, undefined),
try
Res = aeso_compiler:file(File, [pp_ast || Verbose]),
write_outfile(OutFile, Res),
io:format("\nCompiled successfully!\n")
catch
%% The compiler errors.
error:{type_errors, Errors} ->
io:format("\n~s\n", [string:join(["** Type errors\n" | Errors], "\n")]);
error:{parse_errors, Errors} ->
io:format("\n~s\n", [string:join(["** Parse errors\n" | Errors], "\n")]);
error:{code_errors, Errors} ->
ErrorStrings = [ io_lib:format("~p", [E]) || E <- Errors ],
io:format("\n~s\n", [string:join(["** Code errors\n" | ErrorStrings], "\n")]);
%% General programming errors in the compiler.
error:Error ->
Where = hd(erlang:get_stacktrace()),
ErrorString = io_lib:format("Error: ~p in\n ~p", [Error,Where]),
io:format("\n~s\n", [ErrorString])
end.
write_outfile(undefined, _) -> ok;
write_outfile(Out, ResMap) ->
%% Lazy approach
file:write_file(Out, term_to_binary(ResMap)),
io:format("Output written to: ~s\n", [Out]).
print_vsn() ->
{ok, Vsn} = aeso_compiler:version(),
io:format("Compiler version: ~s\n", [Vsn]).
+32 -9
View File
@@ -4,6 +4,9 @@
-compile(export_all).
-define(SANDBOX(Code), sandbox(fun() -> Code end)).
-define(DUMMY_HASH_WORD, 16#123).
-define(DUMMY_HASH, <<0:30/unit:8, 127, 119>>). %% 16#123
-define(DUMMY_HASH_LIT, "#0000000000000000000000000000000000000000000000000000000000000123").
sandbox(Code) ->
Parent = self(),
@@ -19,8 +22,8 @@ sandbox(Code) ->
malicious_from_binary_test() ->
CircularList = from_words([32, 1, 32]), %% Xs = 1 :: Xs
{ok, {error, circular_references}} = ?SANDBOX(aeso_heap:from_binary({list, word}, CircularList)),
{ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeso_heap:from_binary(word, <<1, 2, 3, 4>>)),
{ok, {error, circular_references}} = ?SANDBOX(aeb_heap:from_binary({list, word}, CircularList)),
{ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeb_heap:from_binary(word, <<1, 2, 3, 4>>)),
ok.
from_words(Ws) ->
@@ -97,7 +100,15 @@ calldata_test() ->
Map = #{ <<"a">> => 4 },
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
[16#123, 16#456] = encode_decode_calldata("foo", ["hash", "address"], ["#123", "#456"]),
[?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"],
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
[?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] =
encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]),
[119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]),
[16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
ok.
calldata_init_test() ->
@@ -126,7 +137,9 @@ parameterized_contract(FunName, Types) ->
parameterized_contract(ExtraCode, FunName, Types) ->
lists:flatten(
["contract Dummy =\n",
["contract Remote =\n"
" function bla : () => ()\n\n"
"contract Dummy =\n",
ExtraCode, "\n",
" type an_alias('a) = (string, 'a)\n"
" record r = {x : an_alias(int), y : variant}\n"
@@ -139,13 +152,15 @@ oracle_test() ->
" function question(o, q : oracle_query(list(string), option(int))) =\n"
" Oracle.get_question(o, q)\n",
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
aeso_compiler:check_call(Contract, "question", ["#123", "#456"], []),
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
"oq_1111111111111111111111111111113AFEFpt5"], []),
ok.
permissive_literals_fail_test() ->
Contract =
"contract OracleTest =\n"
" function haxx(o : oracle(list(string), option(int))) =\n"
" stateful function haxx(o : oracle(list(string), option(int))) =\n"
" Chain.spend(o, 1000000)\n",
{error, <<"Type errors\nCannot unify", _/binary>>} =
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
@@ -161,7 +176,15 @@ encode_decode_calldata(FunName, Types, Args, RetType) ->
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
{ok, Calldata, CalldataType, RetVMType1} = aeso_compiler:create_calldata(Code, FunName, Args),
?assertEqual(RetVMType1, RetVMType),
{ok, {_Hash, ArgTuple}} = aeso_heap:from_binary(CalldataType, Calldata),
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
case FunName of
"init" ->
ok;
_ ->
{ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata),
Values = [ prettypr:format(aeso_pretty:expr(V)) || V <- ValueASTs ],
?assertMatch({X, X}, {Args, Values})
end,
tuple_to_list(ArgTuple).
encode_decode(T, D) ->
@@ -169,8 +192,8 @@ encode_decode(T, D) ->
D.
encode(D) ->
aeso_heap:to_binary(D).
aeb_heap:to_binary(D).
decode(T,B) ->
{ok, D} = aeso_heap:from_binary(T, B),
{ok, D} = aeb_heap:from_binary(T, B),
D.
+58 -27
View File
@@ -5,41 +5,72 @@
do_test() ->
test_contract(1),
test_contract(2).
test_contract(2),
test_contract(3).
test_contract(N) ->
{Contract,DecACI} = test_cases(N),
{ok,Enc} = aeso_aci:encode(Contract),
?assertEqual(DecACI, jsx:decode(Enc)).
{Contract,MapACI,DecACI} = test_cases(N),
{ok,JSON} = aeso_aci:encode(Contract),
?assertEqual(MapACI, jsx:decode(JSON, [return_maps])),
?assertEqual(DecACI, aeso_aci:decode(JSON)).
test_cases(1) ->
Contract = <<"contract C =\n"
" function a(i : int) = i+1\n">>,
DecodedACI = [{<<"contract">>,
[{<<"name">>,<<"C">>},
{<<"type_defs">>,[]},
{<<"functions">>,
[[{<<"name">>,<<"a">>},
{<<"arguments">>,
[[{<<"name">>,<<"i">>},{<<"type">>,<<"int">>}]]},
{<<"type">>,<<"int">>},
{<<"stateful">>,false}]]}]}],
{Contract,DecodedACI};
MapACI = #{<<"contract">> =>
#{<<"name">> => <<"C">>,
<<"type_defs">> => [],
<<"functions">> =>
[#{<<"name">> => <<"a">>,
<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> => [<<"int">>]}],
<<"returns">> => <<"int">>,
<<"stateful">> => false}]}},
DecACI = <<"contract C =\n"
" function a : (int) => int\n">>,
{Contract,MapACI,DecACI};
test_cases(2) ->
Contract = <<"contract C =\n"
" type allan = int\n"
" function a(i : allan) = i+1\n">>,
DecodedACI = [{<<"contract">>,
[{<<"name">>,<<"C">>},
{<<"type_defs">>,
[[{<<"name">>,<<"allan">>},
{<<"vars">>,[]},
{<<"typedef">>,<<"int">>}]]},
{<<"functions">>,
[[{<<"name">>,<<"a">>},
{<<"arguments">>,
[[{<<"name">>,<<"i">>},{<<"type">>,<<"int">>}]]},
{<<"type">>,<<"int">>},
{<<"stateful">>,false}]]}]}],
{Contract,DecodedACI}.
MapACI = #{<<"contract">> =>
#{<<"name">> => <<"C">>,
<<"type_defs">> =>
[#{<<"name">> => <<"allan">>,
<<"typedef">> => <<"int">>,
<<"vars">> => []}],
<<"functions">> =>
[#{<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> => [<<"int">>]}],
<<"name">> => <<"a">>,
<<"returns">> => <<"int">>,
<<"stateful">> => false}]}},
DecACI = <<"contract C =\n"
" function a : (int) => int\n">>,
{Contract,MapACI,DecACI};
test_cases(3) ->
Contract = <<"contract C =\n"
" datatype bert('a) = Bin('a)\n"
" function a(i : bert(string)) = 1\n">>,
MapACI = #{<<"contract">> =>
#{<<"functions">> =>
[#{<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> =>
[#{<<"C.bert">> => [<<"string">>]}]}],
<<"name">> => <<"a">>,<<"returns">> => <<"int">>,
<<"stateful">> => false}],
<<"name">> => <<"C">>,
<<"type_defs">> =>
[#{<<"name">> => <<"bert">>,
<<"typedef">> =>
#{<<"variant">> =>
[#{<<"Bin">> => [<<"'a">>]}]},
<<"vars">> => [#{<<"name">> => <<"'a">>}]}]}},
DecACI = <<"contract C =\n"
" datatype bert('a) = Bin('a)\n"
" function a : (C.bert(string)) => int\n">>,
{Contract,MapACI,DecACI}.
-73
View File
@@ -1,73 +0,0 @@
%%%=============================================================================
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Unit tests for the aeso_blake2 module
%%%
%%% In addition the aeso_blake2 module was compared to the C reference
%%% implementation by writing a QuickCheck property.
%%% @end
%%%=============================================================================
-module(aeso_blake2_tests).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
blake2b_test_() ->
{"Tests for BLAKE2b hash implementation",
[ fun() -> blake2b(Data) end || Data <- test_data_blake2b() ]}.
blake2b({Msg0, Key0, ExpectedOut0}) ->
Msg = mk_binary(Msg0),
Key = mk_binary(Key0),
ExpectedOut = mk_binary(ExpectedOut0),
Result = aeso_blake2:blake2b(byte_size(ExpectedOut), Msg, Key),
?assertEqual(Result, {ok, ExpectedOut}).
mk_binary(Bin) when is_binary(Bin) -> Bin;
mk_binary(HexStr) when is_list(HexStr) ->
<< << (erlang:list_to_integer([H], 16)):4 >> || H <- HexStr >>.
test_data_blake2b() ->
[ %% {Message, Key, ExpectedHash}
%% From Wikipedia
%% https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2
{<<>>,
<<>>,
"786A02F742015903C6C6FD852552D272912F4740E15847618A86E217F71F5419D25E1031AFEE585313896444934EB04B903A685B1448B755D56F701AFE9BE2CE"}
, {<<"The quick brown fox jumps over the lazy dog">>,
<<>>,
"A8ADD4BDDDFD93E4877D2746E62817B116364A1FA7BC148D95090BC7333B3673F82401CF7AA2E4CB1ECD90296E3F14CB5413F8ED77BE73045B13914CDCD6A918"}
%% From reference implementation testvectors
%% https://github.com/BLAKE2/BLAKE2/tree/master/testvectors
%%
%% Non-keyed
, {"00",
"",
"2FA3F686DF876995167E7C2E5D74C4C7B6E48F8068FE0E44208344D480F7904C36963E44115FE3EB2A3AC8694C28BCB4F5A0F3276F2E79487D8219057A506E4B"}
, {"0001",
"",
"1C08798DC641ABA9DEE435E22519A4729A09B2BFE0FF00EF2DCD8ED6F8A07D15EAF4AEE52BBF18AB5608A6190F70B90486C8A7D4873710B1115D3DEBBB4327B5"}
, {"00010203040506070809",
"",
"29102511D749DB3CC9B4E335FA1F5E8FACA8421D558F6A3F3321D50D044A248BA595CFC3EFD3D2ADC97334DA732413F5CBF4751C362BA1D53862AC1E8DABEEE8"}
%% Keyed
, {"",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568"}
, {"00",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"961f6dd1e4dd30f63901690c512e78e4b45e4742ed197c3c5e45c549fd25f2e4187b0bc9fe30492b16b0d0bc4ef9b0f34c7003fac09a5ef1532e69430234cebd"}
, {"0001",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"da2cfbe2d8409a0f38026113884f84b50156371ae304c4430173d08a99d9fb1b983164a3770706d537f49e0c916d9f32b95cc37a95b99d857436f0232c88a965"}
, {"00010203040506070809",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"4fe181f54ad63a2983feaaf77d1e7235c2beb17fa328b6d9505bda327df19fc37f02c4b6f0368ce23147313a8e5738b5fa2a95b29de1c7f8264eb77b69f585cd"}
].
-endif.
+115 -3
View File
@@ -8,6 +8,8 @@
-module(aeso_compiler_tests).
-compile([export_all, nowarn_export_all]).
-include_lib("eunit/include/eunit.hrl").
%% Very simply test compile the given contracts. Only basic checks
@@ -16,10 +18,14 @@
simple_compile_test_() ->
[ {"Testing the " ++ ContractName ++ " contract",
fun() ->
case compile(ContractName) of
#{byte_code := ByteCode,
contract_source := _,
type_info := _} = compile(ContractName),
?assertMatch(Code when is_binary(Code), ByteCode)
type_info := _} -> ?assertMatch(Code when is_binary(Code), ByteCode);
ErrBin ->
io:format("\n~s", [ErrBin]),
error(ErrBin)
end
end} || ContractName <- compilable_contracts() ] ++
[ {"Testing error messages of " ++ ContractName,
fun() ->
@@ -80,6 +86,7 @@ compilable_contracts() ->
"dutch_auction",
"environment",
"factorial",
"functions",
"fundme",
"identity",
"maps",
@@ -97,7 +104,12 @@ compilable_contracts() ->
"variant_types",
"state_handling",
"events",
"include"
"include",
"basic_auth",
"bitcoin_auth",
"address_literals",
"bytes_equality",
"address_chain"
].
%% Contracts that should produce type errors
@@ -179,6 +191,8 @@ failing_contracts() ->
" - r' (at line 5, column 10)">>,
<<"Repeated name x in pattern\n"
" x :: x (at line 26, column 7)">>,
<<"Repeated argument x to function repeated_arg (at line 44, column 12).">>,
<<"Repeated argument y to function repeated_arg (at line 44, column 12).">>,
<<"No record type with fields y, z (at line 14, column 22)">>,
<<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
<<"Record type r2 does not have field y (at line 15, column 22)">>]}
@@ -217,4 +231,102 @@ failing_contracts() ->
, {"bad_include_and_ns",
[<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
<<"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]}
, {"bad_address_literals",
[<<"The type bytes(32) is not a contract type\n"
"when checking that the contract literal at line 32, column 5\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n"
" bytes(32)">>,
<<"The type oracle(int, bool) is not a contract type\n"
"when checking that the contract literal at line 30, column 5\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n"
" oracle(int, bool)">>,
<<"The type address is not a contract type\n"
"when checking that the contract literal at line 28, column 5\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n"
" address">>,
<<"Cannot unify oracle_query('1, '2)\n"
" and Remote\n"
"when checking the type of the expression at line 25, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('1, '2)\n"
"against the expected type\n"
" Remote">>,
<<"Cannot unify oracle_query('3, '4)\n"
" and bytes(32)\n"
"when checking the type of the expression at line 23, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('3, '4)\n"
"against the expected type\n"
" bytes(32)">>,
<<"Cannot unify oracle_query('5, '6)\n"
" and oracle(int, bool)\n"
"when checking the type of the expression at line 21, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('5, '6)\n"
"against the expected type\n"
" oracle(int, bool)">>,
<<"Cannot unify oracle('7, '8)\n"
" and Remote\n"
"when checking the type of the expression at line 18, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('7, '8)\n"
"against the expected type\n"
" Remote">>,
<<"Cannot unify oracle('9, '10)\n"
" and bytes(32)\n"
"when checking the type of the expression at line 16, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('9, '10)\n"
"against the expected type\n"
" bytes(32)">>,
<<"Cannot unify oracle('11, '12)\n"
" and oracle_query(int, bool)\n"
"when checking the type of the expression at line 14, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('11, '12)\n"
"against the expected type\n"
" oracle_query(int, bool)">>,
<<"Cannot unify address\n"
" and oracle(int, bool)\n"
"when checking the type of the expression at line 11, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n"
" oracle(int, bool)">>,
<<"Cannot unify address\n"
" and Remote\n"
"when checking the type of the expression at line 9, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n"
" Remote">>,
<<"Cannot unify address\n"
" and bytes(32)\n"
"when checking the type of the expression at line 7, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n"
" bytes(32)">>]}
, {"stateful",
[<<"Cannot reference stateful function Chain.spend (at line 13, column 33)\nin the definition of non-stateful function fail1.">>,
<<"Cannot reference stateful function local_spend (at line 14, column 33)\nin the definition of non-stateful function fail2.">>,
<<"Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>,
<<"Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>,
<<"Cannot reference stateful function Chain.spend (at line 35, column 53)\nin the definition of non-stateful function fail5.">>,
<<"Cannot pass non-zero value argument 1000 (at line 48, column 55)\nin the definition of non-stateful function fail6.">>,
<<"Cannot pass non-zero value argument 1000 (at line 49, column 54)\nin the definition of non-stateful function fail7.">>,
<<"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>]}
, {"bad_init_state_access",
[<<"The init function should return the initial state as its result and cannot write the state,\n"
"but it calls\n"
" - set_state (at line 11, column 5), which calls\n"
" - roundabout (at line 8, column 36), which calls\n"
" - put (at line 7, column 37)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n"
" - new_state (at line 12, column 5), which calls\n"
" - state (at line 5, column 27)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n"
" - state (at line 13, column 13)">>]}
].
+3 -3
View File
@@ -41,14 +41,14 @@ all_tokens() ->
%% Operators
lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++
%% Keywords
lists:map(Lit, [contract, type, 'let', switch, rec, 'and']) ++
lists:map(Lit, [contract, type, 'let', switch]) ++
%% Comment token (not an actual token), just for tests
[{comment, 0, "// *Comment!\"\n"},
{comment, 0, "/* bla /* bla bla */*/"}] ++
%% Literals
[ Lit(true), Lit(false)
, Tok(id, "foo"), Tok(id, "_"), Tok(con, "Foo")
, Tok(hash, Hash)
, Tok(bytes, Hash)
, Tok(int, 1234567890), Tok(hex, 9876543210)
, Tok(string, <<"bla\"\\\b\e\f\n\r\t\vbla">>)
].
@@ -78,7 +78,7 @@ show_token({param, _, P}) -> "@" ++ P;
show_token({string, _, S}) -> fmt(binary_to_list(S));
show_token({int, _, N}) -> fmt(N);
show_token({hex, _, N}) -> fmt("0x~.16b", N);
show_token({hash, _, <<N:256>>}) -> fmt("#~.16b", N);
show_token({bytes, _, <<N:256>>}) -> fmt("#~64.16.0b", N);
show_token({comment, _, S}) -> S;
show_token({_, _, _}) -> "TODO".
+33
View File
@@ -0,0 +1,33 @@
contract Remote =
function main : (int) => ()
contract AddrChain =
type o_type = oracle(string, map(string, int))
type oq_type = oracle_query(string, map(string, int))
function is_o(a : address) =
Address.is_oracle(a)
function is_c(a : address) =
Address.is_contract(a)
// function get_o(a : address) : option(o_type) =
// Address.get_oracle(a)
// function get_c(a : address) : option(Remote) =
// Address.get_contract(a)
function check_o(o : o_type) =
Oracle.check(o)
function check_oq(o : o_type, oq : oq_type) =
Oracle.check_query(o, oq)
// function h_to_i(h : hash) : int =
// Hash.to_int(h)
// function a_to_i(a : address) : int =
// Address.to_int(a) mod 10 ^ 16
function c_creator() : address =
Contract.creator
+14
View File
@@ -0,0 +1,14 @@
contract Remote =
function foo : () => ()
contract AddressLiterals =
function addr() : address =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
function oracle() : oracle(int, bool) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
function query() : oracle_query(int, bool) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
function contr() : Remote =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
-5
View File
@@ -36,11 +36,6 @@ contract AllSyntax =
(x, [y, z]) => bar({x = z, y = -y + - -z * (-1)})
(x, y :: _) => ()
function mutual() =
let rec recFun(x : int) = mutFun(x)
and mutFun(x) = if(x =< 0) 1 else x * recFun(x - 1)
recFun(0)
let hash : address = #01ab0fff11
let b = false
let qcon = Mod.Con
+33
View File
@@ -0,0 +1,33 @@
contract Remote =
function foo : () => ()
contract AddressLiterals =
function addr1() : bytes(32) =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
function addr2() : Remote =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
function addr3() : oracle(int, bool) =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
function oracle1() : oracle_query(int, bool) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
function oracle2() : bytes(32) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
function oracle3() : Remote =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
function query1() : oracle(int, bool) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
function query2() : bytes(32) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
function query3() : Remote =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
function contr1() : address =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
function contr2() : oracle(int, bool) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
function contr3() : bytes(32) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
+13
View File
@@ -0,0 +1,13 @@
contract BadInit =
type state = int
function new_state(n) = state + n
stateful function roundabout(n) = put(n)
stateful function set_state(n) = roundabout(n)
stateful function init() =
set_state(4)
new_state(0)
state + state
+19
View File
@@ -0,0 +1,19 @@
// Contract replicating "normal" Aeternity authentication
contract BasicAuth =
record state = { nonce : int, owner : address }
function init() = { nonce = 1, owner = Call.caller }
stateful function authorize(n : int, s : signature) : bool =
require(n >= state.nonce, "Nonce too low")
require(n =< state.nonce, "Nonce too high")
put(state{ nonce = n + 1 })
switch(Auth.tx_hash)
None => abort("Not in Auth context")
Some(tx_hash) => Crypto.ecverify(to_sign(tx_hash, n), state.owner, s)
function to_sign(h : hash, n : int) =
Crypto.blake2b((h, n))
private function require(b : bool, err : string) =
if(!b) abort(err)
+18
View File
@@ -0,0 +1,18 @@
contract BitcoinAuth =
record state = { nonce : int, owner : bytes(64) }
function init(owner' : bytes(64)) = { nonce = 1, owner = owner' }
stateful function authorize(n : int, s : signature) : bool =
require(n >= state.nonce, "Nonce too low")
require(n =< state.nonce, "Nonce too high")
put(state{ nonce = n + 1 })
switch(Auth.tx_hash)
None => abort("Not in Auth context")
Some(tx_hash) => Crypto.ecverify_secp256k1(to_sign(tx_hash, n), state.owner, s)
function to_sign(h : hash, n : int) : hash =
Crypto.blake2b((h, n))
private function require(b : bool, err : string) =
if(!b) abort(err)
+18
View File
@@ -0,0 +1,18 @@
contract BytesEquality =
function eq16(a : bytes(16), b) = a == b
function ne16(a : bytes(16), b) = a != b
function eq32(a : bytes(32), b) = a == b
function ne32(a : bytes(32), b) = a != b
function eq47(a : bytes(47), b) = a == b
function ne47(a : bytes(47), b) = a != b
function eq64(a : bytes(64), b) = a == b
function ne64(a : bytes(64), b) = a != b
function eq65(a : bytes(65), b) = a == b
function ne65(a : bytes(65), b) = a != b
+1 -1
View File
@@ -5,5 +5,5 @@ contract Counter =
function init(val) = { value = val }
function get() = state.value
function tick() = put(state{ value = state.value + 1 })
stateful function tick() = put(state{ value = state.value + 1 })
+1 -1
View File
@@ -10,7 +10,7 @@ contract DutchAuction =
sold : bool }
// Add to work around current lack of predefined functions
private function spend(to, amount) =
private stateful function spend(to, amount) =
let total = Contract.balance
Chain.spend(to, amount)
total - amount
+2 -2
View File
@@ -12,7 +12,7 @@ contract Environment =
function init(remote) = {remote = remote}
function set_remote(remote) = put({remote = remote})
stateful function set_remote(remote) = put({remote = remote})
// -- Information about the this contract ---
@@ -38,7 +38,7 @@ contract Environment =
// Value
function call_value() : int = Call.value
function nested_value(value : int) : int =
stateful function nested_value(value : int) : int =
state.remote.call_value(value = value / 2)
// Gas price
+1 -1
View File
@@ -9,7 +9,7 @@ contract Factorial =
function init(worker) = {worker = worker}
function set_worker(worker) = put(state{worker = worker})
stateful function set_worker(worker) = put(state{worker = worker})
function fac(x : int) : int =
if(x == 0) 1
+15
View File
@@ -0,0 +1,15 @@
contract Functions =
private function curry(f : ('a, 'b) => 'c) =
(x) => (y) => f(x, y)
private function map(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map(f, xs)
private function map'() = map
private function plus(x, y) = x + y
function test1(xs : list(int)) = map(curry(plus)(5), xs)
function test2(xs : list(int)) = map'()(((x) => (y) => ((x, y) => x + y)(x, y))(100), xs)
function test3(xs : list(int)) =
let m(f, xs) = map(f, xs)
m((x) => x + 1, xs)
+2 -2
View File
@@ -15,7 +15,7 @@ contract FundMe =
private function require(b : bool, err : string) =
if(!b) abort(err)
private function spend(args : spend_args) =
private stateful function spend(args : spend_args) =
Chain.spend(args.recipient, args.amount)
public function init(beneficiary, deadline, goal) : state =
@@ -53,7 +53,7 @@ contract FundMe =
require(state.total >= state.goal, "Project was not funded")
spend({recipient = state.beneficiary,
amount = Contract.balance })
put(state{ beneficiary = #0 })
put(state{ beneficiary = ak_11111111111111111111111111111111273Yts })
private stateful function withdraw_contributor() =
if(state.total >= state.goal)
+12 -12
View File
@@ -17,8 +17,8 @@ contract Maps =
{ ["one"] = {x = 1, y = 2},
["two"] = {x = 3, y = 4},
["three"] = {x = 5, y = 6} }
function map_state_i() = put(state{ map_i = map_i() })
function map_state_s() = put(state{ map_s = map_s() })
stateful function map_state_i() = put(state{ map_i = map_i() })
stateful function map_state_s() = put(state{ map_s = map_s() })
// m[k]
function get_i(k, m : map(int, pt)) = m[k]
@@ -35,20 +35,20 @@ contract Maps =
// m{[k] = v}
function set_i(k, p, m : map(int, pt)) = m{ [k] = p }
function set_s(k, p, m : map(string, pt)) = m{ [k] = p }
function set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) })
function set_state_s(k, p) = put(state{ map_s = set_s(k, p, state.map_s) })
stateful function set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) })
stateful function set_state_s(k, p) = put(state{ map_s = set_s(k, p, state.map_s) })
// m{f[k].x = v}
function setx_i(k, x, m : map(int, pt)) = m{ [k].x = x }
function setx_s(k, x, m : map(string, pt)) = m{ [k].x = x }
function setx_state_i(k, x) = put(state{ map_i[k].x = x })
function setx_state_s(k, x) = put(state{ map_s[k].x = x })
stateful function setx_state_i(k, x) = put(state{ map_i[k].x = x })
stateful function setx_state_s(k, x) = put(state{ map_s[k].x = x })
// m{[k] @ x = v }
function addx_i(k, d, m : map(int, pt)) = m{ [k].x @ x = x + d }
function addx_s(k, d, m : map(string, pt)) = m{ [k].x @ x = x + d }
function addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d })
function addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d })
stateful function addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d })
stateful function addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d })
// m{[k = def] @ x = v }
function addx_def_i(k, v, d, m : map(int, pt)) = m{ [k = v].x @ x = x + d }
@@ -77,8 +77,8 @@ contract Maps =
// Map.delete
function delete_i(k, m : map(int, pt)) = Map.delete(k, m)
function delete_s(k, m : map(string, pt)) = Map.delete(k, m)
function delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) })
function delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) })
stateful function delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) })
stateful function delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) })
// Map.size
function size_i(m : map(int, pt)) = Map.size(m)
@@ -95,6 +95,6 @@ contract Maps =
// Map.from_list
function fromlist_i(xs : list((int, pt))) = Map.from_list(xs)
function fromlist_s(xs : list((string, pt))) = Map.from_list(xs)
function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) })
function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) })
stateful function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) })
stateful function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) })
+13 -13
View File
@@ -9,22 +9,22 @@ contract Oracles =
type oracle_id = oracle(query_t, answer_t)
type query_id = oracle_query(query_t, answer_t)
function registerOracle(acct : address,
stateful function registerOracle(acct : address,
qfee : fee,
ttl : ttl) : oracle_id =
Oracle.register(acct, qfee, ttl)
function registerIntIntOracle(acct : address,
stateful function registerIntIntOracle(acct : address,
qfee : fee,
ttl : ttl) : oracle(int, int) =
Oracle.register(acct, qfee, ttl)
function registerStringStringOracle(acct : address,
stateful function registerStringStringOracle(acct : address,
qfee : fee,
ttl : ttl) : oracle(string, string) =
Oracle.register(acct, qfee, ttl)
function signedRegisterOracle(acct : address,
stateful function signedRegisterOracle(acct : address,
sign : signature,
qfee : fee,
ttl : ttl) : oracle_id =
@@ -33,7 +33,7 @@ contract Oracles =
function queryFee(o : oracle_id) : fee =
Oracle.query_fee(o)
function createQuery(o : oracle_id,
stateful function createQuery(o : oracle_id,
q : query_t,
qfee : fee,
qttl : ttl,
@@ -42,7 +42,7 @@ contract Oracles =
Oracle.query(o, q, qfee, qttl, rttl)
// Do not use in production!
function unsafeCreateQuery(o : oracle_id,
stateful function unsafeCreateQuery(o : oracle_id,
q : query_t,
qfee : fee,
qttl : ttl,
@@ -50,7 +50,7 @@ contract Oracles =
Oracle.query(o, q, qfee, qttl, rttl)
// Do not use in production!
function unsafeCreateQueryThenErr(o : oracle_id,
stateful function unsafeCreateQueryThenErr(o : oracle_id,
q : query_t,
qfee : fee,
qttl : ttl,
@@ -59,21 +59,21 @@ contract Oracles =
require(qfee >= 100000000000000000, "causing a late error")
res
function extendOracle(o : oracle_id,
stateful function extendOracle(o : oracle_id,
ttl : ttl) : () =
Oracle.extend(o, ttl)
function signedExtendOracle(o : oracle_id,
stateful function signedExtendOracle(o : oracle_id,
sign : signature, // Signed oracle address
ttl : ttl) : () =
Oracle.extend(o, signature = sign, ttl)
function respond(o : oracle_id,
stateful function respond(o : oracle_id,
q : query_id,
r : answer_t) : () =
Oracle.respond(o, q, r)
function signedRespond(o : oracle_id,
stateful function signedRespond(o : oracle_id,
q : query_id,
sign : signature,
r : answer_t) : () =
@@ -96,13 +96,13 @@ contract Oracles =
datatype complexQuestion = Why(int) | How(string)
datatype complexAnswer = NoAnswer | Answer(complexQuestion, string, int)
function complexOracle(question) =
stateful function complexOracle(question) =
let o = Oracle.register(Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
Oracle.respond(o, q, Answer(question, "magic", 1337))
Oracle.get_answer(o, q)
function signedComplexOracle(question, sig) =
stateful function signedComplexOracle(question, sig) =
let o = Oracle.register(signature = sig, Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
Oracle.respond(o, q, Answer(question, "magic", 1337), signature = sig)
+1 -1
View File
@@ -11,7 +11,7 @@ contract Remote3 =
contract RemoteCall =
function call(r : Remote1, x : int) : int =
stateful function call(r : Remote1, x : int) : int =
r.main(gas = 10000, value = 10, x)
function staged_call(r1 : Remote1, r2 : Remote2, x : int) =
-99
View File
@@ -1,99 +0,0 @@
contract Oracles =
function registerOracle :
(address,
int,
Chain.ttl) => oracle(string, int)
function createQuery :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
function unsafeCreateQuery :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
function respond :
(oracle(string, int),
oracle_query(string, int),
int) => ()
contract OraclesErr =
function unsafeCreateQueryThenErr :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
contract RemoteOracles =
public function callRegisterOracle(
r : Oracles,
acct : address,
qfee : int,
ttl : Chain.ttl) : oracle(string, int) =
r.registerOracle(acct, qfee, ttl)
public function callCreateQuery(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
require(value =< Call.value, "insufficient value")
r.createQuery(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQuery(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
r.unsafeCreateQuery(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQueryThenErr(
r : OraclesErr,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
r.unsafeCreateQueryThenErr(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQueryAndThenErr(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
let x = r.unsafeCreateQuery(value = value, o, q, qfee, qttl, rttl)
switch(0) 1 => ()
x // Never reached.
public function callRespond(
r : Oracles,
o : oracle(string, int),
q : oracle_query(string, int),
qr : int) =
r.respond(o, q, qr)
private function require(b : bool, err : string) =
if(!b) abort(err)
+1 -1
View File
@@ -24,5 +24,5 @@ contract SimpleStorage =
function get() : int = state.data
function set(value : int) =
stateful function set(value : int) =
put(state{data = value})
+4 -4
View File
@@ -4,19 +4,19 @@ contract SpendContract =
contract SpendTest =
function spend(to, amount) =
stateful function spend(to, amount) =
let total = Contract.balance
Chain.spend(to, amount)
total - amount
function withdraw(amount) : int =
stateful function withdraw(amount) : int =
spend(Call.caller, amount)
function withdraw_from(account, amount) =
stateful function withdraw_from(account, amount) =
account.withdraw(amount)
withdraw(amount)
function spend_from(from, to, amount) =
stateful function spend_from(from, to, amount) =
from.withdraw(amount)
Chain.spend(to, amount)
Chain.balance(to)
+9 -9
View File
@@ -27,13 +27,13 @@ contract StateHandling =
function read_s() = state.s
function read_m() = state.m
function update(new_state : state) = put(new_state)
function update_i(new_i) = put(state{ i = new_i })
function update_s(new_s) = put(state{ s = new_s })
function update_m(new_m) = put(state{ m = new_m })
stateful function update(new_state : state) = put(new_state)
stateful function update_i(new_i) = put(state{ i = new_i })
stateful function update_s(new_s) = put(state{ s = new_s })
stateful function update_m(new_m) = put(state{ m = new_m })
function pass_it(r : Remote) = r.look_at(state)
function nop(r : Remote) = put(state{ i = state.i })
stateful function nop(r : Remote) = put(state{ i = state.i })
function return_it_s(r : Remote, big : bool) =
let x = r.return_s(big)
String.length(x)
@@ -50,10 +50,10 @@ contract StateHandling =
function pass_update_s(r : Remote, s) = r.fun_update_s(state, s)
function pass_update_m(r : Remote, m) = r.fun_update_m(state, m)
function remote_update_i (r : Remote, i) = put(r.fun_update_i(state, i))
function remote_update_s (r : Remote, s) = put(r.fun_update_s(state, s))
function remote_update_m (r : Remote, m) = put(r.fun_update_m(state, m))
function remote_update_mk(r : Remote, k, v) = put(r.fun_update_mk(state, k, v))
stateful function remote_update_i (r : Remote, i) = put(r.fun_update_i(state, i))
stateful function remote_update_s (r : Remote, s) = put(r.fun_update_s(state, s))
stateful function remote_update_m (r : Remote, m) = put(r.fun_update_m(state, m))
stateful function remote_update_mk(r : Remote, k, v) = put(r.fun_update_mk(state, k, v))
// remote called
function look_at(s : state) = ()
+54
View File
@@ -0,0 +1,54 @@
contract Remote =
stateful function remote_spend : (address, int) => ()
function remote_pure : int => int
contract Stateful =
private function pure(x) = x + 1
private stateful function local_spend(a) =
Chain.spend(a, 1000)
// Non-stateful functions cannot mention stateful functions
function fail1(a : address) = Chain.spend(a, 1000)
function fail2(a : address) = local_spend(a)
function fail3(a : address) =
let foo = Chain.spend
foo(a, 1000)
// Private functions must also be annotated
private function fail4(a) = Chain.spend(a, 1000)
// If annotated, stateful functions are allowed
stateful function ok1(a : address) = Chain.spend(a, 1000)
// And pure functions are always allowed
stateful function ok2(a : address) = pure(5)
stateful function ok3(a : address) =
let foo = pure
foo(5)
// No error here (fail4 is annotated as not stateful)
function ok4(a : address) = fail4(a)
// Lamdbas are checked at the construction site
private function fail5() : address => () = (a) => Chain.spend(a, 1000)
// .. so you can pass a stateful lambda to a non-stateful higher-order
// function:
private function apply(f : 'a => 'b, x) = f(x)
stateful function ok5(a : address) =
apply((val) => Chain.spend(a, val), 1000)
// It doesn't matter if remote calls are stateful or not
function ok6(r : Remote) = r.remote_spend(Contract.address, 1000)
function ok7(r : Remote) = r.remote_pure(5)
// But you can't send any tokens if not stateful
function fail6(r : Remote) = r.remote_spend(value = 1000, Contract.address, 1000)
function fail7(r : Remote) = r.remote_pure(value = 1000, 5)
function fail8(r : Remote) =
let foo = r.remote_pure
foo(value = 1000, 5)
function ok8(r : Remote) = r.remote_spend(Contract.address, 1000, value = 0)
+2
View File
@@ -40,3 +40,5 @@ contract Test =
function type_error(r, x) =
set_x(set_x(x, r), x)
function repeated_arg(x : int, y, x : string, y : bool) : string = x
+3 -3
View File
@@ -11,11 +11,11 @@ contract VariantTypes =
function require(b) = if(!b) abort("required")
function start(bal : int) =
stateful function start(bal : int) =
switch(state)
Stopped => put(Started({owner = Call.caller, balance = bal, color = Grey(0)}))
function stop() =
stateful function stop() =
switch(state)
Started(st) =>
require(Call.caller == st.owner)
@@ -23,7 +23,7 @@ contract VariantTypes =
st.balance
function get_color() = switch(state) Started(st) => st.color
function set_color(c) = switch(state) Started(st) => put(Started(st{color = c}))
stateful function set_color(c) = switch(state) Started(st) => put(Started(st{color = c}))
function get_state() = state