Compare commits

..

48 Commits

Author SHA1 Message Date
Gaith Hallak 21cc6f2b3e Add constrained_t to fold 2022-06-20 10:28:17 +04:00
Gaith Hallak e8da0a7cfe Update docs/sophia_features.md
Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>
2022-06-19 21:44:47 +04:00
Gaith Hallak 4562a7166c Fix the tests after changing address to ord 2022-06-19 21:07:41 +04:00
Gaith Hallak fc2875965e Make address comparable by inequality 2022-06-19 20:54:58 +04:00
Gaith Hallak 9d296f04cb Formatting fix 2022-06-19 19:19:15 +04:00
Gaith Hallak ea98fc97bb Add an example of address type to the docs 2022-06-19 19:18:10 +04:00
Gaith Hallak 46ac9bfa82 Update the docs 2022-06-14 22:19:35 +04:00
Gaith Hallak 75f2711148 Update CHANGELOG 2022-06-14 19:04:20 +04:00
Gaith Hallak 8e6c6d81ad Add char type to the docs 2022-06-14 18:29:27 +04:00
Gaith Hallak 2d17ce3ee2 Add test for unknown tvar constraints 2022-06-14 18:29:27 +04:00
Gaith Hallak fc08fe09a5 Add tests for calling constrained functions 2022-06-14 18:29:27 +04:00
Gaith Hallak 6c17e57a7c Fix dialyzer warnings 2022-06-14 18:29:27 +04:00
Gaith Hallak 69713036d0 Add constraints to typechecker, fix old tests, add new ones 2022-06-14 18:29:26 +04:00
Gaith Hallak f56eeb0b2b Use id() for constraints instead of keywords 2022-06-14 18:26:22 +04:00
Gaith Hallak 3f177d363f Add constraints to the syntax and parser 2022-06-14 18:25:21 +04:00
Gaith Hallak b599d581ee Fix warnings reporting and stdlib warnings (#367)
* Fix stdlib warnings

* Mark unused includes when used from non-included files

* Do not mark indirectly included files as unused

* Show unused include warning only for files that are never used

* Remove unused include from Option.aes

* Consider functions passed as args as used

* Return warnings as a sorted list

* Fix failing tests

* Fix dialyzer warning

* Fix warning in Func.aes
2022-06-14 12:22:32 +04:00
Gaith Hallak b3767071a8 Allow binary operators to be used as lambdas (#385)
* Add operator lambdas

* Do not register anonymous functions as called functions

* Add tests

* Update CHANGELOG

* Update the docs

* Do not allow (..) to be used as a lambda

* Rename the function sum to any
2022-06-03 13:12:23 +04:00
Gaith Hallak b0e6418161 Ban empty record definitions (#384)
* Ban empty record declarations

* Use definition instead of declaration

* Fix the failing test
2022-05-25 17:59:46 +04:00
Gaith Hallak a894876f56 Show the file name in the location if the file is included (#383) 2022-05-10 18:27:06 +04:00
Radosław Rowicki 0af45dfd19 Deprecate AEVM (#375)
* Deprecate AEVM

* Fix test, changelog

* Restore old rebar

* rebar lock fix

* undo export

Co-authored-by: Gaith Hallak <gaithhallak@gmail.com>

* undo export

Co-authored-by: Gaith Hallak <gaithhallak@gmail.com>

* Solve GH suggestions

* Fix the docs

* update docs

* Remove unused tests

* undo weird change

Co-authored-by: Gaith Hallak <gaithhallak@gmail.com>
2022-05-10 15:33:59 +02:00
Gaith Hallak c5bfcd3bdc Add MCL_BLS12_381 types to from_fate_builtin (#382)
* Add MCL_BLS12_381 types to from_fate_builtin

* Add tests for mcl_bls12_381 types to sophia value
2022-05-05 13:19:43 +04:00
Dincho Todorov 85879f5380 Fix BLS12_381.fp and BLS12_381.fr size in the docs (#377) 2022-04-27 17:10:56 +03:00
Dincho Todorov 8897cc6cbd Fix bls12_381 anchor in the stdlib docs (#376) 2022-04-26 20:10:11 +03:00
Marco Walz 0ec7fdc6ac Merge pull request #368 from aeternity/docs/improve-stdlib-structure
docs: order namespaces alphabetically and place Set in includables
2022-04-12 12:49:40 +02:00
Gaith Hallak 74aff5401b Introduce pipe operator |> (#371)
* Add pipe operator

* Add tests

* Update docs and CHANGELOG
2022-04-12 12:40:32 +03:00
Marco Walz cfcf0a8a81 Merge pull request #372 from aeternity/pygments-version
chore(deps): bump pygments version
2022-03-28 12:09:20 +02:00
marc0olo ca31db7cad chore(deps): bump mkdocs version from 1.2.3 to 1.2.4 2022-03-28 10:43:37 +02:00
marc0olo 196460a607 chore(deps): bump pygments version 2022-03-22 13:19:11 +01:00
marc0olo bf04362f9a docs: order namespaces alphabetically and place Set in includables 2022-02-04 12:41:08 +01:00
Hans Svensson d4ea7d5d3b Clarify signature format for ecverify/ecrecover (#365) 2022-01-11 14:02:40 +01:00
Hans Svensson c1c3c29393 Add oneline error pretty-printing (#364) 2022-01-11 14:02:05 +01:00
Gaith Hallak b474bb22cd Implement caching for compiled child contracts (#363) 2022-01-11 16:50:59 +04:00
Hans Svensson c04f66a00a Merge pull request #362 from aeternity/ghallak/354
Simplify error messages reported by the compiler
2022-01-11 11:48:11 +04:00
Gaith Hallak 37d86ad45b Simplify error messages reported by the compiler
Add raw error message for 2 errors

The errors: `unnamed_map_update_with_default` and `unbound_variable`.

Revert "Add raw error message for 2 errors"

This reverts commit 0db6d16140.

Remove trailing new lines and at POS from error messages

Convert multiple line error messages into single line error messages

Remove at POS from pp_why_record context

Change error message with new line before code

Fix tests for changed error messages

Fix the rest of the error messages

Add new line after error message

Remove new line from the end of data error messages
2022-01-11 11:48:08 +04:00
Gaith Hallak 60f3a484e6 Solve constraints together and in the order they are added (#360)
* Solve named argument constraints when record type dereferencing fails

* Revert "Solve named argument constraints when record type dereferencing fails"

This reverts commit ca38a171a9eefdddbc3f6a41f8a268c42662cd7a.

* Solve constraints together and in order

* Fix dialyzer warnings

* Add comment on solve_known_record_types

* Remove unused function
2021-12-16 13:54:06 +02:00
Hans Svensson 40c78c1707 Merge pull request #361 from aeternity/clarify_protected_calls
Clarify documentation on protected calls
2021-12-10 14:52:11 +01:00
Hans Svensson cf08aeee04 Clarify documentation on protected calls 2021-12-10 14:46:44 +01:00
Marco Walz a04dd6c86d Merge pull request #359 from marc0olo/feature/syntax-highlighting
feat: activate Sophia syntax highlighting by using specific pygments …
2021-12-03 17:26:14 +01:00
marc0olo f488b35f2e chore: make sure python libs are updated on install 2021-12-01 10:06:07 +01:00
marc0olo cc1de9baba feat: activate Sophia syntax highlighting by using specific pygments version 2021-12-01 08:37:49 +01:00
Gaith Hallak fe5f5545d3 Add compiler warnings (#346)
* Add compiler warnings

Add include_type annotation to position

Add warning for unused includes

Add warning for unused stateful annotation

Add warning for unused functions

Add warning for shadowed variables

Add division by zero warning

Add warning for negative spends

Add warning for unused variables

Add warning for unused parameters

Change the ets table type to set for unused vars

Add warning for unused type defs

Move unused variables warning to the top level

Temporarily disable unused functions warnings

Add all kinds of warnings to a single ets table

Enable warnings separately through options

Use when_option instead of enabled_warnings

Turn warnings into type errors with warn_error option

Enable warning package warn_all

Re-enable unused functions warnings

Report warnings as type errors in a separate function

Make unused_function a recognized warning

Report warnings as a result of compilation

Fix tests and error for unknown warnings options

Fix dialyzer warnings

Do not show warning for variables called "_"

Move warnings handling into a separate module

Do not show warning for unused public functions in namespaces

Add src file name to unused include warning

Mark public functions in namespaces as used

Add tests for added warnings

Add warning for unused return value

Add test for turning warnings into type errors

* Update CHANGELOG
2021-11-24 11:46:21 +02:00
Dincho Todorov 98a4049f03 Use OTP 21 for builds (#332) 2021-11-11 23:08:31 +02:00
seanhinde 3dce0e627b Merge pull request #353 from aeternity/otp-24-deps
Update aebytecode dep for otp-24
2021-11-11 10:51:40 +01:00
Sean Hinde 6b46fc268b Use older rebar3 for upgrade 2021-11-10 14:32:29 +01:00
Sean Hinde 30bedad164 Use older rebar3 for upgrade 2021-11-10 14:25:33 +01:00
Sean Hinde 4d6938c741 Update aebytecode dep for otp-24 2021-11-10 14:21:23 +01:00
Hans Svensson 10fc88a21d Merge pull request #349 from aeternity/fix_oracle_expiry_doc
Fix docs Oracle.expire -> Oracle.expiry
2021-10-21 14:16:32 +02:00
Hans Svensson 3218a2c172 Fix docs Oracle.expire -> Oracle.expiry 2021-10-21 14:08:03 +02:00
83 changed files with 3332 additions and 6403 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ version: 2.1
executors:
aebuilder:
docker:
- image: aeternity/builder
- image: aeternity/builder:xenial-otp21
user: builder
working_directory: ~/aesophia
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
with:
path: ~/.cache/pip3
key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/requirements.txt') }}
- run: pip3 install -r .github/workflows/requirements.txt
- run: pip3 install -r .github/workflows/requirements.txt -U
- run: git config --global user.email "github-action@users.noreply.github.com"
- run: git config --global user.name "GitHub Action"
- run: |
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
with:
path: ~/.cache/pip3
key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/requirements.txt') }}
- run: pip3 install -r .github/workflows/requirements.txt
- run: pip3 install -r .github/workflows/requirements.txt -U
- run: git config --global user.email "github-action@users.noreply.github.com"
- run: git config --global user.name "GitHub Action"
- run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV
+3 -2
View File
@@ -1,4 +1,5 @@
mkdocs==1.2.3
mkdocs==1.2.4
mkdocs-simple-hooks==0.1.3
mkdocs-material==7.1.9
mike==1.0.1
mike==1.0.1
pygments==2.11.2
+21
View File
@@ -6,8 +6,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Compiler warnings for the follwing: shadowing, negative spends, division by zero, unused functions, unused includes, unused stateful annotations, unused variables, unused parameters, unused user-defined type, dead return value.
- The pipe operator |>
```
[1, 2, 3] |> List.first |> Option.is_some // Option.is_some(List.first([1, 2, 3]))
```
- Allow binary operators to be used as lambdas
```
function sum(l : list(int)) : int = foldl((+), 0, l)
function logical_and(x, y) = (&&)(x, y)
```
- Add comparable typevar constraints (`ord` and `eq`)
```
lt : 'a is ord ; ('a, 'a) => bool
lt(x, y) = x < y
is_eq : 'a is eq ; ('a, 'a) => bool
is_eq(x, y) = x == y
```
### Changed
- Error messages have been restructured (less newlines) to provide more unified errors. Also `pp_oneline/1` has been added.
- Ban empty record definitions (e.g. `record r = {}` would give an error).
### Removed
- Support for AEVM has been entirely wiped
## [6.1.0] - 2021-10-20
### Added
-13
View File
@@ -49,12 +49,8 @@ The **pp_** options all print to standard output the following:
`pp_typed_ast` - print the AST with type information at each node
`pp_icode` - print the internal code structure
`pp_assembler` - print the generated assembler code
`pp_bytecode` - print the bytecode instructions
#### check_call(ContractString, Options) -> CheckRet
Types
@@ -66,15 +62,6 @@ Type = term()
```
Check a call in contract through the `__call` function.
#### sophia_type_to_typerep(String) -> TypeRep
Types
``` erlang
{ok,TypeRep} | {error, badtype}
```
Get the type representation of a type declaration.
#### version() -> {ok, Version} | {error, term()}
Types
+84 -36
View File
@@ -99,6 +99,9 @@ running out of gas it is necessary to set a gas limit using the `gas` argument.
However, note that errors that would normally consume all the gas in the
transaction still only uses up the gas spent running the contract.
Any side effects (state change, token transfers, etc.) made by a failing
protected call is rolled back, just like they would be in the unprotected case.
### Contract factories and child contracts
@@ -222,9 +225,6 @@ payable stateful entrypoint buy(to : address) =
abort("Value too low")
```
Note: In the æternity VM (AEVM) contracts and entrypoints were by default
payable until the Lima release.
## Namespaces
Code can be split into libraries using the `namespace` construct. Namespaces
@@ -354,28 +354,29 @@ namespace C =
## Types
Sophia has the following types:
| Type | Description | Example |
|----------------------|---------------------------------------------------------------------------------------------|--------------------------------------------------------------|
| int | A 2-complement integer | ```-1``` |
| address | æternity address, 32 bytes | ```Call.origin``` |
| bool | A Boolean | ```true``` |
| bits | A bit field | ```Bits.none``` |
| bytes(n) | A byte array with `n` bytes | ```#fedcba9876543210``` |
| string | An array of bytes | ```"Foo"``` |
| list | A homogeneous immutable singly linked list. | ```[1, 2, 3]``` |
| ('a, 'b) => 'c | A function. Parentheses can be skipped if there is only one argument | ```(x : int, y : int) => x + y``` |
| tuple | An ordered heterogeneous array | ```(42, "Foo", true)``` |
| record | An immutable key value store with fixed key names and typed values | ``` record balance = { owner: address, value: int } ``` |
| map | An immutable key value store with dynamic mapping of keys of one type to values of one type | ```type accounts = map(string, address)``` |
| option('a) | An optional value either None or Some('a) | ```Some(42)``` |
| state | A user defined type holding the contract state | ```record state = { owner: address, magic_key: bytes(4) }``` |
| event | An append only list of blockchain events (or log entries) | ```datatype event = EventX(indexed int, string)``` |
| hash | A 32-byte hash - equivalent to `bytes(32)` | |
| signature | A signature - equivalent to `bytes(64)` | |
| Chain.ttl | Time-to-live (fixed height or relative to current block) | ```FixedTTL(1050)``` ```RelativeTTL(50)``` |
| oracle('a, 'b) | And oracle answering questions of type 'a with answers of type 'b | ```Oracle.register(acct, qfee, ttl)``` |
| oracle_query('a, 'b) | A specific oracle query | ```Oracle.query(o, q, qfee, qttl, rttl)``` |
| contract | A user defined, typed, contract address | ```function call_remote(r : RemoteContract) = r.fun()``` |
| Type | Description | Example |
|----------------------|---------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------|
| int | A 2-complement integer | ```-1``` |
| char | A single character | ```'c'``` |
| address | æternity address, 32 bytes | ```Call.origin``` ```ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt``` |
| bool | A Boolean | ```true``` |
| bits | A bit field | ```Bits.none``` |
| bytes(n) | A byte array with `n` bytes | ```#fedcba9876543210``` |
| string | An array of bytes | ```"Foo"``` |
| list | A homogeneous immutable singly linked list. | ```[1, 2, 3]``` |
| ('a, 'b) => 'c | A function. Parentheses can be skipped if there is only one argument | ```(x : int, y : int) => x + y``` |
| tuple | An ordered heterogeneous array | ```(42, "Foo", true)``` |
| record | An immutable key value store with fixed key names and typed values | ``` record balance = { owner: address, value: int } ``` |
| map | An immutable key value store with dynamic mapping of keys of one type to values of one type | ```type accounts = map(string, address)``` |
| option('a) | An optional value either None or Some('a) | ```Some(42)``` |
| state | A user defined type holding the contract state | ```record state = { owner: address, magic_key: bytes(4) }``` |
| event | An append only list of blockchain events (or log entries) | ```datatype event = EventX(indexed int, string)``` |
| hash | A 32-byte hash - equivalent to `bytes(32)` | |
| signature | A signature - equivalent to `bytes(64)` | |
| Chain.ttl | Time-to-live (fixed height or relative to current block) | ```FixedTTL(1050)``` ```RelativeTTL(50)``` |
| oracle('a, 'b) | And oracle answering questions of type 'a with answers of type 'b | ```Oracle.register(acct, qfee, ttl)``` |
| oracle_query('a, 'b) | A specific oracle query | ```Oracle.query(o, q, qfee, qttl, rttl)``` |
| contract | A user defined, typed, contract address | ```function call_remote(r : RemoteContract) = r.fun()``` |
## Literals
| Type | Constant/Literal example(s) |
@@ -402,7 +403,7 @@ Sophia has the following types:
## Arithmetic
Sophia integers (`int`) are represented by 256-bit (AEVM) or arbitrary-sized (FATE) signed words and supports the following
Sophia integers (`int`) are represented by arbitrary-sized signed words and support the following
arithmetic operations:
- addition (`x + y`)
- subtraction (`x - y`)
@@ -411,22 +412,16 @@ arithmetic operations:
- remainder (`x mod y`), satisfying `y * (x / y) + x mod y == x` for non-zero `y`
- exponentiation (`x ^ y`)
All operations are *safe* with respect to overflow and underflow. On AEVM they behave as the corresponding
operations on arbitrary-size integers and fail with `arithmetic_error` if the
result cannot be represented by a 256-bit signed word. For example, `2 ^ 255`
fails rather than wrapping around to -2²⁵⁵.
The division and modulo operations also throw an arithmetic error if the
second argument is zero.
All operations are *safe* with respect to overflow and underflow.
The division and modulo operations throw an arithmetic error if the
right-hand operand is zero.
## Bit fields
Sophia integers do not support bit arithmetic. Instead there is a separate
type `bits`. See the standard library [documentation](sophia_stdlib.md#bits).
On the AEVM a bit field is represented by a 256-bit word and reading or writing
a bit outside the 0..255 range fails with an `arithmetic_error`. On FATE a bit
field can be of arbitrary size (but it is still represented by the
A bit field can be of arbitrary size (but it is still represented by the
corresponding integer, so setting very high bits can be expensive).
## Type aliases
@@ -507,6 +502,59 @@ function
Guards cannot be stateful even when used inside a stateful function.
## Comparable types
Only certain types are allowed to be compared by equality (`==`, `!=`) and
inequality (`<`, `>`, `=<`, `>=`). For instance, while it is legal to compare
integers, comparing functions would lead to an error:
```
function f() =
f == f // type error
```
The rules apply as follows:
- All types that are comparable by inequality are also comparable by equality.
- The builtin types `bool`, `int`, `char`, `bits`, `bytes`, `string`, `unit`,
`hash`, `address` and `signature` are comparable by inequality (and thus by
equality).
- The composite types `list`, `option`, and tuples are comparable by
equality/inequality if their type parameters are comparable by
equality/inequality.
- The composite types `map`, `oracle`, and `oracle_query` are comparable by
equality if their type parameters are comparable by equality.
- User-defined records and datatypes are comparable by equality if their type
parameters are comparable by equality.
- Smart contracts are comparable by equality.
- User-declared type variables are comparable according to the [type
constraints](#type-constraints) given in the function signature.
In all other cases the types are not comparable.
### Type constraints
Polymorphic types are not declared as comparable by default. If the user
specifies the type signature for a function, they need to manually declare type
constraints in order to allow the variables to be compared. This can only be
done if the type declaration is separated from the function definition. The
constraints have to be prepended to the type declaration and separated with a
semicolon:
```
function eq(x : 'a, y : 'a) = x == y // Type error, 'a is not comparable
function
eq : 'a is eq ; ('a, 'a) => bool
eq(x, y) = x == y // Compiles
function eq(x, y) = x == y // Compiles as the constraints are inferred
```
Currently only two constraints are allowed: `eq` for equality and `ord` for
inequality. Declaring a type as `ord` automatically implies `eq`.
## Lists
A Sophia list is a dynamically sized, homogenous, immutable, singly
+1282 -1271
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -200,6 +200,7 @@ switch(f(x))
```c
Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + 1
| '(' BinOp ')' // Operator lambda (+)
| 'if' '(' Expr ')' Expr 'else' Expr // If expression if(x < y) y else x
| Expr ':' Type // Type annotation 5 : int
| Expr BinOp Expr // Binary operator x + y
@@ -234,6 +235,7 @@ Path ::= Id // Record field
BinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!='
| '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^'
| '|>'
UnOp ::= '-' | '!'
```
@@ -245,6 +247,7 @@ UnOp ::= '-' | '!'
| `!` `&&` `\|\|` | logical operators
| `==` `!=` `<` `>` `=<` `>=` | comparison operators
| `::` `++` | list operators
| `\|>` | functional operators
## Operator precendences
@@ -261,3 +264,4 @@ In order of highest to lowest precedence.
| `<` `>` `=<` `>=` `==` `!=` | none
| `&&` | right
| `\|\|` | right
| `\|>` | left
+4 -4
View File
@@ -7,13 +7,13 @@ namespace BLS12_381 =
record gt = { x1 : fp, x2 : fp, x3 : fp, x4 : fp, x5 : fp, x6 : fp,
x7 : fp, x8 : fp, x9 : fp, x10 : fp, x11 : fp, x12 : fp }
function pairing_check(xs : list(g1), ys : list(g2)) =
switch((xs, ys))
function pairing_check(us : list(g1), vs : list(g2)) =
switch((us, vs))
([], []) => true
(x :: xs, y :: ys) => pairing_check_(pairing(x, y), xs, ys)
function pairing_check_(acc : gt, xs : list(g1), ys : list(g2)) =
switch((xs, ys))
function pairing_check_(acc : gt, us : list(g1), vs : list(g2)) =
switch((us, vs))
([], []) => gt_is_one(acc)
(x :: xs, y :: ys) =>
pairing_check_(gt_mul(acc, pairing(x, y)), xs, ys)
-10
View File
@@ -116,16 +116,6 @@ namespace Bitwise =
1 => ubxor__(a / 2, b / 2, val * 2, acc + val)
_ => ubxor__(a / 2, b / 2, val * 2, acc)
// Bitwise combined 'and' and 'not' of second argument for positive integers
// x 'bnand' y = x 'band' ('bnot' y)
// The tricky bit is that after negation the second argument has an infinite number of 1's
// use as many as needed!
//
// NOTE: this function is not symmetric!
private function ubnand(a, b) =
require(a >= 0 && b >= 0, "ubxor is only defined for non-negative integers")
ubnand__(a, b, 1, 0)
private function ubnand_(a, b) = ubnand__(a, b, 1, 0)
private function
+1 -1
View File
@@ -2,7 +2,7 @@ namespace Func =
function id(x : 'a) : 'a = x
function const(x : 'a) : 'b => 'a = (y) => x
function const(x : 'a) : 'b => 'a = (_) => x
function flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c = (b, a) => f(a, b)
+8 -6
View File
@@ -29,9 +29,11 @@ namespace List =
[] => abort("drop_last_unsafe: list empty")
function contains(e : 'a, l : list('a)) = switch(l)
[] => false
h::t => h == e || contains(e, t)
function
contains : 'a is eq; ('a, list('a)) => bool
contains(e, l) = switch(l)
[] => false
h::t => h == e || contains(e, t)
/** Finds first element of `l` fulfilling predicate `p` as `Some` or `None`
* if no such element exists.
@@ -173,7 +175,7 @@ namespace List =
if (n == 0) l
else switch(l)
[] => []
h::t => drop_(n-1, t)
_::t => drop_(n-1, t)
/** Get the longest prefix of a list in which every element
* matches predicate `p`
@@ -191,7 +193,7 @@ namespace List =
/** Splits list into two lists of elements that respectively
* match and don't match predicate `p`
*/
function partition(p : 'a => bool, l : list('a)) : (list('a) * list('a)) = switch(l)
function partition(p : 'a => bool, lst : list('a)) : (list('a) * list('a)) = switch(lst)
[] => ([], [])
h::t =>
let (l, r) = partition(p, t)
@@ -313,4 +315,4 @@ namespace List =
function enumerate(l : list('a)) : list(int * 'a) = enumerate_(l, 0)
private function enumerate_(l : list('a), n : int) : list(int * 'a) = switch(l)
[] => []
h::t => (n, h)::enumerate_(t, n + 1)
h::t => (n, h)::enumerate_(t, n + 1)
+2 -2
View File
@@ -2,8 +2,8 @@ namespace ListInternal =
// -- Flatmap ----------------------------------------------------------------
function flat_map(f : 'a => list('b), xs : list('a)) : list('b) =
switch(xs)
function flat_map(f : 'a => list('b), lst : list('a)) : list('b) =
switch(lst)
[] => []
x :: xs => f(x) ++ flat_map(f, xs)
+3 -3
View File
@@ -1,5 +1,3 @@
include "List.aes"
namespace Option =
function is_none(o : option('a)) : bool = switch(o)
@@ -32,7 +30,9 @@ namespace Option =
None => abort(err)
Some(x) => x
function contains(e : 'a, o : option('a)) = o == Some(e)
function
contains : 'a is eq; ('a, option('a)) => bool
contains(e, o) = o == Some(e)
function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o)
+18 -17
View File
@@ -53,21 +53,21 @@ namespace String =
// Converts a decimal ("123", "-253") or a hexadecimal ("0xa2f", "-0xBBB") string
// into an integer. If the string doesn't contain a valid number `None` is returned.
function to_int(s : string) : option(int) =
let s = StringInternal.to_list(s)
switch(is_prefix(['-'], s))
None => to_int_pos(s)
function to_int(str : string) : option(int) =
let lst = StringInternal.to_list(str)
switch(is_prefix(['-'], lst))
None => to_int_pos(lst)
Some(s) => switch(to_int_pos(s))
None => None
Some(x) => Some(-x)
// Private helper functions below
private function to_int_pos(s : list(char)) =
switch(is_prefix(['0', 'x'], s))
private function to_int_pos(chs : list(char)) =
switch(is_prefix(['0', 'x'], chs))
None =>
to_int_(s, ch_to_int_10, 0, 10)
Some(s) =>
to_int_(s, ch_to_int_16, 0, 16)
to_int_(chs, ch_to_int_10, 0, 10)
Some(str) =>
to_int_(str, ch_to_int_16, 0, 16)
private function
tokens_(_, [], acc) = [StringInternal.from_list(List.reverse(acc))]
@@ -84,12 +84,13 @@ namespace String =
contains_(ix, str, substr) =
switch(is_prefix(substr, str))
None =>
let _ :: str = str
contains_(ix + 1, str, substr)
let _ :: tailstr = str
contains_(ix + 1, tailstr, substr)
Some(_) =>
Some(ix)
private function
is_prefix : (list(char), list(char)) => option(list(char))
is_prefix([], ys) = Some(ys)
is_prefix(_, []) = None
is_prefix(x :: xs, y :: ys) =
@@ -98,18 +99,18 @@ namespace String =
private function
to_int_([], _, x, _) = Some(x)
to_int_(i :: is, value, x, b) =
to_int_(i :: ints, value, x, b) =
switch(value(i))
None => None
Some(i) => to_int_(is, value, x * b + i, b)
Some(i) => to_int_(ints, value, x * b + i, b)
private function ch_to_int_10(c) =
let c = Char.to_int(c)
private function ch_to_int_10(ch) =
let c = Char.to_int(ch)
if(c >= 48 && c =< 57) Some(c - 48)
else None
private function ch_to_int_16(c) =
let c = Char.to_int(c)
private function ch_to_int_16(ch) =
let c = Char.to_int(ch)
if(c >= 48 && c =< 57) Some(c - 48)
elif(c >= 65 && c =< 70) Some(c - 55)
elif(c >= 97 && c =< 102) Some(c - 87)
+1 -1
View File
@@ -2,7 +2,7 @@
{erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"05dfd7f"}}}
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"0699f35"}}}
, {getopt, "1.0.1"}
, {eblake2, "1.0.0"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
+1 -1
View File
@@ -1,7 +1,7 @@
{"1.1.0",
[{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git",
{ref,"05dfd7ffc7fb1e07ecc0b1e516da571f56d7dc8f"}},
{ref,"0699f35b0398bac6cd4468da654d608375bd853d"}},
0},
{<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git",
+1 -1
View File
@@ -70,7 +70,7 @@ do_contract_interface(Type, Contract, Options) when is_binary(Contract) ->
do_contract_interface(Type, ContractString, Options) ->
try
Ast = aeso_compiler:parse(ContractString, Options),
{TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
{TypedAst, _, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
from_typed_ast(Type, TypedAst)
catch
throw:{error, Errors} -> {error, Errors}
File diff suppressed because it is too large Load Diff
+8 -3
View File
@@ -363,7 +363,7 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest])
to_fcode(Env1, Rest)
end;
to_fcode(_Env, [NotMain = {NotMainHead, _ ,_ , _}]) when NotMainHead =/= contract_def ->
fcode_error({last_declaration_must_be_contract_def, NotMain});
fcode_error({last_declaration_must_be_main_contract, NotMain});
to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) ->
Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls),
to_fcode(Env1, Code).
@@ -495,6 +495,8 @@ type_to_fcode(_Env, _Sub, {tvar, Ann, "void"}) ->
fcode_error({found_void, Ann});
type_to_fcode(_Env, Sub, {tvar, _, X}) ->
maps:get(X, Sub, {tvar, X});
type_to_fcode(Env, Sub, {constrained_t, _, _, TVar = {tvar, _, _}}) ->
type_to_fcode(Env, Sub, TVar);
type_to_fcode(_Env, _Sub, {fun_t, Ann, _, var_args, _}) ->
fcode_error({var_args_not_set, {id, Ann, "a very suspicious function"}});
type_to_fcode(Env, Sub, {fun_t, _, Named, Args, Res}) ->
@@ -703,8 +705,11 @@ expr_to_fcode(Env, _Type, {block, _, Stmts}) ->
expr_to_fcode(Env, _Type, Expr = {app, _, {Op, _}, [_, _]}) when Op == '&&'; Op == '||' ->
Tree = expr_to_decision_tree(Env, Expr),
decision_tree_to_fcode(Tree);
expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A, B]}) when is_atom(Op) ->
{op, Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]};
expr_to_fcode(Env, Type, {app, Ann, {Op, _}, [A, B]}) when is_atom(Op) ->
case Op of
'|>' -> expr_to_fcode(Env, Type, {app, Ann, B, [A]});
_ -> {op, Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]}
end;
expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
case Op of
'-' -> {op, '-', [{lit, {int, 0}}, expr_to_fcode(Env, A)]};
File diff suppressed because it is too large Load Diff
-684
View File
@@ -1,684 +0,0 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc
%%% Compiler builtin functions for Aeterinty Sophia language.
%%% @end
%%% Created : 20 Dec 2018
%%%
%%%-------------------------------------------------------------------
-module(aeso_builtins).
-export([ builtin_function/1
, bytes_to_raw_string/2
, check_event_type/1
, used_builtins/1 ]).
-import(aeso_ast_to_icode, [prim_call/5]).
-include_lib("aebytecode/include/aeb_opcodes.hrl").
-include("aeso_icode.hrl").
used_builtins(#funcall{ function = #var_ref{ name = {builtin, Builtin} }, args = Args }) ->
lists:umerge(dep_closure([Builtin]), used_builtins(Args));
used_builtins([H|T]) ->
lists:umerge(used_builtins(H), used_builtins(T));
used_builtins(T) when is_tuple(T) ->
used_builtins(tuple_to_list(T));
used_builtins(M) when is_map(M) ->
used_builtins(maps:to_list(M));
used_builtins(_) -> [].
builtin_deps(Builtin) ->
lists:usort(builtin_deps1(Builtin)).
builtin_deps1({map_lookup_default, Type}) -> [{map_lookup, Type}];
builtin_deps1({map_get, Type}) -> [{map_lookup, Type}];
builtin_deps1(map_member) -> [{map_lookup, word}];
builtin_deps1({map_upd, Type}) -> [{map_get, Type}, map_put];
builtin_deps1({map_upd_default, Type}) -> [{map_lookup_default, Type}, map_put];
builtin_deps1(map_from_list) -> [map_put];
builtin_deps1(str_equal) -> [str_equal_p];
builtin_deps1(string_concat) -> [string_concat_inner1, string_copy, string_shift_copy];
builtin_deps1(int_to_str) -> [{baseX_int, 10}];
builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}];
builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}];
builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker, bytes_to_str_worker_x];
builtin_deps1(string_reverse) -> [string_reverse_];
builtin_deps1(require) -> [abort];
builtin_deps1(_) -> [].
dep_closure(Deps) ->
case lists:umerge(lists:map(fun builtin_deps/1, Deps)) of
[] -> Deps;
Deps1 -> lists:umerge(Deps, dep_closure(Deps1))
end.
%% Helper functions/macros
v(X) when is_atom(X) -> v(atom_to_list(X));
v(X) when is_list(X) -> #var_ref{name = X}.
option_none() -> {tuple, [{integer, 0}]}.
option_some(X) -> {tuple, [{integer, 1}, X]}.
-define(HASH_BYTES, 32).
-define(call(Fun, Args), #funcall{ function = #var_ref{ name = {builtin, Fun} }, args = Args }).
-define(I(X), {integer, X}).
-define(V(X), v(X)).
-define(A(Op), aeb_opcodes:mnemonic(Op)).
-define(LET(Var, Expr, Body), {switch, Expr, [{v(Var), Body}]}).
-define(DEREF(Var, Ptr, Body), {switch, operand(Ptr), [{{tuple, [v(Var)]}, Body}]}).
-define(NXT(Ptr), op('+', Ptr, 32)).
-define(NEG(A), op('/', A, {unop, '-', {integer, 1}})).
-define(BYTE(Ix, Word), op('byte', Ix, Word)).
-define(EQ(A, B), op('==', A, B)).
-define(LT(A, B), op('<', A, B)).
-define(GT(A, B), op('>', A, B)).
-define(ADD(A, B), op('+', A, B)).
-define(SUB(A, B), op('-', A, B)).
-define(MUL(A, B), op('*', A, B)).
-define(DIV(A, B), op('div', A, B)).
-define(MOD(A, B), op('mod', A, B)).
-define(EXP(A, B), op('^', A, B)).
-define(AND(A, B), op('&&', A, B)).
%% Bit shift operations takes their arguments backwards!?
-define(BSL(X, B), op('bsl', ?MUL(B, 8), X)).
-define(BSR(X, B), op('bsr', ?MUL(B, 8), X)).
op(Op, A, B) -> simpl({binop, Op, operand(A), operand(B)}).
%% We generate a lot of B * 8 for integer B from BSL and BSR.
simpl({binop, '*', {integer, A}, {integer, B}}) when A >= 0, B >= 0, A * B < 1 bsl 256 ->
{integer, A * B};
simpl(Op) -> Op.
operand(A) when is_atom(A) -> v(A);
operand(I) when is_integer(I) -> {integer, I};
operand(T) -> T.
check_event_type(Icode) ->
case maps:get(event_type, Icode) of
{variant_t, Cons} ->
check_event_type(Cons, Icode);
_ ->
error({event_should_be_variant_type})
end.
check_event_type(Evts, Icode) ->
[ check_event_type(Name, Ix, T, Icode)
|| {constr_t, Ann, {con, _, Name}, Types} <- Evts,
{Ix, T} <- lists:zip(aeso_syntax:get_ann(indices, Ann), Types) ].
check_event_type(EvtName, Ix, Type, Icode) ->
VMType =
try
aeso_ast_to_icode:ast_typerep(Type, Icode)
catch _:_ ->
error({EvtName, could_not_resolve_type, Type})
end,
case {Ix, VMType, Type} of
{indexed, word, _} -> ok;
{notindexed, string, _} -> ok;
{notindexed, _, {bytes_t, _, N}} when N > 32 -> ok;
{indexed, _, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
{notindexed, _, _} -> error({EvtName, payload_should_be_string, is, VMType})
end.
bfun(B, {IArgs, IExpr, IRet}) ->
{{builtin, B}, [private], IArgs, IExpr, IRet}.
builtin_function(BF) ->
case BF of
{event, EventT} -> bfun(BF, builtin_event(EventT));
abort -> bfun(BF, builtin_abort());
block_hash -> bfun(BF, builtin_block_hash());
require -> bfun(BF, builtin_require());
{map_lookup, Type} -> bfun(BF, builtin_map_lookup(Type));
map_put -> bfun(BF, builtin_map_put());
map_delete -> bfun(BF, builtin_map_delete());
map_size -> bfun(BF, builtin_map_size());
{map_get, Type} -> bfun(BF, builtin_map_get(Type));
{map_lookup_default, Type} -> bfun(BF, builtin_map_lookup_default(Type));
map_member -> bfun(BF, builtin_map_member());
{map_upd, Type} -> bfun(BF, builtin_map_upd(Type));
{map_upd_default, Type} -> bfun(BF, builtin_map_upd_default(Type));
map_from_list -> bfun(BF, builtin_map_from_list());
list_concat -> bfun(BF, builtin_list_concat());
string_length -> bfun(BF, builtin_string_length());
string_concat -> bfun(BF, builtin_string_concat());
string_concat_inner1 -> bfun(BF, builtin_string_concat_inner1());
string_copy -> bfun(BF, builtin_string_copy());
string_shift_copy -> bfun(BF, builtin_string_shift_copy());
str_equal_p -> bfun(BF, builtin_str_equal_p());
str_equal -> bfun(BF, builtin_str_equal());
popcount -> bfun(BF, builtin_popcount());
int_to_str -> bfun(BF, builtin_int_to_str());
addr_to_str -> bfun(BF, builtin_addr_to_str());
{baseX_int, X} -> bfun(BF, builtin_baseX_int(X));
{baseX_digits, X} -> bfun(BF, builtin_baseX_digits(X));
{baseX_tab, X} -> bfun(BF, builtin_baseX_tab(X));
{baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X));
{baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X));
{baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X));
{bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N));
{bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N));
{bytes_concat, A, B} -> bfun(BF, builtin_bytes_concat(A, B));
{bytes_split, A, B} -> bfun(BF, builtin_bytes_split(A, B));
bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker());
bytes_to_str_worker_x -> bfun(BF, builtin_bytes_to_str_worker_x());
string_reverse -> bfun(BF, builtin_string_reverse());
string_reverse_ -> bfun(BF, builtin_string_reverse_())
end.
%% Event primitive (dependent on Event type)
%%
%% We need to switch on the event and prepare the correct #event for icode_to_asm
%% NOTE: we assume all errors are already checked!
builtin_event(EventT) ->
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end,
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
Payload = %% Should put data ptr, length on stack.
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
([{{id, _, "string"}, V}]) ->
{seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]}; %% ptr+32, length
([{{bytes_t, _, N}, V}]) -> {seq, [V, {integer, N}, {inline_asm, A(?SWAP1)}]}
end,
Ix =
fun({bytes_t, _, N}, V) when N < 32 -> ?BSR(V, 32 - N);
(_, V) -> V end,
Clause =
fun(_Tag, {con, _, Con}, IxTypes) ->
Types = [ T || {_Ix, T} <- IxTypes ],
Indexed = [ Ix(Type, Var) || {Var, {indexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
Data = [ {Type, Var} || {Var, {notindexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
{ok, <<EvtIndexN:256>>} = eblake2:blake2b(?HASH_BYTES, list_to_binary(Con)),
EvtIndex = {integer, EvtIndexN},
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(Data)}
end,
Pat = fun(Tag, Types) -> {tuple, [{integer, Tag} | ArgPats(Types)]} end,
{variant_t, Cons} = EventT,
Tags = lists:seq(0, length(Cons) - 1),
{[{"e", event}],
{switch, v(e),
[{Pat(Tag, Types), Clause(Tag, Con, lists:zip(aeso_syntax:get_ann(indices, Ann), Types))}
|| {Tag, {constr_t, Ann, Con, Types}} <- lists:zip(Tags, Cons) ]},
{tuple, []}}.
%% Abort primitive.
builtin_abort() ->
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
{[{"s", string}],
{inline_asm, [A(?PUSH1),0, %% Push a dummy 0 for the first arg
A(?REVERT)]}, %% Stack: 0,Ptr
{tuple,[]}}.
builtin_block_hash() ->
{[{"height", word}],
?LET(hash, #prim_block_hash{ height = ?V(height)},
{ifte, ?EQ(hash, 0), option_none(), option_some(?V(hash))}),
aeso_icode:option_typerep(word)}.
builtin_require() ->
{[{"c", word}, {"msg", string}],
{ifte, ?V(c), {tuple, []}, ?call(abort, [?V(msg)])},
{tuple, []}}.
%% Map primitives
builtin_map_lookup(Type) ->
Ret = aeso_icode:option_typerep(Type),
{[{"m", word}, {"k", word}],
prim_call(?PRIM_CALL_MAP_GET, #integer{value = 0},
[#var_ref{name = "m"}, #var_ref{name = "k"}],
[word, word], Ret),
Ret}.
builtin_map_put() ->
%% We don't need the types for put.
{[{"m", word}, {"k", word}, {"v", word}],
prim_call(?PRIM_CALL_MAP_PUT, #integer{value = 0},
[v(m), v(k), v(v)], [word, word, word], word),
word}.
builtin_map_delete() ->
{[{"m", word}, {"k", word}],
prim_call(?PRIM_CALL_MAP_DELETE, #integer{value = 0},
[v(m), v(k)], [word, word], word),
word}.
builtin_map_size() ->
{[{"m", word}],
prim_call(?PRIM_CALL_MAP_SIZE, #integer{value = 0},
[v(m)], [word], word),
word}.
%% Map builtins
builtin_map_get(Type) ->
%% function map_get(m, k) =
%% switch(map_lookup(m, k))
%% Some(v) => v
{[{"m", word}, {"k", word}],
{switch, ?call({map_lookup, Type}, [v(m), v(k)]), [{option_some(v(v)), v(v)}]},
Type}.
builtin_map_lookup_default(Type) ->
%% function map_lookup_default(m, k, default) =
%% switch(map_lookup(m, k))
%% None => default
%% Some(v) => v
{[{"m", word}, {"k", word}, {"default", Type}],
{switch, ?call({map_lookup, Type}, [v(m), v(k)]),
[{option_none(), v(default)},
{option_some(v(v)), v(v)}]},
Type}.
builtin_map_member() ->
%% function map_member(m, k) : bool =
%% switch(Map.lookup(m, k))
%% None => false
%% _ => true
{[{"m", word}, {"k", word}],
{switch, ?call({map_lookup, word}, [v(m), v(k)]),
[{option_none(), {integer, 0}},
{{var_ref, "_"}, {integer, 1}}]},
word}.
builtin_map_upd(Type) ->
%% function map_upd(map, key, fun) =
%% map_put(map, key, fun(map_get(map, key)))
{[{"map", word}, {"key", word}, {"valfun", word}],
?call(map_put,
[v(map), v(key),
#funcall{ function = v(valfun),
args = [?call({map_get, Type}, [v(map), v(key)])] }]),
word}.
builtin_map_upd_default(Type) ->
%% function map_upd(map, key, val, fun) =
%% map_put(map, key, fun(map_lookup_default(map, key, val)))
{[{"map", word}, {"key", word}, {"val", word}, {"valfun", word}],
?call(map_put,
[v(map), v(key),
#funcall{ function = v(valfun),
args = [?call({map_lookup_default, Type}, [v(map), v(key), v(val)])] }]),
word}.
builtin_map_from_list() ->
%% function map_from_list(xs, acc) =
%% switch(xs)
%% [] => acc
%% (k, v) :: xs => map_from_list(xs, acc { [k] = v })
{[{"xs", {list, {tuple, [word, word]}}}, {"acc", word}],
{switch, v(xs),
[{{list, []}, v(acc)},
{{binop, '::', {tuple, [v(k), v(v)]}, v(ys)},
?call(map_from_list,
[v(ys), ?call(map_put, [v(acc), v(k), v(v)])])}]},
word}.
%% list_concat
%%
%% Concatenates two lists.
builtin_list_concat() ->
{[{"l1", {list, word}}, {"l2", {list, word}}],
{switch, v(l1),
[{{list, []}, v(l2)},
{{binop, '::', v(hd), v(tl)},
{binop, '::', v(hd), ?call(list_concat, [v(tl), v(l2)])}}
]
},
word}.
builtin_string_length() ->
%% function length(str) =
%% switch(str)
%% {n} -> n // (ab)use the representation
{[{"s", string}],
?DEREF(n, s, ?V(n)),
word}.
%% str_concat - concatenate two strings
%%
%% Unless the second string is the empty string, a new string is created at the
%% top of the Heap and the address to it is returned. The tricky bit is when
%% the words from the second string has to be shifted to fit next to the first
%% string.
builtin_string_concat() ->
{[{"s1", string}, {"s2", string}],
?DEREF(n1, s1,
?DEREF(n2, s2,
{ifte, ?EQ(n1, 0),
?V(s2), %% First string is empty return second string
{ifte, ?EQ(n2, 0),
?V(s1), %% Second string is empty return first string
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?ADD(n1, n2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, %% Store total len
?call(string_concat_inner1, [?V(n1), ?NXT(s1), ?V(n2), ?NXT(s2)]),
{inline_asm, [?A(?POP)]}, %% Discard fun ret val
?V(ret) %% Put the actual return value
]})}
}
)),
word}.
builtin_string_concat_inner1() ->
%% Copy all whole words from the first string, and set up for word fusion
%% Special case when the length of the first string is divisible by 32.
{[{"n1", word}, {"p1", pointer}, {"n2", word}, {"p2", pointer}],
?LET(w1, ?call(string_copy, [?V(n1), ?V(p1)]),
?LET(nx, ?MOD(n1, 32),
{ifte, ?EQ(nx, 0),
?LET(w2, ?call(string_copy, [?V(n2), ?V(p2)]),
{seq, [?V(w2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}),
?call(string_shift_copy, [?V(nx), ?V(w1), ?V(n2), ?V(p2)])
})),
word}.
builtin_string_copy() ->
{[{"n", word}, {"p", pointer}],
?DEREF(w, p,
{ifte, ?GT(n, 31),
{seq, [?V(w), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call(string_copy, [?SUB(n, 32), ?NXT(p)])]},
?V(w)
}),
word}.
builtin_string_shift_copy() ->
{[{"off", word}, {"dst", word}, {"n", word}, {"p", pointer}],
?DEREF(w, p,
{seq, [?ADD(dst, ?BSR(w, off)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
{ifte, ?GT(n, ?SUB(32, off)),
?call(string_shift_copy, [?V(off), ?BSL(w, ?SUB(32, off)), ?SUB(n, 32), ?NXT(p)]),
{inline_asm, [?A(?MSIZE)]}}]
}),
word}.
builtin_str_equal_p() ->
%% function str_equal_p(n, p1, p2) =
%% if(n =< 0) true
%% else
%% let w1 = *p1
%% let w2 = *p2
%% w1 == w2 && str_equal_p(n - 32, p1 + 32, p2 + 32)
{[{"n", word}, {"p1", pointer}, {"p2", pointer}],
{ifte, ?LT(n, 1),
?I(1),
?DEREF(w1, p1,
?DEREF(w2, p2,
?AND(?EQ(w1, w2),
?call(str_equal_p, [?SUB(n, 32), ?NXT(p1), ?NXT(p2)]))))},
word}.
builtin_str_equal() ->
%% function str_equal(s1, s2) =
%% let n1 = length(s1)
%% let n2 = length(s2)
%% n1 == n2 && str_equal_p(n1, s1 + 32, s2 + 32)
{[{"s1", string}, {"s2", string}],
?DEREF(n1, s1,
?DEREF(n2, s2,
?AND(?EQ(n1, n2), ?call(str_equal_p, [?V(n1), ?NXT(s1), ?NXT(s2)]))
)),
word}.
%% Count the number of 1s in a bit field.
builtin_popcount() ->
%% function popcount(bits, acc) =
%% if (bits == 0) acc
%% else popcount(bits bsr 1, acc + bits band 1)
{[{"bits", word}, {"acc", word}],
{ifte, ?EQ(bits, 0),
?V(acc),
?call(popcount, [op('bsr', 1, bits), ?ADD(acc, op('band', bits, 1))])
}, word}.
builtin_int_to_str() ->
{[{"i", word}], ?call({baseX_int, 10}, [?V(i)]), word}.
builtin_baseX_tab(_X = 10) ->
{[{"ix", word}], ?ADD($0, ix), word};
builtin_baseX_tab(_X = 58) ->
<<Fst32:256>> = <<"123456789ABCDEFGHJKLMNPQRSTUVWXY">>,
<<Lst26:256>> = <<"Zabcdefghijkmnopqrstuvwxyz", 0:48>>,
{[{"ix", word}],
{ifte, ?LT(ix, 32),
?BYTE(ix, Fst32),
?BYTE(?SUB(ix, 32), Lst26)
},
word}.
builtin_baseX_int(X) ->
{[{"w", word}],
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?call({baseX_int_pad, X}, [?V(w), ?I(0), ?I(0)]), {inline_asm, [?A(?POP)]}, ?V(ret)]}),
word}.
builtin_baseX_int_pad(X = 10) ->
{[{"src", word}, {"ix", word}, {"dst", word}],
{ifte, ?LT(src, 0),
?call({baseX_int_encode, X}, [?NEG(src), ?I(1), ?BSL($-, 31)]),
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)])},
word};
builtin_baseX_int_pad(X = 16) ->
{[{"src", word}, {"ix", word}, {"dst", word}],
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
word};
builtin_baseX_int_pad(X = 58) ->
{[{"src", word}, {"ix", word}, {"dst", word}],
{ifte, ?GT(?ADD(?DIV(ix, 31), ?BYTE(ix, src)), 0),
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
?call({baseX_int_pad, X}, [?V(src), ?ADD(ix, 1), ?ADD(dst, ?BSL($1, ?SUB(31, ix)))])},
word}.
builtin_baseX_int_encode(X) ->
{[{"src", word}, {"ix", word}, {"dst", word}],
?LET(n, ?call({baseX_digits, X}, [?V(src), ?I(0)]),
{seq, [?ADD(n, ?ADD(ix, 1)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call({baseX_int_encode_, X}, [?V(src), ?V(dst), ?EXP(X, n), ?V(ix)])]}),
word}.
builtin_baseX_int_encode_(X) ->
{[{"src", word}, {"dst", word}, {"fac", word}, {"ix", word}],
{ifte, ?EQ(fac, 0),
{seq, [?V(dst), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
{ifte, ?EQ(ix, 32),
%% We've filled a word, write it and start on new word
{seq, [?V(dst), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call({baseX_int_encode_, X}, [?V(src), ?I(0), ?V(fac), ?I(0)])]},
?call({baseX_int_encode_, X},
[?MOD(src, fac), ?ADD(dst, ?BSL(?call({baseX_tab, X}, [?DIV(src, fac)]), ?SUB(31, ix))),
?DIV(fac, X), ?ADD(ix, 1)])}
},
word}.
builtin_baseX_digits(X) ->
{[{"x0", word}, {"dgts", word}],
?LET(x1, ?DIV(x0, X),
{ifte, ?EQ(x1, 0), ?V(dgts), ?call({baseX_digits, X}, [?V(x1), ?ADD(dgts, 1)])}),
word}.
builtin_bytes_to_int(32) ->
{[{"w", word}], ?V(w), word};
builtin_bytes_to_int(N) when N < 32 ->
{[{"w", word}], ?BSR(w, 32 - N), word};
builtin_bytes_to_int(N) when N > 32 ->
LastFullWord = N div 32 - 1,
Body = case N rem 32 of
0 -> ?DEREF(n, ?ADD(b, LastFullWord * 32), ?V(n));
R ->
?DEREF(hi, ?ADD(b, LastFullWord * 32),
?DEREF(lo, ?ADD(b, (LastFullWord + 1) * 32),
?ADD(?BSR(lo, 32 - R), ?BSL(hi, R))))
end,
{[{"b", pointer}], Body, word}.
%% Two versions of this helper function, worker for sections not even 16 bytes long
%% and worker_x for the full sized chunks.
builtin_bytes_to_str_worker_x() ->
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
{[{"w", word}, {"offs", word}, {"acc", word}],
{ifte, ?EQ(offs, 16), {seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
?LET(b, ?BYTE(offs, w),
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?call(bytes_to_str_worker_x, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))]))))
},
word}.
builtin_bytes_to_str_worker() ->
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
{[{"w", word}, {"offs", word}, {"acc", word}, {"stop", word}],
{ifte, ?EQ(stop, offs), {seq, [?BSL(acc, ?MUL(2, ?SUB(16, offs))), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
?LET(b, ?BYTE(offs, w),
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?call(bytes_to_str_worker, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo)), ?V(stop)]))))
},
word}.
builtin_bytes_to_str_body(Var, N) when N < 16 ->
[?call(bytes_to_str_worker, [?V(Var), ?I(0), ?I(0), ?I(N)])];
builtin_bytes_to_str_body(Var, 16) ->
[?call(bytes_to_str_worker_x, [?V(Var), ?I(0), ?I(0)])];
builtin_bytes_to_str_body(Var, N) when N < 32 ->
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
[?call(bytes_to_str_worker, [?BSL(Var, 16), ?I(0), ?I(0), ?I(N - 16)])];
builtin_bytes_to_str_body(Var, 32) ->
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
[?call(bytes_to_str_worker_x, [?BSL(Var, 16), ?I(0), ?I(0)])];
builtin_bytes_to_str_body(Var, N) when N > 32 ->
WholeWords = ((N + 31) div 32) - 1,
lists:append(
[ [?DEREF(w, ?ADD(Var, 32 * I), {seq, builtin_bytes_to_str_body(w, 32)}), {inline_asm, [?A(?POP)]}]
|| I <- lists:seq(0, WholeWords - 1) ]) ++
[ ?DEREF(w, ?ADD(Var, 32 * WholeWords), {seq, builtin_bytes_to_str_body(w, N - WholeWords * 32)}) ].
builtin_bytes_to_str(N) when N =< 32 ->
{[{"w", word}],
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
builtin_bytes_to_str_body(w, N) ++
[{inline_asm, [?A(?POP)]}, ?V(ret)]}),
string};
builtin_bytes_to_str(N) when N > 32 ->
{[{"p", pointer}],
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
builtin_bytes_to_str_body(p, N) ++
[{inline_asm, [?A(?POP)]}, ?V(ret)]}),
string}.
builtin_string_reverse() ->
{[{"s", string}],
?DEREF(n, s,
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?V(n), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call(string_reverse_, [?NXT(s), ?I(0), ?I(31), ?SUB(?V(n), 1)]),
{inline_asm, [?A(?POP)]}, ?V(ret)]})),
word}.
builtin_string_reverse_() ->
{[{"p", pointer}, {"x", word}, {"i1", word}, {"i2", word}],
{ifte, ?LT(i2, 0),
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
?LET(p1, ?ADD(p, ?MUL(?DIV(i2, 32), 32)),
?DEREF(w, p1,
?LET(b, ?BYTE(?MOD(i2, 32), w),
{ifte, ?LT(i1, 0),
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call(string_reverse_,
[?V(p), ?BSL(b, 31), ?I(30), ?SUB(i2, 1)])]},
?call(string_reverse_,
[?V(p), ?ADD(x, ?BSL(b, i1)), ?SUB(i1, 1), ?SUB(i2, 1)])})))},
word}.
builtin_addr_to_str() ->
{[{"a", word}], ?call({baseX_int, 58}, [?V(a)]), word}.
%% At most one word
%% | ..... | ========= | ........ |
%% Offs ^ ^- Len -^ TotalLen ^
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen =< 32 ->
%% Bytes are packed into a single word
Masked =
case Offs of
0 -> Bytes;
_ -> ?MOD(Bytes, 1 bsl ((32 - Offs) * 8))
end,
Unpadded =
case 32 - (Offs + Len) of
0 -> Masked;
N -> ?BSR(Masked, N)
end,
case Len of
32 -> Unpadded;
_ -> ?BSL(Unpadded, 32 - Len)
end;
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen > 32 ->
%% Bytes is a pointer to memory. The VM can read at non-aligned addresses.
%% Might read one word more than necessary.
Word = op('!', Offs, Bytes),
case Len == 32 of
true -> Word;
_ -> ?BSL(?BSR(Word, 32 - Len), 32 - Len)
end.
builtin_bytes_concat(A, B) ->
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
MkBytes = fun([W]) -> W;
(Ws) -> {tuple, Ws} end,
Words = fun(N) -> (N + 31) div 32 end,
WordsRes = Words(A + B),
Word = fun(I) when 32 * (I + 1) =< A -> bytes_slice(I * 32, 32, A, ?V(a));
(I) when 32 * I < A ->
Len = A rem 32,
Hi = bytes_slice(32 * I, Len, A, ?V(a)),
Lo = bytes_slice(0, min(32 - Len, B), B, ?V(b)),
?ADD(Hi, ?BSR(Lo, Len));
(I) ->
Offs = 32 * I - A,
Len = min(32, B - Offs),
bytes_slice(Offs, Len, B, ?V(b))
end,
Body =
case {A, B} of
{0, _} -> ?V(b);
{_, 0} -> ?V(a);
_ -> MkBytes([ Word(I) || I <- lists:seq(0, WordsRes - 1) ])
end,
{[{"a", Type(A)}, {"b", Type(B)}], Body, Type(A + B)}.
builtin_bytes_split(A, B) ->
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
MkBytes = fun([W]) -> W;
(Ws) -> {tuple, Ws} end,
Word = fun(I, Max) ->
bytes_slice(I, min(32, Max - I), A + B, ?V(c))
end,
Body =
case {A, B} of
{0, _} -> [?I(0), ?V(c)];
{_, 0} -> [?V(c), ?I(0)];
_ -> [MkBytes([ Word(I, A) || I <- lists:seq(0, A - 1, 32) ]),
MkBytes([ Word(I, A + B) || I <- lists:seq(A, A + B - 1, 32) ])]
end,
{[{"c", Type(A + B)}], {tuple, Body}, {tuple, [Type(A), Type(B)]}}.
bytes_to_raw_string(N, Term) when N =< 32 ->
{tuple, [?I(N), Term]};
bytes_to_raw_string(N, Term) when N > 32 ->
Elem = fun(I) -> #binop{op = '!', left = ?I(32 * I), right = ?V(bin)}
end,
Words = (N + 31) div 32,
?LET(bin, Term, {tuple, [?I(N) | [Elem(I) || I <- lists:seq(0, Words - 1)]]}).
+10 -42
View File
@@ -11,21 +11,21 @@
-export([format/1, pos/1]).
format({last_declaration_must_be_main_contract, Decl = {Kind, _, {con, _, C}, _}}) ->
Msg = io_lib:format("Expected a main contract as the last declaration instead of the ~p '~s'\n",
Msg = io_lib:format("Expected a main contract as the last declaration instead of the ~p '~s'",
[Kind, C]),
mk_err(pos(Decl), Msg);
format({missing_init_function, Con}) ->
Msg = io_lib:format("Missing init function for the contract '~s'.\n", [pp_expr(Con)]),
Cxt = "The 'init' function can only be omitted if the state type is 'unit'.\n",
Msg = io_lib:format("Missing init function for the contract '~s'.", [pp_expr(Con)]),
Cxt = "The 'init' function can only be omitted if the state type is 'unit'.",
mk_err(pos(Con), Msg, Cxt);
format({missing_definition, Id}) ->
Msg = io_lib:format("Missing definition of function '~s'.\n", [pp_expr(Id)]),
Msg = io_lib:format("Missing definition of function '~s'.", [pp_expr(Id)]),
mk_err(pos(Id), Msg);
format({parameterized_state, Decl}) ->
Msg = "The state type cannot be parameterized.\n",
Msg = "The state type cannot be parameterized.",
mk_err(pos(Decl), Msg);
format({parameterized_event, Decl}) ->
Msg = "The event type cannot be parameterized.\n",
Msg = "The event type cannot be parameterized.",
mk_err(pos(Decl), Msg);
format({invalid_entrypoint, Why, Ann, {id, _, Name}, Thing}) ->
What = case Why of higher_order -> "higher-order (contains function types)";
@@ -38,54 +38,22 @@ format({invalid_entrypoint, Why, Ann, {id, _, Name}, Thing}) ->
{argument, _, _} -> io_lib:format("has a ~s type", [What]);
{result, _} -> io_lib:format("is ~s", [What])
end,
Msg = io_lib:format("The ~sof entrypoint '~s' ~s.\n",
Msg = io_lib:format("The ~sof entrypoint '~s' ~s.",
[ThingS, Name, Bad]),
case Why of
polymorphic -> mk_err(pos(Ann), Msg, "Use the FATE backend if you want polymorphic entrypoints.\n");
higher_order -> mk_err(pos(Ann), Msg)
end;
format({cant_compare_type_aevm, Ann, Op, Type}) ->
StringAndTuple = [ "- type string\n"
"- tuple or record of word type\n" || lists:member(Op, ['==', '!=']) ],
Msg = io_lib:format("Cannot compare values of type\n"
"~s\n"
"The AEVM only supports '~s' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"~s",
[pp_type(2, Type), Op, StringAndTuple]),
Cxt = "Use FATE if you need to compare arbitrary types.\n",
mk_err(pos(Ann), Msg, Cxt);
format({invalid_aens_resolve_type, Ann, T}) ->
Msg = io_lib:format("Invalid return type of AENS.resolve:\n"
"~s\n"
"It must be a string or a pubkey type (address, oracle, etc).\n",
"It must be a string or a pubkey type (address, oracle, etc).",
[pp_type(2, T)]),
mk_err(pos(Ann), Msg);
format({unapplied_contract_call, Contract}) ->
Msg = io_lib:format("The AEVM does not support unapplied contract call to\n"
"~s\n", [pp_expr(2, Contract)]),
Cxt = "Use FATE if you need this.\n",
mk_err(pos(Contract), Msg, Cxt);
format({unapplied_builtin, Id}) ->
Msg = io_lib:format("The AEVM does not support unapplied use of ~s.\n", [pp_expr(0, Id)]),
Cxt = "Use FATE if you need this.\n",
mk_err(pos(Id), Msg, Cxt);
format({invalid_map_key_type, Why, Ann, Type}) ->
Msg = io_lib:format("Invalid map key type\n~s\n", [pp_type(2, Type)]),
Cxt = case Why of
polymorphic -> "Map keys cannot be polymorphic in the AEVM. Use FATE if you need this.\n";
function -> "Map keys cannot be higher-order.\n"
end,
mk_err(pos(Ann), Msg, Cxt);
format({invalid_oracle_type, Why, What, Ann, Type}) ->
WhyS = case Why of higher_order -> "higher-order (contain function types)";
polymorphic -> "polymorphic (contain type variables)" end,
Msg = io_lib:format("Invalid oracle type\n~s\n", [pp_type(2, Type)]),
Cxt = io_lib:format("The ~s type must not be ~s.\n", [What, WhyS]),
mk_err(pos(Ann), Msg, Cxt);
format({higher_order_state, {type_def, Ann, _, _, State}}) ->
Msg = io_lib:format("Invalid state type\n~s\n", [pp_type(2, State)]),
Cxt = "The state cannot contain functions in the AEVM. Use FATE if you need this.\n",
Msg = io_lib:format("Invalid oracle type\n~s", [pp_type(2, Type)]),
Cxt = io_lib:format("The ~s type must not be ~s.", [What, WhyS]),
mk_err(pos(Ann), Msg, Cxt);
format({var_args_not_set, Expr}) ->
mk_err( pos(Expr), "Could not deduce type of variable arguments list"
+110 -340
View File
@@ -2,7 +2,7 @@
%%% @author Happi (Erik Stenman)
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% Compiler from Aeterinty Sophia language to the Aeternity VM, aevm.
%%% Compiler from Aeterinty Sophia language to FATE.
%%% @end
%%% Created : 12 Dec 2017
%%%-------------------------------------------------------------------
@@ -12,14 +12,13 @@
, file/2
, from_string/2
, check_call/4
, create_calldata/3 %% deprecated
, create_calldata/3
, create_calldata/4
, version/0
, numeric_version/0
, sophia_type_to_typerep/1
, to_sophia_value/4 %% deprecated, need a backend
, to_sophia_value/4
, to_sophia_value/5
, decode_calldata/3 %% deprecated
, decode_calldata/3
, decode_calldata/4
, parse/2
, add_include_path/2
@@ -27,7 +26,6 @@
]).
-include_lib("aebytecode/include/aeb_opcodes.hrl").
-include("aeso_icode.hrl").
-include("aeso_utils.hrl").
@@ -35,13 +33,10 @@
| pp_ast
| pp_types
| pp_typed_ast
| pp_icode
| pp_assembler
| pp_bytecode
| no_code
| keep_included
| debug_mode
| {backend, aevm | fate}
| {include, {file_system, [string()]} |
{explicit_files, #{string() => binary()}}}
| {src_file, string()}
@@ -106,42 +101,22 @@ add_include_path(File, Options) ->
end.
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, [aeso_errors:error()]}.
from_string(Contract, Options) ->
from_string(proplists:get_value(backend, Options, aevm), Contract, Options).
from_string(Backend, ContractBin, Options) when is_binary(ContractBin) ->
from_string(Backend, binary_to_list(ContractBin), Options);
from_string(Backend, ContractString, Options) ->
from_string(ContractBin, Options) when is_binary(ContractBin) ->
from_string(binary_to_list(ContractBin), Options);
from_string(ContractString, Options) ->
try
from_string1(Backend, ContractString, Options)
from_string1(ContractString, Options)
catch
throw:{error, Errors} -> {error, Errors}
end.
from_string1(aevm, ContractString, Options) ->
#{ icode := Icode
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(aevm, Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options),
{ok, Version} = version(),
Res = #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo,
abi_version => aeb_aevm_abi:abi_version(),
payable => maps:get(payable, Icode)
},
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)};
from_string1(fate, ContractString, Options) ->
from_string1(ContractString, Options) ->
#{ fcode := FCode
, fcode_env := #{child_con_env := ChildContracts}
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
, folded_typed_ast := FoldedTypedAst
, warnings := Warnings } = string_to_code(ContractString, Options),
FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, Options),
pp_assembler(fate, FateCode, Options),
pp_assembler(FateCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(),
Res = #{byte_code => ByteCode,
@@ -150,7 +125,8 @@ from_string1(fate, ContractString, Options) ->
type_info => [],
fate_code => FateCode,
abi_version => aeb_fate_abi:abi_version(),
payable => maps:get(payable, FCode)
payable => maps:get(payable, FCode),
warnings => Warnings
},
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
@@ -168,29 +144,18 @@ string_to_code(ContractString, Options) ->
Ast = parse(ContractString, Options),
pp_sophia_code(Ast, Options),
pp_ast(Ast, Options),
{TypeEnv, FoldedTypedAst, UnfoldedTypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
{TypeEnv, FoldedTypedAst, UnfoldedTypedAst, Warnings} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
pp_typed_ast(UnfoldedTypedAst, Options),
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = ast_to_icode(UnfoldedTypedAst, Options),
pp_icode(Icode, Options),
#{ icode => Icode
, unfolded_typed_ast => UnfoldedTypedAst
, folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv
, ast => Ast };
fate ->
{Env, Fcode} = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, [{original_src, ContractString}|Options]),
#{ fcode => Fcode
, fcode_env => Env
, unfolded_typed_ast => UnfoldedTypedAst
, folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv
, ast => Ast }
end.
{Env, Fcode} = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, [{original_src, ContractString}|Options]),
#{ fcode => Fcode
, fcode_env => Env
, unfolded_typed_ast => UnfoldedTypedAst
, folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv
, ast => Ast
, warnings => Warnings }.
-define(CALL_NAME, "__call").
-define(DECODE_NAME, "__decode").
%% Takes a string containing a contract with a declaration/prototype of a
%% function (foo, say) and adds function __call() = foo(args) calling this
@@ -198,10 +163,8 @@ string_to_code(ContractString, Options) ->
%% terms for the arguments.
%% NOTE: Special treatment for "init" since it might be implicit and has
%% a special return type (typerep, T)
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]}
| {ok, string(), [term()]}
| {error, [aeso_errors:error()]}
when Type :: term().
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), [term()]}
| {error, [aeso_errors:error()]}.
check_call(Source, "init" = FunName, Args, Options) ->
case check_call1(Source, FunName, Args, Options) of
Err = {error, _} when Args == [] ->
@@ -218,44 +181,20 @@ check_call(Source, FunName, Args, Options) ->
check_call1(ContractString0, FunName, Args, Options) ->
try
case proplists:get_value(backend, Options, aevm) of
aevm ->
%% First check the contract without the __call function
#{ast := Ast} = string_to_code(ContractString0, Options),
ContractString = insert_call_function(Ast, ContractString0, ?CALL_NAME, FunName, Args),
#{unfolded_typed_ast := TypedAst,
icode := Icode} = string_to_code(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
{id, _, "_"} -> any;
_ -> aeso_ast_to_icode:ast_typerep(RetType, Icode)
end,
#{ functions := Funs } = Icode,
ArgIcode = get_arg_icode(Funs),
ArgTerms = [ icode_to_term(T, Arg) ||
{T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
RetVMType1 =
case FunName of
"init" -> {tuple, [typerep, RetVMType]};
_ -> RetVMType
end,
{ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms};
fate ->
%% First check the contract without the __call function
#{ fcode := OrgFcode
, fcode_env := #{child_con_env := ChildContracts}
, ast := Ast } = string_to_code(ContractString0, Options),
FateCode = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, []),
%% collect all hashes and compute the first name without hash collision to
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
CallName = first_none_match(?CALL_NAME, SymbolHashes,
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args),
#{fcode := Fcode} = string_to_code(ContractString, Options),
CallArgs = arguments_of_body(CallName, FunName, Fcode),
{ok, FunName, CallArgs}
end
%% First check the contract without the __call function
#{fcode := OrgFcode
, fcode_env := #{child_con_env := ChildContracts}
, ast := Ast} = string_to_code(ContractString0, Options),
FateCode = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, []),
%% collect all hashes and compute the first name without hash collision to
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
CallName = first_none_match(?CALL_NAME, SymbolHashes,
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args),
#{fcode := Fcode} = string_to_code(ContractString, Options),
CallArgs = arguments_of_body(CallName, FunName, Fcode),
{ok, FunName, CallArgs}
catch
throw:{error, Errors} -> {error, Errors}
end.
@@ -303,32 +242,21 @@ last_contract_indent(Decls) ->
_ -> 0
end.
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
{ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
-spec to_sophia_value(string(), string(), ok | error | revert, binary()) ->
{ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
to_sophia_value(ContractString, Fun, ResType, Data) ->
to_sophia_value(ContractString, Fun, ResType, Data, [{backend, aevm}]).
to_sophia_value(ContractString, Fun, ResType, Data, []).
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
{ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
to_sophia_value(_, _, error, Err, _Options) ->
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
to_sophia_value(_, _, revert, Data, Options) ->
case proplists:get_value(backend, Options, aevm) of
aevm ->
case aeb_heap:from_binary(string, Data) of
{ok, Err} ->
{ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
{error, _} ->
Msg = "Could not interpret the revert message\n",
{error, [aeso_errors:new(data_error, Msg)]}
end;
fate ->
try aeb_fate_encoding:deserialize(Data) of
Err -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}
catch _:_ ->
Msg = "Could not deserialize the revert message\n",
{error, [aeso_errors:new(data_error, Msg)]}
end
to_sophia_value(_, _, revert, Data, _Options) ->
try aeso_vm_decode:from_fate({id, [], "string"}, aeb_fate_encoding:deserialize(Data)) of
Err ->
{ok, {app, [], {id, [], "abort"}, [Err]}}
catch _:_ ->
Msg = "Could not deserialize the revert message",
{error, [aeso_errors:new(data_error, Msg)]}
end;
to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
Options = [no_code | Options0],
@@ -338,74 +266,44 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = maps:get(icode, Code),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeb_heap:from_binary(VmType, Data) of
{ok, VmValue} ->
try
{ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[Data, VmType, Type0Str]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
{error, _Err} ->
Msg = io_lib:format("Failed to decode binary as type ~p\n", [VmType]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
fate ->
try
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
catch throw:cannot_translate_to_sophia ->
Type1 = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s\n",
[aeb_fate_encoding:deserialize(Data), Type1]),
{error, [aeso_errors:new(data_error, Msg)]};
_:_ ->
Type1 = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Failed to decode binary as type ~s\n", [Type1]),
{error, [aeso_errors:new(data_error, Msg)]}
end
try
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
catch throw:cannot_translate_to_sophia ->
Type1 = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s",
[aeb_fate_encoding:deserialize(Data), Type1]),
{error, [aeso_errors:new(data_error, Msg)]};
_:_ ->
Type1 = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Failed to decode binary as type ~s", [Type1]),
{error, [aeso_errors:new(data_error, Msg)]}
end
catch
throw:{error, Errors} -> {error, Errors}
end.
-spec create_calldata(string(), string(), [string()]) ->
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
| {error, [aeso_errors:error()]}.
{ok, binary()} | {error, [aeso_errors:error()]}.
create_calldata(Code, Fun, Args) ->
create_calldata(Code, Fun, Args, [{backend, aevm}]).
create_calldata(Code, Fun, Args, []).
-spec create_calldata(string(), string(), [string()], [{atom(), any()}]) ->
{ok, binary()} | {error, [aeso_errors:error()]}.
create_calldata(Code, Fun, Args, Options0) ->
Options = [no_code | Options0],
case proplists:get_value(backend, Options, aevm) of
aevm ->
case check_call(Code, Fun, Args, Options) of
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
aeb_aevm_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
{error, _} = Err -> Err
end;
fate ->
case check_call(Code, Fun, Args, Options) of
{ok, FunName, FateArgs} ->
aeb_fate_abi:create_calldata(FunName, FateArgs);
{error, _} = Err -> Err
end
case check_call(Code, Fun, Args, Options) of
{ok, FunName, FateArgs} ->
aeb_fate_abi:create_calldata(FunName, FateArgs);
{error, _} = Err -> Err
end.
-spec decode_calldata(string(), string(), binary()) ->
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
| {error, [aeso_errors:error()]}.
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
| {error, [aeso_errors:error()]}.
decode_calldata(ContractString, FunName, Calldata) ->
decode_calldata(ContractString, FunName, Calldata, [{backend, aevm}]).
decode_calldata(ContractString, FunName, Calldata, []).
-spec decode_calldata(string(), string(), binary(), options()) ->
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
| {error, [aeso_errors:error()]}.
decode_calldata(ContractString, FunName, Calldata, Options0) ->
Options = [no_code | Options0],
try
@@ -418,73 +316,27 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
Type0 = {tuple_t, [], ArgTypes},
%% user defined data types such as variants needed to match against
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = maps:get(icode, Code),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
{ok, {_, VmValue}} ->
try
{tuple, [], Values} = aeso_vm_decode:from_aevm(VmType, Type, VmValue),
%% Values are Sophia expressions in AST format
{ok, ArgTypes, Values}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[VmValue, VmType, Type0Str]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
{error, _Err} ->
Msg = io_lib:format("Failed to decode calldata as type ~p\n", [VmType]),
case aeb_fate_abi:decode_calldata(FunName, Calldata) of
{ok, FateArgs} ->
try
{tuple_t, [], ArgTypes1} = Type,
AstArgs = [ aeso_vm_decode:from_fate(ArgType, FateArg)
|| {ArgType, FateArg} <- lists:zip(ArgTypes1, FateArgs)],
{ok, ArgTypes, AstArgs}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate FATE value ~p\n to Sophia type ~s",
[FateArgs, Type0Str]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
fate ->
case aeb_fate_abi:decode_calldata(FunName, Calldata) of
{ok, FateArgs} ->
try
{tuple_t, [], ArgTypes1} = Type,
AstArgs = [ aeso_vm_decode:from_fate(ArgType, FateArg)
|| {ArgType, FateArg} <- lists:zip(ArgTypes1, FateArgs)],
{ok, ArgTypes, AstArgs}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate FATE value ~p\n to Sophia type ~s\n",
[FateArgs, Type0Str]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
{error, _} ->
Msg = io_lib:format("Failed to decode calldata binary\n", []),
{error, [aeso_errors:new(data_error, Msg)]}
end
{error, _} ->
Msg = io_lib:format("Failed to decode calldata binary", []),
{error, [aeso_errors:new(data_error, Msg)]}
end
catch
throw:{error, Errors} -> {error, Errors}
end.
get_arg_icode(Funs) ->
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
[Args] -> Args;
[] -> error_missing_call_function()
end.
-dialyzer({nowarn_function, error_missing_call_function/0}).
error_missing_call_function() ->
Msg = "Internal error: missing '__call'-function",
aeso_errors:throw(aeso_errors:new(internal_error, Msg)).
get_call_type([{Contract, _, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
case [ {lists:last(QFunName), FunType}
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
[{guarded, _, [], {typed, _,
{app, _,
{typed, _, {qid, _, QFunName}, FunType}, _}, _}}]} <- Defs ] of
[Call] -> {ok, Call};
[] -> error_missing_call_function()
end;
get_call_type([_ | Contracts]) ->
%% The __call should be in the final contract
get_call_type(Contracts).
-dialyzer({nowarn_function, get_decode_type/2}).
get_decode_type(FunName, [{Contract, Ann, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}];
@@ -496,7 +348,7 @@ get_decode_type(FunName, [{Contract, Ann, _, Defs}]) when ?IS_CONTRACT_HEAD(Cont
case FunName of
"init" -> {ok, [], {tuple_t, [], []}};
_ ->
Msg = io_lib:format("Function '~s' is missing in contract\n", [FunName]),
Msg = io_lib:format("Function '~s' is missing in contract", [FunName]),
Pos = aeso_code_errors:pos(Ann),
aeso_errors:throw(aeso_errors:new(data_error, Pos, Msg))
end
@@ -505,87 +357,17 @@ get_decode_type(FunName, [_ | Contracts]) ->
%% The __decode should be in the final contract
get_decode_type(FunName, Contracts).
%% Translate an icode value (error if not value) to an Erlang term that can be
%% consumed by aeb_heap:to_binary().
icode_to_term(word, {integer, N}) -> N;
icode_to_term(word, {unop, '-', {integer, N}}) -> -N;
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
Str;
icode_to_term({list, T}, {list, Vs}) ->
[ icode_to_term(T, V) || V <- Vs ];
icode_to_term({tuple, Ts}, {tuple, Vs}) ->
list_to_tuple(icodes_to_terms(Ts, Vs));
icode_to_term({variant, Cs}, {tuple, [{integer, Tag} | Args]}) ->
Ts = lists:nth(Tag + 1, Cs),
{variant, Tag, icodes_to_terms(Ts, Args)};
icode_to_term(T = {map, KT, VT}, M) ->
%% Maps are compiled to builtin and primop calls, so this gets a little hairy
case M of
{funcall, {var_ref, {builtin, map_put}}, [M1, K, V]} ->
Map = icode_to_term(T, M1),
Key = icode_to_term(KT, K),
Val = icode_to_term(VT, V),
Map#{ Key => Val };
#prim_call_contract{ address = {integer, 0},
arg = {tuple, [{integer, ?PRIM_CALL_MAP_EMPTY}, _, _]} } ->
#{};
_ -> throw({todo, M})
end;
icode_to_term(word, {unop, 'bnot', A}) ->
bnot icode_to_term(word, A);
icode_to_term(word, {binop, 'bor', A, B}) ->
icode_to_term(word, A) bor icode_to_term(word, B);
icode_to_term(word, {binop, 'bsl', A, B}) ->
icode_to_term(word, B) bsl icode_to_term(word, A);
icode_to_term(word, {binop, 'band', A, B}) ->
icode_to_term(word, A) band icode_to_term(word, B);
icode_to_term(typerep, _) ->
throw({todo, typerep});
icode_to_term(T, V) ->
throw({not_a_value, T, V}).
icodes_to_terms(Ts, Vs) ->
[ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ].
ast_to_icode(TypedAst, Options) ->
aeso_ast_to_icode:convert_typed(TypedAst, Options).
assemble(Icode, Options) ->
aeso_icode_to_asm:convert(Icode, Options).
to_bytecode(['COMMENT',_|Rest],_Options) ->
to_bytecode(Rest,_Options);
to_bytecode([Op|Rest], Options) ->
[aeb_opcodes:m_to_op(Op)|to_bytecode(Rest, Options)];
to_bytecode([], _) -> [].
extract_type_info(#{functions := Functions} =_Icode) ->
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
Payable = fun(Attrs) -> proplists:get_value(payable, Attrs, false) end,
TypeInfo = [aeb_aevm_abi:function_type_info(list_to_binary(lists:last(Name)),
Payable(Attrs), ArgTypesOnly(Args), TypeRep)
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
not is_tuple(Name),
not lists:member(private, Attrs)
],
lists:sort(TypeInfo).
pp_sophia_code(C, Opts)-> pp(C, Opts, pp_sophia_code, fun(Code) ->
io:format("~s\n", [prettypr:format(aeso_pretty:decls(Code))])
end).
pp_ast(C, Opts) -> pp(C, Opts, pp_ast, fun aeso_ast:pp/1).
pp_typed_ast(C, Opts)-> pp(C, Opts, pp_typed_ast, fun aeso_ast:pp_typed/1).
pp_icode(C, Opts) -> pp(C, Opts, pp_icode, fun aeso_icode:pp/1).
pp_bytecode(C, Opts) -> pp(C, Opts, pp_bytecode, fun aeb_disassemble:pp/1).
pp_assembler(aevm, C, Opts) -> pp(C, Opts, pp_assembler, fun aeb_asm:pp/1);
pp_assembler(fate, C, Opts) -> pp(C, Opts, pp_assembler, fun(Asm) -> io:format("~s", [aeb_fate_asm:pp(Asm)]) end).
pp_assembler(C, Opts) -> pp(C, Opts, pp_assembler, fun(Asm) -> io:format("~s", [aeb_fate_asm:pp(Asm)]) end).
pp(Code, Options, Option, PPFun) ->
case proplists:lookup(Option, Options) of
{Option, true} ->
{Option1, true} when Option1 =:= Option ->
PPFun(Code);
none ->
ok
@@ -598,31 +380,27 @@ pp(Code, Options, Option, PPFun) ->
-spec validate_byte_code(map(), string(), options()) -> ok | {error, [aeso_errors:error()]}.
validate_byte_code(#{ byte_code := ByteCode, payable := Payable }, Source, Options) ->
Fail = fun(Err) -> {error, [aeso_errors:new(data_error, Err)]} end,
case proplists:get_value(backend, Options, aevm) of
B when B /= fate -> Fail(io_lib:format("Unsupported backend: ~s\n", [B]));
fate ->
try
FCode1 = ?protect(deserialize, aeb_fate_code:strip_init_function(aeb_fate_code:deserialize(ByteCode))),
{FCode2, SrcPayable} =
?protect(compile,
begin
{ok, #{ byte_code := SrcByteCode, payable := SrcPayable }} =
from_string1(fate, Source, Options),
FCode = aeb_fate_code:deserialize(SrcByteCode),
{aeb_fate_code:strip_init_function(FCode), SrcPayable}
end),
case compare_fate_code(FCode1, FCode2) of
ok when SrcPayable /= Payable ->
Not = fun(true) -> ""; (false) -> " not" end,
Fail(io_lib:format("Byte code contract is~s payable, but source code contract is~s.\n",
[Not(Payable), Not(SrcPayable)]));
ok -> ok;
{error, Why} -> Fail(io_lib:format("Byte code does not match source code.\n~s", [Why]))
end
catch
throw:{deserialize, _} -> Fail("Invalid byte code");
throw:{compile, {error, Errs}} -> {error, Errs}
end
try
FCode1 = ?protect(deserialize, aeb_fate_code:strip_init_function(aeb_fate_code:deserialize(ByteCode))),
{FCode2, SrcPayable} =
?protect(compile,
begin
{ok, #{ byte_code := SrcByteCode, payable := SrcPayable }} =
from_string1(Source, Options),
FCode = aeb_fate_code:deserialize(SrcByteCode),
{aeb_fate_code:strip_init_function(FCode), SrcPayable}
end),
case compare_fate_code(FCode1, FCode2) of
ok when SrcPayable /= Payable ->
Not = fun(true) -> ""; (false) -> " not" end,
Fail(io_lib:format("Byte code contract is~s payable, but source code contract is~s.\n",
[Not(Payable), Not(SrcPayable)]));
ok -> ok;
{error, Why} -> Fail(io_lib:format("Byte code does not match source code.\n~s", [Why]))
end
catch
throw:{deserialize, _} -> Fail("Invalid byte code");
throw:{compile, {error, Errs}} -> {error, Errs}
end.
compare_fate_code(FCode1, FCode2) ->
@@ -674,14 +452,6 @@ pp_fate_type(T) -> io_lib:format("~w", [T]).
%% -------------------------------------------------------------------
-spec sophia_type_to_typerep(string()) -> {error, bad_type} | {ok, aeb_aevm_data:type()}.
sophia_type_to_typerep(String) ->
Ast = aeso_parser:run_parser(aeso_parser:type(), String),
try aeso_ast_to_icode:ast_typerep(Ast) of
Type -> {ok, Type}
catch _:_ -> {error, bad_type}
end.
-spec parse(string(), aeso_compiler:options()) -> none() | aeso_syntax:ast().
parse(Text, Options) ->
parse(Text, sets:new(), Options).
+14 -3
View File
@@ -30,12 +30,15 @@
-export([ err_msg/1
, msg/1
, msg_oneline/1
, new/2
, new/3
, new/4
, pos/2
, pos/3
, pp/1
, pp_oneline/1
, pp_pos/1
, to_json/1
, throw/1
, type/1
@@ -65,10 +68,13 @@ throw(#err{} = Err) ->
erlang:throw({error, [Err]}).
msg(#err{ message = Msg, context = none }) -> Msg;
msg(#err{ message = Msg, context = Ctxt }) -> Msg ++ Ctxt.
msg(#err{ message = Msg, context = Ctxt }) -> Msg ++ "\n" ++ Ctxt.
msg_oneline(#err{ message = Msg, context = none }) -> Msg;
msg_oneline(#err{ message = Msg, context = Ctxt }) -> Msg ++ " - " ++ Ctxt.
err_msg(#err{ pos = Pos } = Err) ->
lists:flatten(io_lib:format("~s~s", [str_pos(Pos), msg(Err)])).
lists:flatten(io_lib:format("~s~s\n", [str_pos(Pos), msg(Err)])).
str_pos(#pos{file = no_file, line = L, col = C}) ->
io_lib:format("~p:~p:", [L, C]);
@@ -78,7 +84,12 @@ str_pos(#pos{file = F, line = L, col = C}) ->
type(#err{ type = Type }) -> Type.
pp(#err{ type = Kind, pos = Pos } = Err) ->
lists:flatten(io_lib:format("~s~s:\n~s", [pp_kind(Kind), pp_pos(Pos), msg(Err)])).
lists:flatten(io_lib:format("~s~s:\n~s\n", [pp_kind(Kind), pp_pos(Pos), msg(Err)])).
pp_oneline(#err{ type = Kind, pos = Pos } = Err) ->
Msg = msg_oneline(Err),
OneLineMsg = re:replace(Msg, "[\s\\n]+", " ", [global]),
lists:flatten(io_lib:format("~s~s: ~s", [pp_kind(Kind), pp_pos(Pos), OneLineMsg])).
pp_kind(type_error) -> "Type error";
pp_kind(parse_error) -> "Parse error";
+24 -13
View File
@@ -176,18 +176,14 @@ lookup_var(#env{vars = Vars}, X) ->
%% -- The compiler --
lit_to_fate(Env, L) ->
case L of
{int, N} -> aeb_fate_data:make_integer(N);
{string, S} -> aeb_fate_data:make_string(S);
{bytes, B} -> aeb_fate_data:make_bytes(B);
{bool, B} -> aeb_fate_data:make_boolean(B);
{account_pubkey, K} -> aeb_fate_data:make_address(K);
{contract_pubkey, K} -> aeb_fate_data:make_contract(K);
{oracle_pubkey, K} -> aeb_fate_data:make_oracle(K);
{oracle_query_id, H} -> aeb_fate_data:make_oracle_query(H);
{contract_code, C} ->
Options = Env#env.options,
serialize_contract_code(Env, C) ->
Cache = case get(contract_code_cache) of
undefined -> put(contract_code_cache, #{}), #{};
Res -> Res
end,
case maps:get(C, Cache, none) of
none ->
Options = Env#env.options,
FCode = maps:get(C, Env#env.child_contracts),
FateCode = compile(Env#env.child_contracts, FCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []),
@@ -201,7 +197,22 @@ lit_to_fate(Env, L) ->
payable => maps:get(payable, FCode)
},
Serialized = aeser_contract_code:serialize(Code),
aeb_fate_data:make_contract_bytearray(Serialized);
put(contract_code_cache, maps:put(C, Serialized, Cache)),
Serialized;
Serialized -> Serialized
end.
lit_to_fate(Env, L) ->
case L of
{int, N} -> aeb_fate_data:make_integer(N);
{string, S} -> aeb_fate_data:make_string(S);
{bytes, B} -> aeb_fate_data:make_bytes(B);
{bool, B} -> aeb_fate_data:make_boolean(B);
{account_pubkey, K} -> aeb_fate_data:make_address(K);
{contract_pubkey, K} -> aeb_fate_data:make_contract(K);
{oracle_pubkey, K} -> aeb_fate_data:make_oracle(K);
{oracle_query_id, H} -> aeb_fate_data:make_oracle_query(H);
{contract_code, C} -> aeb_fate_data:make_contract_bytearray(serialize_contract_code(Env, C));
{typerep, T} -> aeb_fate_data:make_typerep(type_to_scode(T))
end.
-153
View File
@@ -1,153 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author Happi (Erik Stenman)
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% Intermediate Code for Aeterinty Sophia language.
%%% @end
%%% Created : 21 Dec 2017
%%%
%%%-------------------------------------------------------------------
-module(aeso_icode).
-export([new/1,
pp/1,
set_name/2,
set_namespace/2,
set_payable/2,
enter_namespace/2,
get_namespace/1,
in_main_contract/1,
qualify/2,
set_functions/2,
map_typerep/2,
option_typerep/1,
get_constructor_tag/2]).
-export_type([icode/0]).
-include("aeso_icode.hrl").
-type type_def() :: fun(([aeb_aevm_data:type()]) -> aeb_aevm_data:type()).
-type bindings() :: any().
-type fun_dec() :: { string()
, [modifier()]
, arg_list()
, expr()
, aeb_aevm_data:type()}.
-type modifier() :: private | stateful.
-type type_name() :: string() | [string()].
-type icode() :: #{ contract_name => string()
, functions => [fun_dec()]
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
, env => [bindings()]
, state_type => aeb_aevm_data:type()
, event_type => aeb_aevm_data:type()
, types => #{ type_name() => type_def() }
, type_vars => #{ string() => aeb_aevm_data:type() }
, constructors => #{ [string()] => integer() } %% name to tag
, options => [any()]
, payable => boolean()
}.
pp(Icode) ->
%% TODO: Actually do *Pretty* printing.
io:format("~p~n", [Icode]).
-spec new([any()]) -> icode().
new(Options) ->
#{ contract_name => ""
, functions => []
, env => new_env()
%% Default to unit type for state and event
, state_type => {tuple, []}
, event_type => {tuple, []}
, types => builtin_types()
, type_vars => #{}
, constructors => builtin_constructors()
, options => Options
, payable => false }.
builtin_types() ->
Word = fun([]) -> word end,
#{ "bool" => Word
, "int" => Word
, "char" => Word
, "bits" => Word
, "string" => fun([]) -> string end
, "address" => Word
, "hash" => Word
, "unit" => fun([]) -> {tuple, []} end
, "signature" => fun([]) -> {tuple, [word, word]} end
, "oracle" => fun([_, _]) -> word end
, "oracle_query" => fun([_, _]) -> word end
, "list" => fun([A]) -> {list, A} end
, "option" => fun([A]) -> {variant, [[], [A]]} end
, "map" => fun([K, V]) -> map_typerep(K, V) end
, ["Chain", "ttl"] => fun([]) -> {variant, [[word], [word]]} end
, ["AENS", "pointee"] => fun([]) -> {variant, [[word], [word], [word]]} end
}.
builtin_constructors() ->
#{ ["RelativeTTL"] => 0
, ["FixedTTL"] => 1
, ["None"] => 0
, ["Some"] => 1
, ["AccountPointee"] => 0
, ["OraclePointee"] => 1
, ["ContractPointee"] => 2
}.
map_typerep(K, V) ->
{map, K, V}.
option_typerep(A) ->
{variant, [[], [A]]}.
new_env() ->
[].
-spec set_name(string(), icode()) -> icode().
set_name(Name, Icode) ->
maps:put(contract_name, Name, Icode).
-spec set_payable(boolean(), icode()) -> icode().
set_payable(Payable, Icode) ->
maps:put(payable, Payable, Icode).
-spec set_namespace(aeso_syntax:con() | aeso_syntax:qcon(), icode()) -> icode().
set_namespace(NS, Icode) -> Icode#{ namespace => NS }.
-spec enter_namespace(aeso_syntax:con(), icode()) -> icode().
enter_namespace(NS, Icode = #{ namespace := NS1 }) ->
Icode#{ namespace => aeso_syntax:qualify(NS1, NS) };
enter_namespace(NS, Icode) ->
Icode#{ namespace => NS }.
-spec in_main_contract(icode()) -> boolean().
in_main_contract(#{ namespace := {con, _, Main}, contract_name := Main }) -> true;
in_main_contract(_Icode) -> false.
-spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon().
get_namespace(Icode) -> maps:get(namespace, Icode, false).
-spec qualify(aeso_syntax:id() | aeso_syntax:con(), icode()) -> aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon().
qualify(X, Icode) ->
case get_namespace(Icode) of
false -> X;
NS -> aeso_syntax:qualify(NS, X)
end.
-spec set_functions([fun_dec()], icode()) -> icode().
set_functions(NewFuns, Icode) ->
maps:put(functions, NewFuns, Icode).
-spec get_constructor_tag([string()], icode()) -> integer().
get_constructor_tag(Name, #{constructors := Constructors}) ->
case maps:get(Name, Constructors, undefined) of
undefined -> error({undefined_constructor, Name});
Tag -> Tag
end.
-59
View File
@@ -1,59 +0,0 @@
-include_lib("aebytecode/include/aeb_typerep_def.hrl").
-record(arg, {name::string(), type::?Type()}).
-type expr() :: term().
-type arg() :: #arg{name::string(), type::?Type()}.
-type arg_list() :: [arg()].
-record(fun_dec, { name :: string()
, args :: arg_list()
, body :: expr()}).
-record(var_ref, { name :: string() | list(string()) | {builtin, atom() | tuple()}}).
-record(prim_call_contract,
{ gas :: expr()
, address :: expr()
, value :: expr()
, arg :: expr()
, type_hash:: expr()
}).
-record(prim_balance, { address :: expr() }).
-record(prim_block_hash, { height :: expr() }).
-record(prim_put, { state :: expr() }).
-record(integer, {value :: integer()}).
-record(tuple, {cpts :: [expr()]}).
-record(list, {elems :: [expr()]}).
-record(unop, { op :: term()
, rand :: expr()}).
-record(binop, { op :: term()
, left :: expr()
, right :: expr()}).
-record(ifte, { decision :: expr()
, then :: expr()
, else :: expr()}).
-record(switch, { expr :: expr()
, cases :: [{expr(),expr()}]}).
-record(funcall, { function :: expr()
, args :: [expr()]}).
-record(lambda, { args :: arg_list(),
body :: expr()}).
-record(missing_field, { format :: string()
, args :: [term()]}).
-record(seq, {exprs :: [expr()]}).
-record(event, {topics :: [expr()], payload :: expr()}).
-983
View File
@@ -1,983 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author Happi (Erik Stenman)
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% Translator from Aesophia Icode to Aevm Assebly
%%% @end
%%% Created : 21 Dec 2017
%%%
%%%-------------------------------------------------------------------
-module(aeso_icode_to_asm).
-export([convert/2]).
-include_lib("aebytecode/include/aeb_opcodes.hrl").
-include("aeso_icode.hrl").
i(Code) -> aeb_opcodes:mnemonic(Code).
%% We don't track purity or statefulness in the type checker yet.
is_stateful({FName, _, _, _, _}) -> lists:last(FName) /= "init".
is_public({_Name, Attrs, _Args, _Body, _Type}) -> not lists:member(private, Attrs).
convert(#{ contract_name := _ContractName
, state_type := StateType
, functions := Functions
},
_Options) ->
%% Create a function dispatcher
DispatchFun = {"%main", [], [{"arg", "_"}],
{switch, {var_ref, "arg"},
[{{tuple, [fun_hash(Fun),
{tuple, make_args(Args)}]},
icode_seq([ hack_return_address(Fun, length(Args) + 1) ] ++
[ {funcall, {var_ref, FName}, make_args(Args)}]
)}
|| Fun={FName, _, Args, _,_TypeRep} <- Functions, is_public(Fun) ]},
word},
NewFunctions = Functions ++ [DispatchFun],
%% Create a function environment
Funs = [{Name, length(Args), make_ref()}
|| {Name, _Attrs, Args, _Body, _Type} <- NewFunctions],
%% Create dummy code to call the main function with one argument
%% taken from the stack
StopLabel = make_ref(),
StatefulStopLabel = make_ref(),
MainFunction = lookup_fun(Funs, "%main"),
StateTypeValue = aeso_ast_to_icode:type_value(StateType),
DispatchCode = [%% push two return addresses to stop, one for stateful
%% functions and one for non-stateful functions.
push_label(StatefulStopLabel),
push_label(StopLabel),
%% The calldata is already on the stack when we start. Put
%% it on top (also reorders StatefulStop and Stop).
swap(2),
jump(MainFunction),
jumpdest(StatefulStopLabel),
%% We need to encode the state type and put it
%% underneath the return value.
assemble_expr(Funs, [], nontail, StateTypeValue), %% StateT Ret
swap(1), %% Ret StateT
%% We should also change the state value at address 0 to a
%% pointer to the state value (to allow 0 to represent an
%% unchanged state).
i(?MSIZE), %% Ptr
push(0), i(?MLOAD), %% Val Ptr
i(?MSIZE), i(?MSTORE), %% Ptr Mem[Ptr] := Val
push(0), i(?MSTORE), %% Mem[0] := Ptr
%% The pointer to the return value is on top of
%% the stack, but the return instruction takes two
%% stack arguments.
push(0),
i(?RETURN),
jumpdest(StopLabel),
%% Set state pointer to 0 to indicate that we didn't change state
push(0), dup(1), i(?MSTORE),
%% Same as StatefulStopLabel above
push(0),
i(?RETURN)
],
%% Code is a deep list of instructions, containing labels and
%% references to them. Labels take the form {'JUMPDEST', Ref}, and
%% references take the form {push_label, Ref}, which is translated
%% into a PUSH instruction.
Code = [assemble_function(Funs, Name, Args, Body)
|| {Name, _, Args, Body, _Type} <- NewFunctions],
resolve_references(
[%% i(?COMMENT), "CONTRACT: " ++ ContractName,
DispatchCode,
Code]).
%% Generate error on correct format.
gen_error(Error) ->
error({code_errors, [Error]}).
make_args(Args) ->
[{var_ref, [I-1 + $a]} || I <- lists:seq(1, length(Args))].
fun_hash({FName, _, Args, _, TypeRep}) ->
ArgType = {tuple, [T || {_, T} <- Args]},
<<Hash:256>> = aeb_aevm_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
%% one for stateful functions and the bottom one for non-stateful.
hack_return_address(Fun, N) ->
case is_stateful(Fun) of
true -> {inline_asm, [i(?MSIZE)]};
false ->
{inline_asm, %% X1 .. XN State NoState
[ dup(N + 2) %% NoState X1 .. XN State NoState
, swap(N + 1) %% State X1 .. XN NoState NoState
]} %% Top of the stack will be discarded.
end.
assemble_function(Funs, Name, Args, Body) ->
[jumpdest(lookup_fun(Funs, Name)),
assemble_expr(Funs, lists:reverse(Args), tail, Body),
%% swap return value and first argument
pop_args(length(Args)),
swap(1),
i(?JUMP)].
%% {seq, Es} - should be "one" operation in terms of stack content
%% i.e. after the `seq` there should be one new element on the stack.
assemble_expr(Funs, Stack, Tail, {seq, [E]}) ->
assemble_expr(Funs, Stack, Tail, E);
assemble_expr(Funs, Stack, Tail, {seq, [E | Es]}) ->
[assemble_expr(Funs, Stack, nontail, E),
assemble_expr(Funs, Stack, Tail, {seq, Es})];
assemble_expr(_Funs, _Stack, _Tail, {inline_asm, Code}) ->
Code; %% Unsafe! Code should take care to respect the stack!
assemble_expr(Funs, Stack, _TailPosition, {var_ref, Id}) ->
case lists:keymember(Id, 1, Stack) of
true ->
dup(lookup_var(Id, Stack));
false ->
%% Build a closure
%% When a top-level fun is called directly, we do not
%% reach this case.
Eta = make_ref(),
Continue = make_ref(),
[i(?MSIZE),
push_label(Eta),
dup(2),
i(?MSTORE),
jump(Continue),
%% the code of the closure
jumpdest(Eta),
%% pop the pointer to the function
pop(1),
jump(lookup_fun(Funs, Id)),
jumpdest(Continue)]
end;
assemble_expr(_, _, _, {missing_field, Format, Args}) ->
io:format(Format, Args),
gen_error(missing_field);
assemble_expr(_Funs, _Stack, _, {integer, N}) ->
push(N);
assemble_expr(Funs, Stack, _, {tuple, Cpts}) ->
%% We build tuples right-to-left, so that the first write to the
%% tuple extends the memory size. Because we use ?MSIZE as the
%% heap pointer, we must allocate the tuple AFTER computing the
%% first element.
%% We store elements into the tuple as soon as possible, to avoid
%% keeping them for a long time on the stack.
case lists:reverse(Cpts) of
[] ->
i(?MSIZE);
[Last|Rest] ->
[assemble_expr(Funs, Stack, nontail, Last),
%% allocate the tuple memory
i(?MSIZE),
%% compute address of last word
push(32 * (length(Cpts) - 1)), i(?ADD),
%% Stack: <last-value> <pointer>
%% Write value to memory (allocates the tuple)
swap(1), dup(2), i(?MSTORE),
%% Stack: pointer to last word written
[[%% Update pointer to next word to be written
push(32), swap(1), i(?SUB),
%% Compute element
assemble_expr(Funs, [pointer|Stack], nontail, A),
%% Write element to memory
dup(2), i(?MSTORE)]
%% And we leave a pointer to the last word written on
%% the stack
|| A <- Rest]]
%% The pointer to the entire tuple is on the stack
end;
assemble_expr(_Funs, _Stack, _, {list, []}) ->
%% Use Erik's value of -1 for []
[push(0), i(?NOT)];
assemble_expr(Funs, Stack, _, {list, [A|B]}) ->
assemble_expr(Funs, Stack, nontail, {tuple, [A, {list, B}]});
assemble_expr(Funs, Stack, _, {unop, '!', A}) ->
case A of
{binop, Logical, _, _} when Logical=='&&'; Logical=='||' ->
assemble_expr(Funs, Stack, nontail, {ifte, A, {integer, 0}, {integer, 1}});
_ ->
[assemble_expr(Funs, Stack, nontail, A),
i(?ISZERO)
]
end;
assemble_expr(Funs, Stack, _, {event, Topics, Payload}) ->
[assemble_exprs(Funs, Stack, Topics ++ [Payload]),
case length(Topics) of
0 -> i(?LOG0);
1 -> i(?LOG1);
2 -> i(?LOG2);
3 -> i(?LOG3);
4 -> i(?LOG4)
end, i(?MSIZE)];
assemble_expr(Funs, Stack, _, {unop, Op, A}) ->
[assemble_expr(Funs, Stack, nontail, A),
assemble_prefix(Op)];
assemble_expr(Funs, Stack, Tail, {binop, '&&', A, B}) ->
assemble_expr(Funs, Stack, Tail, {ifte, A, B, {integer, 0}});
assemble_expr(Funs, Stack, Tail, {binop, '||', A, B}) ->
assemble_expr(Funs, Stack, Tail, {ifte, A, {integer, 1}, B});
assemble_expr(Funs, Stack, Tail, {binop, '::', A, B}) ->
%% Take advantage of optimizations in tuple construction.
assemble_expr(Funs, Stack, Tail, {tuple, [A, B]});
assemble_expr(Funs, Stack, _, {binop, Op, A, B}) ->
%% EEVM binary instructions take their first argument from the top
%% of the stack, so to get operands on the stack in the right
%% order, we evaluate from right to left.
[assemble_expr(Funs, Stack, nontail, B),
assemble_expr(Funs, [dummy|Stack], nontail, A),
assemble_infix(Op)];
assemble_expr(Funs, Stack, _, {lambda, Args, Body}) ->
Function = make_ref(),
FunBody = make_ref(),
Continue = make_ref(),
NoMatch = make_ref(),
FreeVars = free_vars({lambda, Args, Body}),
{NewVars, MatchingCode} = assemble_pattern(FunBody, NoMatch, {tuple, [{var_ref, "_"}|FreeVars]}),
BodyCode = assemble_expr(Funs, NewVars ++ lists:reverse([ {Arg#arg.name, Arg#arg.type} || Arg <- Args ]), tail, Body),
[assemble_expr(Funs, Stack, nontail, {tuple, [{label, Function}|FreeVars]}),
jump(Continue), %% will be optimized away
jumpdest(Function),
%% A pointer to the closure is on the stack
MatchingCode,
jumpdest(FunBody),
BodyCode,
pop_args(length(Args)+length(NewVars)),
swap(1),
i(?JUMP),
jumpdest(NoMatch), %% dead code--raise an exception just in case
push(0),
i(?NOT),
i(?MLOAD),
i(?STOP),
jumpdest(Continue)];
assemble_expr(_, _, _, {label, Label}) ->
push_label(Label);
assemble_expr(Funs, Stack, nontail, {funcall, Fun, Args}) ->
Return = make_ref(),
%% This is the obvious code:
%% [{push_label, Return},
%% assemble_exprs(Funs, [return_address|Stack], Args++[Fun]),
%% 'JUMP',
%% {'JUMPDEST', Return}];
%% Its problem is that it stores the return address on the stack
%% while the arguments are computed, which is unnecessary. To
%% avoid that, we compute the last argument FIRST, and replace it
%% with the return address using a SWAP.
%%
%% assemble_function leaves the code pointer of the function to
%% call on top of the stack, and--if the function is not a
%% top-level name--a pointer to its tuple of free variables. In
%% either case a JUMP is the right way to call it.
case Args of
[] ->
[push_label(Return),
assemble_function(Funs, [return_address|Stack], Fun),
i(?JUMP),
jumpdest(Return)];
_ ->
{Init, [Last]} = lists:split(length(Args) - 1, Args),
[assemble_exprs(Funs, Stack, [Last|Init]),
%% Put the return address in the right place, which also
%% reorders the args correctly.
push_label(Return),
swap(length(Args)),
assemble_function(Funs, [dummy || _ <- Args] ++ [return_address|Stack], Fun),
i(?JUMP),
jumpdest(Return)]
end;
assemble_expr(Funs, Stack, tail, {funcall, Fun, Args}) ->
IsTopLevel = is_top_level_fun(Stack, Fun),
%% If the fun is not top-level, then it may refer to local
%% variables and must be computed before stack shuffling.
ArgsAndFun = Args++[Fun || not IsTopLevel],
ComputeArgsAndFun = assemble_exprs(Funs, Stack, ArgsAndFun),
%% Copy arguments back down the stack to the start of the frame
ShuffleSpec = lists:seq(length(ArgsAndFun), 1, -1) ++ [discard || _ <- Stack],
Shuffle = shuffle_stack(ShuffleSpec),
[ComputeArgsAndFun, Shuffle,
if IsTopLevel ->
%% still need to compute function
assemble_function(Funs, [], Fun);
true ->
%% need to unpack a closure
[dup(1), i(?MLOAD)]
end,
i(?JUMP)];
assemble_expr(Funs, Stack, Tail, {ifte, Decision, Then, Else}) ->
%% This compilation scheme introduces a lot of labels and
%% jumps. Unnecessary ones are removed later in
%% resolve_references.
Close = make_ref(),
ThenL = make_ref(),
ElseL = make_ref(),
[assemble_decision(Funs, Stack, Decision, ThenL, ElseL),
jumpdest(ElseL),
assemble_expr(Funs, Stack, Tail, Else),
jump(Close),
jumpdest(ThenL),
assemble_expr(Funs, Stack, Tail, Then),
jumpdest(Close)
];
assemble_expr(Funs, Stack, Tail, {switch, A, Cases}) ->
Close = make_ref(),
[assemble_expr(Funs, Stack, nontail, A),
assemble_cases(Funs, Stack, Tail, Close, Cases),
{'JUMPDEST', Close}];
%% State primitives
%% (A pointer to) the contract state is stored at address 0.
assemble_expr(_Funs, _Stack, _Tail, prim_state) ->
[push(0), i(?MLOAD)];
assemble_expr(Funs, Stack, _Tail, #prim_put{ state = State }) ->
[assemble_expr(Funs, Stack, nontail, State),
push(0), i(?MSTORE), %% We need something for the unit value on the stack,
i(?MSIZE)]; %% MSIZE is the cheapest instruction.
%% 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) ->
[i(?CALLER)];
assemble_expr(_Funs, _Stack, _Tail, prim_call_value) ->
[i(?CALLVALUE)];
assemble_expr(_Funs, _Stack, _Tail, prim_gas_price) ->
[i(?GASPRICE)];
assemble_expr(_Funs, _Stack, _Tail, prim_gas_left) ->
[i(?GAS)];
assemble_expr(_Funs, _Stack, _Tail, prim_coinbase) ->
[i(?COINBASE)];
assemble_expr(_Funs, _Stack, _Tail, prim_timestamp) ->
[i(?TIMESTAMP)];
assemble_expr(_Funs, _Stack, _Tail, prim_block_height) ->
[i(?NUMBER)];
assemble_expr(_Funs, _Stack, _Tail, prim_difficulty) ->
[i(?DIFFICULTY)];
assemble_expr(_Funs, _Stack, _Tail, prim_gas_limit) ->
[i(?GASLIMIT)];
assemble_expr(Funs, Stack, _Tail, #prim_balance{ address = Addr }) ->
[assemble_expr(Funs, Stack, nontail, Addr),
i(?BALANCE)];
assemble_expr(Funs, Stack, _Tail, #prim_block_hash{ height = Height }) ->
[assemble_expr(Funs, Stack, nontail, Height),
i(?BLOCKHASH)];
assemble_expr(Funs, Stack, _Tail,
#prim_call_contract{ gas = Gas
, address = To
, value = Value
, arg = Arg
, type_hash= TypeHash
}) ->
%% ?CALL takes (from the top)
%% Gas, To, Value, Arg, TypeHash, _OOffset,_OSize
%% So assemble these in reverse order.
[ assemble_exprs(Funs, Stack, [ {integer, 0}, {integer, 0}, TypeHash
, Arg, Value, To, Gas ])
, i(?CALL)
].
assemble_exprs(_Funs, _Stack, []) ->
[];
assemble_exprs(Funs, Stack, [E|Es]) ->
[assemble_expr(Funs, Stack, nontail, E),
assemble_exprs(Funs, [dummy|Stack], Es)].
assemble_decision(Funs, Stack, {binop, '&&', A, B}, Then, Else) ->
Label = make_ref(),
[assemble_decision(Funs, Stack, A, Label, Else),
jumpdest(Label),
assemble_decision(Funs, Stack, B, Then, Else)];
assemble_decision(Funs, Stack, {binop, '||', A, B}, Then, Else) ->
Label = make_ref(),
[assemble_decision(Funs, Stack, A, Then, Label),
jumpdest(Label),
assemble_decision(Funs, Stack, B, Then, Else)];
assemble_decision(Funs, Stack, {unop, '!', A}, Then, Else) ->
assemble_decision(Funs, Stack, A, Else, Then);
assemble_decision(Funs, Stack, {ifte, A, B, C}, Then, Else) ->
TrueL = make_ref(),
FalseL = make_ref(),
[assemble_decision(Funs, Stack, A, TrueL, FalseL),
jumpdest(TrueL), assemble_decision(Funs, Stack, B, Then, Else),
jumpdest(FalseL), assemble_decision(Funs, Stack, C, Then, Else)];
assemble_decision(Funs, Stack, Decision, Then, Else) ->
[assemble_expr(Funs, Stack, nontail, Decision),
jump_if(Then), jump(Else)].
%% Entered with value to switch on on top of the stack
%% Evaluate selected case, then jump to Close with result on the
%% stack.
assemble_cases(_Funs, _Stack, _Tail, _Close, []) ->
%% No match! What should be do? There's no real way to raise an
%% exception, except consuming all the gas.
%% There should not be enough gas to do this:
[push(1), i(?NOT),
i(?MLOAD),
%% now stop, so that jump optimizer realizes we will not fall
%% through this code.
i(?STOP)];
assemble_cases(Funs, Stack, Tail, Close, [{Pattern, Body}|Cases]) ->
Succeed = make_ref(),
Fail = make_ref(),
{NewVars, MatchingCode} =
assemble_pattern(Succeed, Fail, Pattern),
%% In the code that follows, if this is NOT the last case, then we
%% save the value being switched on, and discard it on
%% success. The code is simpler if this IS the last case.
[[dup(1) || Cases /= []], %% save value for next case, if there is one
MatchingCode,
jumpdest(Succeed),
%% Discard saved value, if we saved one
[case NewVars of
[] ->
pop(1);
[_] ->
%% Special case for peep-hole optimization
pop_args(1);
_ ->
[swap(length(NewVars)), pop(1)]
end
|| Cases/=[]],
assemble_expr(Funs,
case Cases of
[] -> NewVars;
_ -> reorder_vars(NewVars)
end
++Stack, Tail, Body),
%% If the Body makes a tail call, then we will not return
%% here--but it doesn't matter, because
%% (a) the NewVars will be popped before the tailcall
%% (b) the code below will be deleted since it is dead
pop_args(length(NewVars)),
jump(Close),
jumpdest(Fail),
assemble_cases(Funs, Stack, Tail, Close, Cases)].
%% Entered with value to match on top of the stack.
%% Generated code removes value, and
%% - jumps to Fail if no match, or
%% - binds variables, leaves them on the stack, and jumps to Succeed
%% Result is a list of variables to add to the stack, and the matching
%% code.
assemble_pattern(Succeed, Fail, {integer, N}) ->
{[], [push(N),
i(?EQ),
jump_if(Succeed),
jump(Fail)]};
assemble_pattern(Succeed, _Fail, {var_ref, "_"}) ->
{[], [i(?POP), jump(Succeed)]};
assemble_pattern(Succeed, Fail, {missing_field, _, _}) ->
%% Missing record fields are quite ok in patterns.
assemble_pattern(Succeed, Fail, {var_ref, "_"});
assemble_pattern(Succeed, _Fail, {var_ref, Id}) ->
{[{Id, "_"}], jump(Succeed)};
assemble_pattern(Succeed, _Fail, {tuple, []}) ->
{[], [pop(1), jump(Succeed)]};
assemble_pattern(Succeed, Fail, {tuple, [A]}) ->
%% Treat this case specially, because we don't need to save the
%% pointer to the tuple.
{AVars, ACode} = assemble_pattern(Succeed, Fail, A),
{AVars, [i(?MLOAD),
ACode]};
assemble_pattern(Succeed, Fail, {tuple, [A|B]}) ->
%% Entered with the address of the tuple on the top of the
%% stack. We will duplicate the address before matching on A.
Continue = make_ref(), %% the label for matching B
Pop1Fail = make_ref(), %% pop 1 word and goto Fail
PopNFail = make_ref(), %% pop length(AVars) words and goto Fail
{AVars, ACode} =
assemble_pattern(Continue, Pop1Fail, A),
{BVars, BCode} =
assemble_pattern(Succeed, PopNFail, {tuple, B}),
{BVars ++ reorder_vars(AVars),
[%% duplicate the pointer so we don't lose it when we match on A
dup(1),
i(?MLOAD),
ACode,
jumpdest(Continue),
%% Bring the pointer to the top of the stack--this reorders AVars!
swap(length(AVars)),
push(32),
i(?ADD),
BCode,
case AVars of
[] ->
[jumpdest(Pop1Fail), pop(1),
jumpdest(PopNFail),
jump(Fail)];
_ ->
[{'JUMPDEST', PopNFail}, pop(length(AVars)-1),
{'JUMPDEST', Pop1Fail}, pop(1),
{push_label, Fail}, 'JUMP']
end]};
assemble_pattern(Succeed, Fail, {list, []}) ->
%% [] is represented by -1.
{[], [push(1),
i(?ADD),
jump_if(Fail),
jump(Succeed)]};
assemble_pattern(Succeed, Fail, {list, [A|B]}) ->
assemble_pattern(Succeed, Fail, {binop, '::', A, {list, B}});
assemble_pattern(Succeed, Fail, {binop, '::', A, B}) ->
%% Make sure it's not [], then match as tuple.
NotNil = make_ref(),
{Vars, Code} = assemble_pattern(Succeed, Fail, {tuple, [A, B]}),
{Vars, [dup(1), push(1), i(?ADD), %% Check for [] without consuming the value
jump_if(NotNil), %% so it's still there when matching the tuple.
pop(1), %% It was [] so discard the saved value.
jump(Fail),
jumpdest(NotNil),
Code]}.
%% When Vars are on the stack, with a value we want to discard
%% below them, then we swap the top variable with that value and pop.
%% This reorders the variables on the stack, as follows:
reorder_vars([]) ->
[];
reorder_vars([V|Vs]) ->
Vs ++ [V].
assemble_prefix('sha3') -> [i(?DUP1), i(?MLOAD), %% length, ptr
i(?SWAP1), push(32), i(?ADD), %% ptr+32, length
i(?SHA3)];
assemble_prefix('-') -> [push(0), i(?SUB)];
assemble_prefix('bnot') -> i(?NOT).
assemble_infix('+') -> i(?ADD);
assemble_infix('-') -> i(?SUB);
assemble_infix('*') -> i(?MUL);
assemble_infix('/') -> i(?SDIV);
assemble_infix('div') -> i(?DIV);
assemble_infix('mod') -> i(?MOD);
assemble_infix('^') -> i(?EXP);
assemble_infix('bor') -> i(?OR);
assemble_infix('band') -> i(?AND);
assemble_infix('bxor') -> i(?XOR);
assemble_infix('bsl') -> i(?SHL);
assemble_infix('bsr') -> i(?SHR);
assemble_infix('<') -> i(?SLT); %% comparisons are SIGNED
assemble_infix('>') -> i(?SGT);
assemble_infix('==') -> i(?EQ);
assemble_infix('<=') -> [i(?SGT), i(?ISZERO)];
assemble_infix('=<') -> [i(?SGT), i(?ISZERO)];
assemble_infix('>=') -> [i(?SLT), i(?ISZERO)];
assemble_infix('!=') -> [i(?EQ), i(?ISZERO)];
assemble_infix('!') -> [i(?ADD), i(?MLOAD)];
assemble_infix('byte') -> i(?BYTE).
%% assemble_infix('::') -> [i(?MSIZE), write_word(0), write_word(1)].
%% a function may either refer to a top-level function, in which case
%% we fetch the code label from Funs, or it may be a lambda-expression
%% (including a top-level function passed as a parameter). In the
%% latter case, the function value is a pointer to a tuple of the code
%% pointer and the free variables: we keep the pointer and push the
%% code pointer onto the stack. In either case, we are ready to enter
%% the function with JUMP.
assemble_function(Funs, Stack, Fun) ->
case is_top_level_fun(Stack, Fun) of
true ->
{var_ref, Name} = Fun,
{push_label, lookup_fun(Funs, Name)};
false ->
[assemble_expr(Funs, Stack, nontail, Fun),
dup(1),
i(?MLOAD)]
end.
free_vars(V={var_ref, _}) ->
[V];
free_vars({switch, E, Cases}) ->
lists:umerge(free_vars(E),
lists:umerge([free_vars(Body)--free_vars(Pattern)
|| {Pattern, Body} <- Cases]));
free_vars({lambda, Args, Body}) ->
free_vars(Body) -- [{var_ref, Arg#arg.name} || Arg <- Args];
free_vars(T) when is_tuple(T) ->
free_vars(tuple_to_list(T));
free_vars([H|T]) ->
lists:umerge(free_vars(H), free_vars(T));
free_vars(_) ->
[].
%% shuffle_stack reorders the stack, for example before a tailcall. It is called
%% with a description of the current stack, and how the final stack
%% should appear. The argument is a list containing
%% a NUMBER for each element that should be kept, the number being
%% the position this element should occupy in the final stack
%% discard, for elements that can be discarded.
%% The positions start at 1, referring to the variable to be placed at
%% the bottom of the stack, and ranging up to the size of the final stack.
shuffle_stack([]) ->
[];
shuffle_stack([discard|Stack]) ->
[i(?POP) | shuffle_stack(Stack)];
shuffle_stack([N|Stack]) ->
case length(Stack) + 1 - N of
0 ->
%% the job should be finished
CorrectStack = lists:seq(N - 1, 1, -1),
CorrectStack = Stack,
[];
MoveBy ->
{Pref, [_|Suff]} = lists:split(MoveBy - 1, Stack),
[swap(MoveBy) | shuffle_stack([lists:nth(MoveBy, Stack) | Pref ++ [N|Suff]])]
end.
lookup_fun(Funs, Name) ->
case [Ref || {Name1, _, Ref} <- Funs,
Name == Name1] of
[Ref] -> Ref;
[] -> gen_error({undefined_function, Name})
end.
is_top_level_fun(Stack, {var_ref, Id}) ->
not lists:keymember(Id, 1, Stack);
is_top_level_fun(_, _) ->
false.
lookup_var(Id, Stack) ->
lookup_var(1, Id, Stack).
lookup_var(N, Id, [{Id, _Type}|_]) ->
N;
lookup_var(N, Id, [_|Stack]) ->
lookup_var(N + 1, Id, Stack);
lookup_var(_, Id, []) ->
gen_error({var_not_in_scope, Id}).
%% Smart instruction generation
%% TODO: handle references to the stack beyond depth 16. Perhaps the
%% best way is to repush variables that will be needed in
%% subexpressions before evaluating he subexpression... i.e. fix the
%% problem in assemble_expr, rather than here. A fix here would have
%% to save the top elements of the stack in memory, duplicate the
%% targetted element, and then repush the values from memory.
dup(N) when 1 =< N, N =< 16 ->
i(?DUP1 + N - 1).
push(N) ->
Bytes = binary:encode_unsigned(N),
true = size(Bytes) =< 32,
[i(?PUSH1 + size(Bytes) - 1) |
binary_to_list(Bytes)].
%% Pop N values from UNDER the top element of the stack.
%% This is a pseudo-instruction so peephole optimization can
%% combine pop_args(M), pop_args(N) to pop_args(M+N)
pop_args(0) ->
[];
pop_args(N) ->
{pop_args, N}.
%% [swap(N), pop(N)].
pop(N) ->
[i(?POP) || _ <- lists:seq(1, N)].
swap(0) ->
%% Doesn't exist, but is logically a no-op.
[];
swap(N) when 1 =< N, N =< 16 ->
i(?SWAP1 + N - 1).
jumpdest(Label) -> {i(?JUMPDEST), Label}.
push_label(Label) -> {push_label, Label}.
jump(Label) -> [push_label(Label), i(?JUMP)].
jump_if(Label) -> [push_label(Label), i(?JUMPI)].
%% ICode utilities (TODO: move to separate module)
icode_noname() -> #var_ref{name = "_"}.
icode_seq([A]) -> A;
icode_seq([A | As]) ->
icode_seq(A, icode_seq(As)).
icode_seq(A, B) ->
#switch{ expr = A, cases = [{icode_noname(), B}] }.
%% Stack: <N elements> ADDR
%% Write elements at addresses ADDR, ADDR+32, ADDR+64...
%% Stack afterwards: ADDR
% write_words(N) ->
% [write_word(I) || I <- lists:seq(N-1, 0, -1)].
%% Unused at the moment. Comment out to please dialyzer.
%% write_word(I) ->
%% [%% Stack: elements e ADDR
%% swap(1),
%% dup(2),
%% %% Stack: elements ADDR e ADDR
%% push(32*I),
%% i(?ADD),
%% %% Stack: elements ADDR e ADDR+32I
%% i(?MSTORE)].
%% Resolve references, and convert code from deep list to flat list.
%% List elements are:
%% Opcodes
%% Byte values
%% {'JUMPDEST', Ref} -- assembles to ?JUMPDEST and sets Ref
%% {push_label, Ref} -- assembles to ?PUSHN address bytes
%% For now, we assemble all code addresses as three bytes.
resolve_references(Code) ->
Peephole = peep_hole(lists:flatten(Code)),
%% WARNING: Optimizing jumps reorders the code and deletes
%% instructions. When debugging the assemble_ functions, it can be
%% useful to replace the next line by:
%% Instrs = lists:flatten(Code),
%% thus disabling the optimization.
OptimizedJumps = optimize_jumps(Peephole),
Instrs = lists:reverse(peep_hole_backwards(lists:reverse(OptimizedJumps))),
Labels = define_labels(0, Instrs),
lists:flatten([use_labels(Labels, I) || I <- Instrs]).
define_labels(Addr, [{'JUMPDEST', Lab}|More]) ->
[{Lab, Addr}|define_labels(Addr + 1, More)];
define_labels(Addr, [{push_label, _}|More]) ->
define_labels(Addr + 4, More);
define_labels(Addr, [{pop_args, N}|More]) ->
define_labels(Addr + N + 1, More);
define_labels(Addr, [_|More]) ->
define_labels(Addr + 1, More);
define_labels(_, []) ->
[].
use_labels(_, {'JUMPDEST', _}) ->
'JUMPDEST';
use_labels(Labels, {push_label, Ref}) ->
case proplists:get_value(Ref, Labels) of
undefined ->
gen_error({undefined_label, Ref});
Addr when is_integer(Addr) ->
[i(?PUSH3),
Addr div 65536, (Addr div 256) rem 256, Addr rem 256]
end;
use_labels(_, {pop_args, N}) ->
[swap(N), pop(N)];
use_labels(_, I) ->
I.
%% Peep-hole optimization.
%% The compilation of conditionals can introduce jumps depending on
%% constants 1 and 0. These are removed by peep-hole optimization.
peep_hole(['PUSH1', 0, {push_label, _}, 'JUMPI'|More]) ->
peep_hole(More);
peep_hole(['PUSH1', 1, {push_label, Lab}, 'JUMPI'|More]) ->
[{push_label, Lab}, 'JUMP'|peep_hole(More)];
peep_hole([{pop_args, M}, {pop_args, N}|More]) when M + N =< 16 ->
peep_hole([{pop_args, M + N}|More]);
peep_hole([I|More]) ->
[I|peep_hole(More)];
peep_hole([]) ->
[].
%% Peep-hole optimization on reversed instructions lists.
peep_hole_backwards(Code) ->
NewCode = peep_hole_backwards1(Code),
if Code == NewCode -> Code;
true -> peep_hole_backwards(NewCode)
end.
peep_hole_backwards1(['ADD', 0, 'PUSH1'|Code]) ->
peep_hole_backwards1(Code);
peep_hole_backwards1(['POP', UnOp|Code]) when UnOp=='MLOAD';UnOp=='ISZERO';UnOp=='NOT' ->
peep_hole_backwards1(['POP'|Code]);
peep_hole_backwards1(['POP', BinOp|Code]) when
%% TODO: more binary operators
BinOp=='ADD';BinOp=='SUB';BinOp=='MUL';BinOp=='SDIV' ->
peep_hole_backwards1(['POP', 'POP'|Code]);
peep_hole_backwards1(['POP', _, 'PUSH1'|Code]) ->
peep_hole_backwards1(Code);
peep_hole_backwards1([I|Code]) ->
[I|peep_hole_backwards1(Code)];
peep_hole_backwards1([]) ->
[].
%% Jump optimization:
%% Replaces a jump to a jump with a jump to the final destination
%% Moves basic blocks to eliminate an unconditional jump to them.
%% The compilation of conditionals generates a lot of labels and
%% jumps, some of them unnecessary. This optimization phase reorders
%% code so that as many jumps as possible can be eliminated, and
%% replaced by just falling through to the destination label. This
%% both optimizes the code generated by conditionals, and converts one
%% call of a function into falling through into its code--so it
%% reorders code quite aggressively. Function returns are indirect
%% jumps, however, and are never optimized away.
%% IMPORTANT: since execution begins at address zero, then the first
%% block of code must never be moved elsewhere. The code below has
%% this property, because it processes blocks from left to right, and
%% because the first block does not begin with a label, and so can
%% never be jumped to--hence no code can be inserted before it.
%% The optimization works by taking one block of code at a time, and
%% then prepending blocks that jump directly to it, and appending
%% blocks that it jumps directly to, resulting in a jump-free sequence
%% that is as long as possible. To do so, we store blocks in the form
%% {OptionalLabel, Body, OptionalJump} which represents the code block
%% OptionalLabel++Body++OptionalJump; the optional parts are the empty
%% list of instructions if not present. Two blocks can be merged if
%% the first ends in an OptionalJump to the OptionalLabel beginning
%% the second; the OptionalJump can then be removed (and the
%% OptionalLabel if there are no other references to it--this happens
%% during dead code elimination.
%% TODO: the present implementation is QUADRATIC, because we search
%% repeatedly for matching blocks to merge with the first one, storing
%% the blocks in a list. A near linear time implementation could use
%% two ets tables, one keyed on the labels, and the other keyed on the
%% final jumps.
optimize_jumps(Code) ->
JJs = jumps_to_jumps(Code),
ShortCircuited = [short_circuit_jumps(JJs, Instr) || Instr <- Code],
NoDeadCode = eliminate_dead_code(ShortCircuited),
MovedCode = merge_blocks(moveable_blocks(NoDeadCode)),
%% Moving code may have made some labels superfluous.
eliminate_dead_code(MovedCode).
jumps_to_jumps([{'JUMPDEST', Label}, {push_label, Target}, 'JUMP'|More]) ->
[{Label, Target}|jumps_to_jumps(More)];
jumps_to_jumps([{'JUMPDEST', Label}, {'JUMPDEST', Target}|More]) ->
[{Label, Target}|jumps_to_jumps([{'JUMPDEST', Target}|More])];
jumps_to_jumps([_|More]) ->
jumps_to_jumps(More);
jumps_to_jumps([]) ->
[].
short_circuit_jumps(JJs, {push_label, Lab}) ->
case proplists:get_value(Lab, JJs) of
undefined ->
{push_label, Lab};
Target ->
%% I wonder if this will ever loop infinitely?
short_circuit_jumps(JJs, {push_label, Target})
end;
short_circuit_jumps(_JJs, Instr) ->
Instr.
eliminate_dead_code(Code) ->
Jumps = lists:usort([Lab || {push_label, Lab} <- Code]),
NewCode = live_code(Jumps, Code),
if Code==NewCode ->
Code;
true ->
eliminate_dead_code(NewCode)
end.
live_code(Jumps, ['JUMP'|More]) ->
['JUMP'|dead_code(Jumps, More)];
live_code(Jumps, ['STOP'|More]) ->
['STOP'|dead_code(Jumps, More)];
live_code(Jumps, [{'JUMPDEST', Lab}|More]) ->
case lists:member(Lab, Jumps) of
true ->
[{'JUMPDEST', Lab}|live_code(Jumps, More)];
false ->
live_code(Jumps, More)
end;
live_code(Jumps, [I|More]) ->
[I|live_code(Jumps, More)];
live_code(_, []) ->
[].
dead_code(Jumps, [{'JUMPDEST', Lab}|More]) ->
case lists:member(Lab, Jumps) of
true ->
[{'JUMPDEST', Lab}|live_code(Jumps, More)];
false ->
dead_code(Jumps, More)
end;
dead_code(Jumps, [_I|More]) ->
dead_code(Jumps, More);
dead_code(_, []) ->
[].
%% Split the code into "moveable blocks" that control flow only
%% reaches via jumps.
moveable_blocks([]) ->
[];
moveable_blocks([I]) ->
[[I]];
moveable_blocks([Jump|More]) when Jump=='JUMP'; Jump=='STOP' ->
[[Jump]|moveable_blocks(More)];
moveable_blocks([I|More]) ->
[Block|MoreBlocks] = moveable_blocks(More),
[[I|Block]|MoreBlocks].
%% Merge blocks to eliminate jumps where possible.
merge_blocks(Blocks) ->
BlocksAndTargets = [label_and_jump(B) || B <- Blocks],
[I || {Pref, Body, Suff} <- merge_after(BlocksAndTargets),
I <- Pref++Body++Suff].
%% Merge the first block with other blocks that come after it
merge_after(All=[{Label, Body, [{push_label, Target}, 'JUMP']}|BlocksAndTargets]) ->
case [{B, J} || {[{'JUMPDEST', L}], B, J} <- BlocksAndTargets,
L == Target] of
[{B, J}|_] ->
merge_after([{Label, Body ++ [{'JUMPDEST', Target}] ++ B, J}|
lists:delete({[{'JUMPDEST', Target}], B, J},
BlocksAndTargets)]);
[] ->
merge_before(All)
end;
merge_after(All) ->
merge_before(All).
%% The first block cannot be merged with any blocks that it jumps
%% to... but maybe it can be merged with a block that jumps to it!
merge_before([Block={[{'JUMPDEST', Label}], Body, Jump}|BlocksAndTargets]) ->
case [{L, B, T} || {L, B, [{push_label, T}, 'JUMP']} <- BlocksAndTargets,
T == Label] of
[{L, B, T}|_] ->
merge_before([{L, B ++ [{'JUMPDEST', Label}] ++ Body, Jump}
|lists:delete({L, B, [{push_label, T}, 'JUMP']}, BlocksAndTargets)]);
_ ->
[Block | merge_after(BlocksAndTargets)]
end;
merge_before([Block|BlocksAndTargets]) ->
[Block | merge_after(BlocksAndTargets)];
merge_before([]) ->
[].
%% Convert each block to a PREFIX, which is a label or empty, a
%% middle, and a SUFFIX which is a JUMP to a label, or empty.
label_and_jump(B) ->
{Label, B1} = case B of
[{'JUMPDEST', L}|More1] ->
{[{'JUMPDEST', L}], More1};
_ ->
{[], B}
end,
{Target, B2} = case lists:reverse(B1) of
['JUMP', {push_label, T}|More2] ->
{[{push_label, T}, 'JUMP'], lists:reverse(More2)};
_ ->
{[], B1}
end,
{Label, B2, Target}.
+9 -1
View File
@@ -15,7 +15,8 @@
many/1, many1/1, sep/2, sep1/2,
infixl/2, infixr/2]).
-export([current_file/0, set_current_file/1]).
-export([current_file/0, set_current_file/1,
current_include_type/0, set_current_include_type/1]).
%% -- Types ------------------------------------------------------------------
@@ -465,6 +466,13 @@ merge_with(Fun, Map1, Map2) ->
end, Map2, maps:to_list(Map1))
end.
%% Current include type
current_include_type() ->
get('$current_include_type').
set_current_include_type(IncludeType) ->
put('$current_include_type', IncludeType).
%% Current source file
current_file() ->
get('$current_file').
+46 -9
View File
@@ -18,7 +18,8 @@
run_parser/3]).
-include("aeso_parse_lib.hrl").
-import(aeso_parse_lib, [current_file/0, set_current_file/1]).
-import(aeso_parse_lib, [current_file/0, set_current_file/1,
current_include_type/0, set_current_include_type/1]).
-type parse_result() :: aeso_syntax:ast() | {aeso_syntax:ast(), sets:set(include_hash())} | none().
@@ -57,6 +58,7 @@ run_parser(P, Inp, Opts) ->
parse_and_scan(P, S, Opts) ->
set_current_file(proplists:get_value(src_file, Opts, no_file)),
set_current_include_type(proplists:get_value(include_type, Opts, none)),
case aeso_scan:scan(S) of
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
{error, {{Input, Pos}, _}} ->
@@ -132,9 +134,19 @@ fun_block(Mods, Kind, [Decl]) ->
fun_block(Mods, Kind, Decls) ->
{block, get_ann(Kind), [ add_modifiers(Mods, Kind, Decl) || Decl <- Decls ]}.
typevar_constraint() ->
?RULE(tvar(), keyword(is), id(), {constraint, get_ann(_1), _1, _3}).
typevars_constraints() ->
?RULE(comma_sep1(typevar_constraint()), tok(';'), _1).
fundecl() ->
choice([?RULE(id(), tok(':'), typevars_constraints(), type(),
{fun_decl, get_ann(_1), _1, {constrained_t, get_ann(_1), _3, _4}}),
?RULE(id(), tok(':'), type(), {fun_decl, get_ann(_1), _1, _3})]).
fundef_or_decl() ->
choice([?RULE(id(), tok(':'), type(), {fun_decl, get_ann(_1), _1, _3}),
fundef()]).
choice([fundecl(), fundef()]).
using() ->
Alias = {keyword(as), con()},
@@ -306,17 +318,18 @@ expr() -> expr100().
expr100() ->
Expr100 = ?LAZY_P(expr100()),
Expr200 = ?LAZY_P(expr200()),
Expr150 = ?LAZY_P(expr150()),
choice(
[ ?RULE(lam_args(), keyword('=>'), body(), {lam, _2, _1, _3}) %% TODO: better location
, {'if', keyword('if'), parens(Expr100), Expr200, right(tok(else), Expr100)}
, ?RULE(Expr200, optional(right(tok(':'), type())),
, {'if', keyword('if'), parens(Expr100), Expr150, right(tok(else), Expr100)}
, ?RULE(Expr150, optional(right(tok(':'), type())),
case _2 of
none -> _1;
{ok, Type} -> {typed, get_ann(_1), _1, Type}
end)
]).
expr150() -> infixl(expr200(), binop('|>')).
expr200() -> infixr(expr300(), binop('||')).
expr300() -> infixr(expr400(), binop('&&')).
expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])).
@@ -332,7 +345,7 @@ exprAtom() ->
?LAZY_P(begin
Expr = ?LAZY_P(expr()),
choice(
[ id_or_addr(), con(), token(qid), token(qcon)
[ id_or_addr(), con(), token(qid), token(qcon), binop_as_lam()
, token(bytes), token(string), token(char)
, token(int)
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
@@ -469,6 +482,19 @@ id() -> token(id).
tvar() -> token(tvar).
str() -> token(string).
binop_as_lam() ->
BinOps = ['&&', '||',
'+', '-', '*', '/', '^', 'mod',
'==', '!=', '<', '>', '<=', '=<', '>=',
'::', '++', '|>'],
OpToLam = fun(Op = {_, Ann}) ->
IdL = {id, Ann, "l"},
IdR = {id, Ann, "r"},
Arg = fun(Id) -> {arg, Ann, Id, type_wildcard(Ann)} end,
{lam, Ann, [Arg(IdL), Arg(IdR)], infix(IdL, Op, IdR)}
end,
?RULE(parens(choice(lists:map(fun token/1, BinOps))), OpToLam(_1)).
token(Tag) ->
?RULE(tok(Tag),
case _1 of
@@ -511,6 +537,7 @@ parens(P) -> between(tok('('), P, tok(')')).
braces(P) -> between(tok('{'), P, tok('}')).
brackets(P) -> between(tok('['), P, tok(']')).
comma_sep(P) -> sep(P, tok(',')).
comma_sep1(P) -> sep1(P, tok(',')).
paren_list(P) -> parens(comma_sep(P)).
brace_list(P) -> braces(comma_sep(P)).
@@ -523,7 +550,11 @@ bracket_list(P) -> brackets(comma_sep(P)).
-type ann_col() :: aeso_syntax:ann_col().
-spec pos_ann(ann_line(), ann_col()) -> ann().
pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}].
pos_ann(Line, Col) ->
[ {file, current_file()}
, {include_type, current_include_type()}
, {line, Line}
, {col, Col} ].
ann_pos(Ann) ->
{proplists:get_value(file, Ann),
@@ -665,9 +696,15 @@ expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Op
Hashed = hash_include(File, Code),
case sets:is_element(Hashed, Included) of
false ->
SrcFile = proplists:get_value(src_file, Opts, no_file),
IncludeType = case proplists:get_value(file, Ann) of
SrcFile -> direct;
_ -> indirect
end,
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
Opts2 = lists:keystore(include_type, 1, Opts1, {include_type, IncludeType}),
Included1 = sets:add_element(Hashed, Included),
case parse_and_scan(file(), Code, Opts1) of
case parse_and_scan(file(), Code, Opts2) of
{ok, AST1} ->
expand_includes(AST1 ++ AST, Included1, Acc, Opts);
Err = {error, _} ->
+3 -1
View File
@@ -284,7 +284,9 @@ type(T = {id, _, _}) -> name(T);
type(T = {qid, _, _}) -> name(T);
type(T = {con, _, _}) -> name(T);
type(T = {qcon, _, _}) -> name(T);
type(T = {tvar, _, _}) -> name(T).
type(T = {tvar, _, _}) -> name(T);
type({constrained_t, _, Cs, T}) ->
beside([name(T), text(" is "), tuple(lists:map(fun expr/1, Cs))]).
-spec args_type([aeso_syntax:type()]) -> doc().
args_type(Args) ->
+1 -1
View File
@@ -45,7 +45,7 @@ lexer() ->
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace",
"interface", "main", "using", "as", "for", "hiding"
"interface", "main", "using", "as", "for", "hiding", "is"
],
KW = string:join(Keywords, "|"),
+3
View File
@@ -79,11 +79,14 @@
-type constructor_t() :: {constr_t, ann(), con(), [type()]}.
-type tvar_constraint() :: {constraint, ann(), tvar(), id()}.
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
| {app_t, ann(), type(), [type()]}
| {tuple_t, ann(), [type()]}
| {args_t, ann(), [type()]} %% old tuple syntax, old for error messages
| {bytes_t, ann(), integer() | any}
| {constrained_t, ann(), [tvar_constraint()], type()}
| id() | qid()
| con() | qcon() %% contracts
| tvar().
+1
View File
@@ -61,6 +61,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{fun_t, _, Named, Args, Ret} -> Type([Named, Args, Ret]);
{app_t, _, T, Ts} -> Type([T | Ts]);
{tuple_t, _, Ts} -> Type(Ts);
{constrained_t, _, _, T} -> Type(T);
%% named_arg_t()
{named_arg_t, _, _, T, E} -> Plus(Type(T), Expr(E));
%% expr()
+9 -62
View File
@@ -1,75 +1,15 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc Decoding aevm and fate data to AST
%%%
%%% @doc Decoding fate data to AST
%%% @end
%%%-------------------------------------------------------------------
-module(aeso_vm_decode).
-export([ from_aevm/3, from_fate/2 ]).
-export([ from_fate/2 ]).
-include_lib("aebytecode/include/aeb_fate_data.hrl").
address_literal(Type, N) -> {Type, [], <<N:256>>}.
-spec from_aevm(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
from_aevm(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
from_aevm(word, {id, _, "int"}, N0) ->
<<N:256/signed>> = <<N0:256>>,
if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]};
true -> {int, [], N} end;
from_aevm(word, {id, _, "bits"}, N0) ->
<<N:256/signed>> = <<N0:256>>,
make_bits(N);
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
<<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>,
{bytes, [], <<Bytes:Len/unit:8>>};
from_aevm({tuple, _}, {bytes_t, _, Len}, Val) ->
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
from_aevm(string, {id, _, "string"}, S) -> {string, [], S};
from_aevm({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
{list, [], [from_aevm(VmType, Type, X) || X <- List]};
from_aevm({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, 0, []} -> {con, [], "None"};
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [from_aevm(VmType, Type, X)]}
end;
from_aevm({tuple, VmTypes}, {tuple_t, _, Types}, Val)
when length(VmTypes) == length(Types),
length(VmTypes) == tuple_size(Val) ->
{tuple, [], [from_aevm(VmType, Type, X)
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
from_aevm({tuple, VmTypes}, {record_t, Fields}, Val)
when length(VmTypes) == length(Fields),
length(VmTypes) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], from_aevm(VmType, FType, X)}
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
from_aevm({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {from_aevm(VmKeyType, KeyType, Key),
from_aevm(VmValType, ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
from_aevm({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
when length(VmCons) == length(Cons),
length(VmCons) > Tag ->
VmTypes = lists:nth(Tag + 1, VmCons),
ConType = lists:nth(Tag + 1, Cons),
from_aevm(VmTypes, ConType, Args);
from_aevm([], {constr_t, _, Con, []}, []) -> Con;
from_aevm(VmTypes, {constr_t, _, Con, Types}, Args)
when length(VmTypes) == length(Types),
length(VmTypes) == length(Args) ->
{app, [], Con, [ from_aevm(VmType, Type, Arg)
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
from_aevm(_VmType, _Type, _Data) ->
throw(cannot_translate_to_sophia).
-spec from_fate(aeso_syntax:type(), aeb_fate_data:fate_type()) -> aeso_syntax:expr().
from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin};
from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin};
@@ -136,6 +76,8 @@ from_fate_builtin(QType, Val) ->
Str = {id, [], "string"},
Adr = {id, [], "address"},
Hsh = {bytes_t, [], 32},
I32 = {bytes_t, [], 32},
I48 = {bytes_t, [], 48},
Qid = fun(Name) -> {qid, [], Name} end,
Map = fun(KT, VT) -> {app_t, [], {id, [], "map"}, [KT, VT]} end,
ChainTxArities = [3, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
@@ -208,6 +150,11 @@ from_fate_builtin(QType, Val) ->
{["Chain", "base_tx"], {variant, ChainTxArities, 21, {}}} ->
App(["Chain","GAAttachTx"], []);
{["MCL_BLS12_381", "fp"], X} ->
App(["MCL_BLS12_381", "fp"], [Chk(I32, X)]);
{["MCL_BLS12_381", "fr"], X} ->
App(["MCL_BLS12_381", "fr"], [Chk(I48, X)]);
_ ->
throw(cannot_translate_to_sophia)
end.
+31
View File
@@ -0,0 +1,31 @@
-module(aeso_warnings).
-record(warn, { pos :: aeso_errors:pos()
, message :: iolist()
}).
-opaque warning() :: #warn{}.
-export_type([warning/0]).
-export([ new/1
, new/2
, warn_to_err/2
, sort_warnings/1
, pp/1
]).
new(Msg) ->
new(aeso_errors:pos(0, 0), Msg).
new(Pos, Msg) ->
#warn{ pos = Pos, message = Msg }.
warn_to_err(Kind, #warn{ pos = Pos, message = Msg }) ->
aeso_errors:new(Kind, Pos, lists:flatten(Msg)).
sort_warnings(Warnings) ->
lists:sort(fun(W1, W2) -> W1#warn.pos =< W2#warn.pos end, Warnings).
pp(#warn{ pos = Pos, message = Msg }) ->
lists:flatten(io_lib:format("Warning~s:\n~s", [aeso_errors:pp_pos(Pos), Msg])).
+78 -90
View File
@@ -5,7 +5,6 @@
-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) ->
@@ -20,12 +19,6 @@ sandbox(Code) ->
{error, loop}
end.
malicious_from_binary_test() ->
CircularList = from_words([32, 1, 32]), %% Xs = 1 :: Xs
{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) ->
<< <<(from_word(W))/binary>> || W <- Ws >>.
@@ -37,23 +30,14 @@ from_word(S) when is_list(S) ->
<<Len:256, Bin/binary>>.
encode_decode_test() ->
encode_decode(word, 42),
42 = encode_decode(word, 42),
-1 = encode_decode(signed_word, -1),
<<"Hello world">> = encode_decode(string, <<"Hello world">>),
{} = encode_decode({tuple, []}, {}),
{42} = encode_decode({tuple, [word]}, {42}),
{42, 0} = encode_decode({tuple, [word, word]}, {42, 0}),
[] = encode_decode({list, word}, []),
[32] = encode_decode({list, word}, [32]),
none = encode_decode({option, word}, none),
{some, 1} = encode_decode({option, word}, {some, 1}),
string = encode_decode(typerep, string),
word = encode_decode(typerep, word),
{list, word} = encode_decode(typerep, {list, word}),
{tuple, [word]} = encode_decode(typerep, {tuple, [word]}),
1 = encode_decode(word, 1),
0 = encode_decode(word, 0),
Tests =
[42, 1, 0 -1, <<"Hello">>,
{tuple, {}}, {tuple, {42}}, {tuple, {21, 37}},
[], [42], [21, 37],
{variant, [0, 1], 0, {}}, {variant, [0, 1], 1, {42}}, {variant, [2], 0, {21, 37}},
{typerep, string}, {typerep, integer}, {typerep, {list, integer}}, {typerep, {tuple, [integer]}}
],
[?assertEqual(Test, encode_decode(Test)) || Test <- Tests],
ok.
encode_decode_sophia_test() ->
@@ -72,55 +56,67 @@ encode_decode_sophia_test() ->
ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
ok.
to_sophia_value_mcl_bls12_381_test() ->
Code = "include \"BLS12_381.aes\"\n"
"contract C =\n"
" entrypoint test_bls12_381_fp(x : int) = BLS12_381.int_to_fp(x)\n"
" entrypoint test_bls12_381_fr(x : int) = BLS12_381.int_to_fr(x)\n"
" entrypoint test_bls12_381_g1(x : int) = BLS12_381.mk_g1(x, x, x)\n",
Opts = [{backend, fate}],
CallValue32 = aeb_fate_encoding:serialize({bytes, <<20:256>>}),
CallValue48 = aeb_fate_encoding:serialize({bytes, <<55:384>>}),
CallValueTp = aeb_fate_encoding:serialize({tuple, {{bytes, <<15:256>>}, {bytes, <<160:256>>}, {bytes, <<1234:256>>}}}),
{ok, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fp", ok, CallValue32, Opts),
{error, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fp", ok, CallValue48, Opts),
{ok, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fr", ok, CallValue48, Opts),
{error, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fr", ok, CallValue32, Opts),
{ok, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_g1", ok, CallValueTp, Opts),
ok.
to_sophia_value_neg_test() ->
Code = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
" entrypoint f(x : int) : string = \"hello\"\n" ],
{error, [Err1]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12)),
?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12), [{backend, fate}]),
?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err2)),
{error, [Err1]} = aeso_compiler:to_sophia_value(Code, "f", ok, encode(12)),
?assertEqual("Data error:\nCannot translate FATE value 12\n of Sophia type string\n", aeso_errors:pp(Err1)),
{error, [Err3]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12)),
?assertEqual("Data error:\nCould not interpret the revert message\n", aeso_errors:pp(Err3)),
{error, [Err4]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12), [{backend, fate}]),
?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err4)),
{error, [Err2]} = aeso_compiler:to_sophia_value(Code, "f", revert, encode(12)),
?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err2)),
ok.
encode_calldata_neg_test() ->
Code = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
" entrypoint f(x : int) : string = \"hello\"\n" ],
ExpErr1 = "Type error at line 5, col 34:\nCannot unify int\n and bool\n"
"when checking the application at line 5, column 34 of\n"
" x : (int) => string\nto arguments\n true : bool\n",
{error, [Err1]} = aeso_compiler:create_calldata(Code, "x", ["true"]),
ExpErr1 = "Type error at line 5, col 34:\nCannot unify `int` and `bool`\n"
"when checking the application of\n"
" `f : (int) => string`\n"
"to arguments\n"
" `true : bool`\n",
{error, [Err1]} = aeso_compiler:create_calldata(Code, "f", ["true"]),
?assertEqual(ExpErr1, aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:create_calldata(Code, "x", ["true"], [{backend, fate}]),
?assertEqual(ExpErr1, aeso_errors:pp(Err2)),
ok.
decode_calldata_neg_test() ->
Code1 = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
" entrypoint f(x : int) : string = \"hello\"\n" ],
Code2 = [ "contract Foo =\n"
" entrypoint x(y : string) : int = 42\n" ],
" entrypoint f(x : string) : int = 42\n" ],
{ok, CallDataAEVM} = aeso_compiler:create_calldata(Code1, "x", ["42"]),
{ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]),
{ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "f", ["42"]),
{error, [Err1]} = aeso_compiler:decode_calldata(Code2, "x", CallDataAEVM),
?assertEqual("Data error:\nFailed to decode calldata as type {tuple,[string]}\n", aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:decode_calldata(Code2, "x", <<1,2,3>>, [{backend, fate}]),
?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err2)),
{error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE, [{backend, fate}]),
?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err3)),
{error, [Err1]} = aeso_compiler:decode_calldata(Code2, "f", <<1,2,3>>),
?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:decode_calldata(Code2, "f", CallDataFATE),
?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err2)),
{error, [Err4]} = aeso_compiler:decode_calldata(Code2, "y", CallDataAEVM),
?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err4)),
{error, [Err5]} = aeso_compiler:decode_calldata(Code2, "y", CallDataFATE, [{backend, fate}]),
?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err5)),
{error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE),
?assertEqual("Data error at line 1, col 1:\nFunction 'x' is missing in contract\n", aeso_errors:pp(Err3)),
ok.
@@ -133,8 +129,7 @@ encode_decode_sophia_string(SophiaType, String) ->
, " datatype variant = Red | Blue(map(string, int))\n"
, " entrypoint foo : arg_type => arg_type\n" ],
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], [no_code]) of
{ok, _, {[Type], _}, [Arg]} ->
io:format("Type ~p~n", [Type]),
{ok, _, [Arg]} ->
Data = encode(Arg),
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data, [no_code]) of
{ok, Sophia} ->
@@ -150,30 +145,32 @@ encode_decode_sophia_string(SophiaType, String) ->
calldata_test() ->
[42, <<"foobar">>] = encode_decode_calldata("foo", ["int", "string"], ["42", "\"foobar\""]),
Map = #{ <<"a">> => 4 },
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
[{variant, [0,1], 1, {#{ <<"a">> := 4 }}}, {tuple, {{tuple, {<<"b">>, 5}}, {variant, [0,1], 0, {}}}}] =
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
[?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"],
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
[?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] =
[{bytes, <<291:256>>}, {address, <<1110:256>>}] =
encode_decode_calldata("foo", ["bytes(32)", "address"],
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
[{bytes, <<291:256>>}, {bytes, <<291:256>>}] =
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)]]),
[119, {bytes, <<0:64/unit:8>>}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]),
[16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
[{contract, <<1110:256>>}] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
ok.
calldata_init_test() ->
encode_decode_calldata("init", ["int"], ["42"], {tuple, [typerep, word]}),
encode_decode_calldata("init", ["int"], ["42"]),
Code = parameterized_contract("foo", ["int"]),
encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}).
encode_decode_calldata_(Code, "init", []),
ok.
calldata_indent_test() ->
Test = fun(Extra) ->
Code = parameterized_contract(Extra, "foo", ["int"]),
encode_decode_calldata_(Code, "foo", ["42"], word)
encode_decode_calldata_(Code, "foo", ["42"])
end,
Test(" stateful entrypoint bla() = ()"),
Test(" type x = int"),
@@ -202,9 +199,9 @@ oracle_test() ->
"contract OracleTest =\n"
" entrypoint 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", ["ok_111111111111111111111111111111ZrdqRz9",
"oq_1111111111111111111111111111113AFEFpt5"], [no_code]),
?assertEqual({ok, "question", [{oracle, <<291:256>>}, {oracle_query, <<1110:256>>}]},
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
"oq_1111111111111111111111111111113AFEFpt5"], [no_code])),
ok.
@@ -220,35 +217,26 @@ permissive_literals_fail_test() ->
ok.
encode_decode_calldata(FunName, Types, Args) ->
encode_decode_calldata(FunName, Types, Args, word).
encode_decode_calldata(FunName, Types, Args, RetType) ->
Code = parameterized_contract(FunName, Types),
encode_decode_calldata_(Code, FunName, Args, RetType).
encode_decode_calldata_(Code, FunName, Args).
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
encode_decode_calldata_(Code, FunName, Args) ->
{ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args, []),
{ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}, no_code]),
?assertEqual(RetType, RetVMType),
CalldataType = {tuple, [word, {tuple, ArgTypes}]},
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
{ok, _, _} = aeso_compiler:check_call(Code, FunName, Args, [no_code]),
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).
{ok, FateArgs} = aeb_fate_abi:decode_calldata(FunName, Calldata),
FateArgs
end.
encode_decode(T, D) ->
?assertEqual(D, decode(T, encode(D))),
encode_decode(D) ->
?assertEqual(D, decode(encode(D))),
D.
encode(D) ->
aeb_heap:to_binary(D).
aeb_fate_encoding:serialize(D).
decode(T,B) ->
{ok, D} = aeb_heap:from_binary(T, B),
D.
decode(B) ->
aeb_fate_encoding:deserialize(B).
+1 -1
View File
@@ -108,7 +108,7 @@ aci_test_contract(Name) ->
{error, ErrorStringJ} when is_binary(ErrorStringJ) -> error(ErrorStringJ);
{error, ErrorJ} -> aeso_compiler_tests:print_and_throw(ErrorJ)
end,
case aeso_compiler:from_string(String, [{aci, json}, {backend, fate} | Opts]) of
case aeso_compiler:from_string(String, [{aci, json} | Opts]) of
{ok, #{aci := JSON1}} ->
?assertEqual(JSON, JSON1),
io:format("JSON:\n~p\n", [JSON]),
+6 -29
View File
@@ -19,19 +19,9 @@ calldata_test_() ->
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
fun() ->
ContractString = aeso_test_utils:read_contract(ContractName),
AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}]);
false -> undefined
end,
FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]);
false -> undefined
end,
FateExprs = ast_exprs(ContractString, Fun, Args),
ParsedExprs = parse_args(Fun, Args),
[ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
[ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
?assertEqual(ParsedExprs, FateExprs),
ok
end} || {ContractName, Fun, Args} <- compilable_contracts()].
@@ -42,19 +32,9 @@ calldata_aci_test_() ->
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString),
ContractACI = binary_to_list(ContractACIBin),
io:format("ACI:\n~s\n", [ContractACIBin]),
AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, aevm}]);
false -> undefined
end,
FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]);
false -> undefined
end,
FateExprs = ast_exprs(ContractACI, Fun, Args),
ParsedExprs = parse_args(Fun, Args),
[ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
[ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
?assertEqual(ParsedExprs, FateExprs),
ok
end} || {ContractName, Fun, Args} <- compilable_contracts()].
@@ -75,6 +55,8 @@ strip_ann1(L) when is_list(L) ->
lists:map(fun strip_ann/1, L);
strip_ann1(X) -> X.
ast_exprs(ContractString, Fun, Args) ->
ast_exprs(ContractString, Fun, Args, []).
ast_exprs(ContractString, Fun, Args, Opts) ->
{ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)),
{ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)),
@@ -159,8 +141,3 @@ compilable_contracts() ->
{"stub", "foo", ["-42"]},
{"payable", "foo", ["42"]}
].
not_yet_compilable(fate) ->
[];
not_yet_compilable(aevm) ->
["funargs", "strings"].
File diff suppressed because it is too large Load Diff
-15
View File
@@ -1,15 +0,0 @@
## Requires ocaml >= 4.02, < 4.06
## and reason-3.0.0 (opam install reason).
default : voting_test
%.ml : %.re
refmt -p ml $< > $@
voting_test : rte.ml voting.ml voting_test.ml
ocamlopt -o $@ $^
clean :
rm -f *.cmi *.cmx *.ml *.o voting_test
-31
View File
@@ -1,31 +0,0 @@
// A simple test of the abort built-in function.
contract AbortTest =
record state = { value : int }
public function init(v : int) =
{ value = v }
// Aborting
public function do_abort(v : int, s : string) : unit =
put_value(v)
revert_abort(s)
// Accessing the value
public function get_value() = state.value
public function put_value(v : int) = put(state{value = v})
public function get_values() : list(int) = [state.value]
public function put_values(v : int) = put(state{value = v})
// Some basic statistics
public function get_stats(acct : address) =
( Contract.balance, Chain.balance(acct) )
// Abort functions.
private function revert_abort(s : string) =
abort(s)
// This is still legal but will be stripped out.
// TODO: This function confuses the type inference, so it cannot be present.
//private function abort(s : string) = 42
-27
View File
@@ -1,27 +0,0 @@
contract Interface =
function do_abort : (int, string) => unit
function get_value : () => int
function put_value : (int) => unit
function get_values : () => list(int)
function put_values : (int) => unit
contract AbortTestInt =
record state = {r : Interface, value : int}
public function init(r : Interface, value : int) =
{r = r, value = value}
// Aborting
public function do_abort(v : int, s : string) =
put_value(v)
state.r.do_abort(v + 100, s)
// Accessing the value
public function put_value(v : int) = put(state{value = v})
public function get_value() = state.value
public function get_values() : list(int) =
state.value :: state.r.get_values()
public function put_values(v : int) =
put_value(v)
state.r.put_values(v + 1000)
-8
View File
@@ -1,8 +0,0 @@
contract ChannelEnv =
public function coinbase() : address = Chain.coinbase
public function timestamp() : int = Chain.timestamp
public function block_height() : int = Chain.block_height
public function difficulty() : int = Chain.difficulty
@@ -1,7 +0,0 @@
contract ChannelOnChainContractNameResolution =
public function can_resolve(name: string, key: string) : bool =
switch(AENS.resolve(name, key) : option(string))
None => false
Some(_address) => true
@@ -1,51 +0,0 @@
contract ChannelOnChainContractOracle =
type query_t = string
type answer_t = string
type oracle_id = oracle(query_t, answer_t)
type query_id = oracle_query(query_t, answer_t)
record state = { oracle : oracle_id,
question : string,
bets : map(string, address)
}
public function init(oracle: oracle_id, question: string) : state =
{ oracle = oracle,
question = question,
bets = {}
}
public stateful function place_bet(answer: string) =
switch(Map.lookup(answer, state.bets))
None =>
put(state{ bets = state.bets{[answer] = Call.caller}})
"ok"
Some(_value) =>
"bet_already_taken"
public function expiry() =
Oracle.expiry(state.oracle)
public function query_fee() =
Oracle.query_fee(state.oracle)
public function get_question(q: query_id) =
Oracle.get_question(state.oracle, q)
public stateful function resolve(q: query_id) =
switch(Oracle.get_answer(state.oracle, q))
None =>
"no response"
Some(result) =>
if(state.question == Oracle.get_question(state.oracle, q))
switch(Map.lookup(result, state.bets))
None =>
"no winning bet"
Some(winner) =>
Chain.spend(winner, Contract.balance)
"ok"
else
"different question"
@@ -1,9 +0,0 @@
contract Remote =
function get : () => int
function can_resolve : (string, string) => bool
contract RemoteCall =
function remote_resolve(r : Remote, name: string, key: string) : bool =
r.can_resolve(name, key)
-51
View File
@@ -1,51 +0,0 @@
contract Chess =
type board = map(int, map(int, string))
type state = board
private function get_row(r, m : board) =
Map.lookup_default(r, m, {})
private function set_piece(r, c, p, m : board) =
m { [r] = get_row(r, m) { [c] = p } }
private function get_piece(r, c, m : board) =
Map.lookup(c, get_row(r, m))
private function from_list(xs, m : board) =
switch(xs)
[] => m
(r, c, p) :: xs => from_list(xs, set_piece(r, c, p, m))
function init() =
from_list([ (2, 1, "white pawn"), (7, 1, "black pawn")
, (2, 2, "white pawn"), (7, 2, "black pawn")
, (2, 3, "white pawn"), (7, 3, "black pawn")
, (2, 4, "white pawn"), (7, 4, "black pawn")
, (2, 5, "white pawn"), (7, 5, "black pawn")
, (2, 6, "white pawn"), (7, 6, "black pawn")
, (2, 7, "white pawn"), (7, 7, "black pawn")
, (2, 8, "white pawn"), (7, 8, "black pawn")
, (1, 1, "white rook"), (8, 1, "black rook")
, (1, 2, "white knight"), (8, 2, "black knight")
, (1, 3, "white bishop"), (8, 3, "black bishop")
, (1, 4, "white queen"), (8, 4, "black queen")
, (1, 5, "white king"), (8, 5, "black king")
, (1, 6, "white bishop"), (8, 6, "black bishop")
, (1, 7, "white knight"), (8, 7, "black knight")
, (1, 8, "white rook"), (8, 8, "black rook")
], {})
function piece(r, c) = get_piece(r, c, state)
function move_piece(r, c, r1, c1) =
switch(piece(r, c))
Some(p) => put(set_piece(r1, c1, p, state))
function destroy_piece(r, c) =
put(state{ [r] = Map.delete(c, get_row(r, state)) })
function delete_row(r) =
put(Map.delete(r, state))
@@ -1,5 +1,4 @@
contract UnappliedNamedArgBuiltin =
// Allowed in FATE, but not AEVM
stateful entrypoint main_fun(s) =
let reg = Oracle.register
reg(signature = s, Contract.address, 100, RelativeTTL(100)) : oracle(int, int)
@@ -0,0 +1,169 @@
contract A = entrypoint init() = ()
main contract C =
datatype custom_datatype('a) = CD('a)
record custom_record('a) = { f : 'a }
// pass
function
passing_ord : 'a is ord ; ('a, 'a) => bool
passing_ord(x, y) = x >= y
// pass
function
passing_eq : 'a is eq ; ('a, 'a) => bool
passing_eq(x, y) = x == y
// fail because eq is not specified for 'a
function
fail_no_eq : ('a, 'a) => bool
fail_no_eq(x, y) = x == y
// fail because 'b is not used
function
fail_unused_tvar : 'a is eq, 'b is eq ; ('a, 'a) => bool
fail_unused_tvar(x, y) = x == y
function
fail_unknown_constraint : 'a is foo ; ('a) => 'a
fail_unknown_constraint(x) = x
// Ord types
function bool_ord(x : bool, y : bool) = x >= y // pass
function bool_eq (x : bool, y : bool) = x == y // pass
function int_ord(x : int, y : int) = x >= y // pass
function int_eq (x : int, y : int) = x == y // pass
function char_ord(x : char, y : char) = x >= y // pass
function char_eq (x : char, y : char) = x == y // pass
function bits_ord(x : bits, y : bits) = x >= y // pass
function bits_eq (x : bits, y : bits) = x == y // pass
function bytes_ord(x : bytes(16), y : bytes(16)) = x >= y // pass
function bytes_eq (x : bytes(16), y : bytes(16)) = x == y // pass
function string_ord(x : string, y : string) = x >= y // pass
function string_eq (x : string, y : string) = x == y // pass
function hash_ord(x : hash, y : hash) = x >= y // pass
function hash_eq (x : hash, y : hash) = x == y // pass
function signature_ord(x : signature, y : signature) = x >= y // pass
function signature_eq (x : signature, y : signature) = x == y // pass
function address_ord(x : address, y : address) = x >= y // pass
function address_eq (x : address, y : address) = x == y // pass
// Eq types
function event_ord(x : Chain.ttl, y : Chain.ttl) = x >= y // fail
function event_eq (x : Chain.ttl, y : Chain.ttl) = x == y // pass
function contract_ord(x : A, y : A) = x >= y // fail
function contract_eq (x : A, y : A) = x == y // pass
// Noncomparable types
type lam = (int, char) => bool
function lambda_ord(x : lam, y : lam) = x >= y // fail
function lambda_eq (x : lam, y : lam) = x == y // fail
// Ord composite types of ord
function list_of_ord_ord(x : list(int), y : list(int)) = x >= y // pass
function list_of_ord_eq (x : list(int), y : list(int)) = x == y // pass
function option_of_ord_ord(x : option(int), y : option(int)) = x >= y // pass
function option_of_ord_eq (x : option(int), y : option(int)) = x == y // pass
function tuple_of_ord_ord(x : (int * bool), y : (int * bool)) = x >= y // pass
function tuple_of_ord_eq (x : (int * bool), y : (int * bool)) = x == y // pass
// Ord composite types of eq
function list_of_eq_ord(x : list(A), y : list(A)) = x >= y // fail
function list_of_eq_eq (x : list(A), y : list(A)) = x == y // pass
function option_of_eq_ord(x : option(A), y : option(A)) = x >= y // fail
function option_of_eq_eq (x : option(A), y : option(A)) = x == y // pass
function tuple_of_eq_ord(x : (A * int), y : (A * int)) = x >= y // fail
function tuple_of_eq_eq (x : (A * int), y : (A * int)) = x == y // pass
// Ord composite types of nomcomparable
function list_of_noncomp_ord(x : list(lam), y : list(lam)) = x >= y // fail
function list_of_noncomp_eq (x : list(lam), y : list(lam)) = x == y // fail
function option_of_noncomp_ord(x : option(lam), y : option(lam)) = x >= y // fail
function option_of_noncomp_eq (x : option(lam), y : option(lam)) = x == y // fail
function tuple_of_noncomp_ord(x : (lam * int), y : (lam * int)) = x >= y // fail
function tuple_of_noncomp_eq (x : (lam * int), y : (lam * int)) = x == y // fail
// Eq composite types of ord
function map_of_ord_ord(x : map(int, int), y : map(int, int)) = x >= y // fail
function map_of_ord_eq (x : map(int, int), y : map(int, int)) = x == y // pass
function oracle_of_ord_ord(x : oracle(int, int), y : oracle(int, int)) = x >= y // fail
function oracle_of_ord_eq (x : oracle(int, int), y : oracle(int, int)) = x == y // pass
function oracle_query_of_ord_ord(x : oracle_query(int, int), y : oracle_query(int, int)) = x >= y // fail
function oracle_query_of_ord_eq (x : oracle_query(int, int), y : oracle_query(int, int)) = x == y // pass
function datatype_of_ord_ord(x : custom_datatype(int), y : custom_datatype(int)) = x >= y // fail
function datatype_of_ord_eq (x : custom_datatype(int), y : custom_datatype(int)) = x == y // pass
function record_of_ord_ord(x : custom_record(int), y : custom_record(int)) = x >= y // fail
function record_of_ord_eq (x : custom_record(int), y : custom_record(int)) = x == y // pass
// Eq composite types of eq
function map_of_eq_ord(x : map(A, A), y : map(A, A)) = x >= y // fail
function map_of_eq_eq (x : map(A, A), y : map(A, A)) = x == y // pass
function oracle_of_eq_ord(x : oracle(A, A), y : oracle(A, A)) = x >= y // fail
function oracle_of_eq_eq (x : oracle(A, A), y : oracle(A, A)) = x == y // pass
function oracle_query_of_eq_ord(x : oracle_query(A, A), y : oracle_query(A, A)) = x >= y // fail
function oracle_query_of_eq_eq (x : oracle_query(A, A), y : oracle_query(A, A)) = x == y // pass
function datatype_of_eq_ord(x : custom_datatype(A), y : custom_datatype(A)) = x >= y // fail
function datatype_of_eq_eq (x : custom_datatype(A), y : custom_datatype(A)) = x == y // pass
function record_of_eq_ord(x : custom_record(A), y : custom_record(A)) = x >= y // fail
function record_of_eq_eq (x : custom_record(A), y : custom_record(A)) = x == y // pass
// Eq composite types of nomcomparable
function map_of_noncomp_ord(x : map(lam, lam), y : map(lam, lam)) = x >= y // fail
function map_of_noncomp_eq (x : map(lam, lam), y : map(lam, lam)) = x == y // fail
function oracle_of_noncomp_ord(x : oracle(lam, lam), y : oracle(lam, lam)) = x >= y // fail
function oracle_of_noncomp_eq (x : oracle(lam, lam), y : oracle(lam, lam)) = x == y // fail
function oracle_query_of_noncomp_ord(x : oracle_query(lam, lam), y : oracle_query(lam, lam)) = x >= y // fail
function oracle_query_of_noncomp_eq (x : oracle_query(lam, lam), y : oracle_query(lam, lam)) = x == y // fail
function datatype_of_noncomp_ord(x : custom_datatype(lam), y : custom_datatype(lam)) = x >= y // fail
function datatype_of_noncomp_eq (x : custom_datatype(lam), y : custom_datatype(lam)) = x == y // pass
function record_of_nomcomp_ord(x : custom_record(lam), y : custom_record(lam)) = x >= y // fail
function record_of_nomcomp_eq (x : custom_record(lam), y : custom_record(lam)) = x == y // pass
entrypoint init() =
let passing_ord_ord = passing_ord([1], [2]) // pass
let passing_ord_eq = passing_ord({[1] = 2}, {[2] = 3}) // fail
let passing_ord_noncomp = passing_ord((x) => x, (x) => x) // fail
let passing_eq_ord = passing_eq([1], [2]) // pass
let passing_eq_eq = passing_eq({[1] = 2}, {[2] = 3}) // pass
let passing_eq_noncomp = passing_eq((x) => x, (x) => x) // fail
()
-20
View File
@@ -1,20 +0,0 @@
contract OtherContract =
function multiply : (int, int) => int
contract ThisContract =
record state = { server : OtherContract, n : int }
function init(server : OtherContract) =
{ server = server, n = 2 }
function square() =
put(state{ n @ n = state.server.multiply(value = 100, n, n) })
function get_n() = state.n
function tip_server() =
Chain.spend(state.server.address, Call.value)
@@ -0,0 +1,3 @@
contract C =
record r = {}
entrypoint init() = ()
-87
View File
@@ -1,87 +0,0 @@
contract ERC20Token =
record state = {
totalSupply : int,
decimals : int,
name : string,
symbol : string,
balances : map(address, int),
allowed : map(address, map(address,int)),
// Logs, remove when native Events are there
transfer_log : list((address,address,int)),
approval_log : list((address,address,int))}
// init(100000000, 10, "Token Name", "TKN")
public stateful function init(_totalSupply : int, _decimals : int, _name : string, _symbol : string ) = {
totalSupply = _totalSupply,
decimals = _decimals,
name = _name,
symbol = _symbol,
balances = {[Call.caller] = _totalSupply }, // creator gets all Tokens
allowed = {},
// Logs, remove when native Events are there
transfer_log = [],
approval_log = []}
public stateful function totalSupply() : int = state.totalSupply
public stateful function decimals() : int = state.decimals
public stateful function name() : string = state.name
public stateful function symbol() : string = state.symbol
public stateful function balanceOf(tokenOwner : address ) : int =
Map.lookup_default(tokenOwner, state.balances, 0)
public stateful function transfer(to : address, tokens : int) =
put( state{balances[Call.caller] = sub(state.balances[Call.caller], tokens) })
put( state{balances[to] = add(Map.lookup_default(to, state.balances, 0), tokens) })
transferEvent(Call.caller, to, tokens)
true
public stateful function approve(spender : address, tokens : int) =
// allowed[Call.caller] field must have a value!
ensure_allowed(Call.caller)
put( state{allowed[Call.caller][spender] = tokens} )
approvalEvent(Call.caller, spender, tokens)
true
public stateful function transferFrom(from : address, to : address, tokens : int) =
put( state{ balances[from] = sub(state.balances[from], tokens) })
put( state{ allowed[from][Call.caller] = sub(state.allowed[from][Call.caller], tokens) })
put( state{ balances[to] = add(balanceOf(to), tokens) })
transferEvent(from, to, tokens)
true
public function allowance(_owner : address, _spender : address) : int =
state.allowed[_owner][_spender]
public stateful function getTransferLog() : list((address,address,int)) =
state.transfer_log
public stateful function getApprovalLog() : list((address,address,int)) =
state.approval_log
//
// Private Functions
//
private function ensure_allowed(key : address) =
switch(Map.lookup(key, state.allowed))
None => put(state{allowed[key] = {}})
Some(_) => ()
private function transferEvent(from : address, to : address, tokens : int) =
let e = (from, to, tokens)
put( state{transfer_log = e :: state.transfer_log })
e
private function approvalEvent(from : address, to : address, tokens : int) =
let e = (from, to, tokens)
put( state{approval_log = e :: state.approval_log })
e
private function sub(_a : int, _b : int) : int =
require(_b =< _a, "Error")
_a - _b
private function add(_a : int, _b : int) : int =
let c : int = _a + _b
require(c >= _a, "Error")
c
-6
View File
@@ -1,6 +0,0 @@
contract Exploits =
// We'll hack the bytecode of this changing the return type to string.
function pair(n : int) = (n, 0)
-9
View File
@@ -1,9 +0,0 @@
contract Remote =
function missing : (int) => int
contract Init_error =
record state = {value : int}
function init(r : Remote, x : int) =
{value = r.missing(x)}
-7
View File
@@ -1,7 +0,0 @@
contract Fail =
entrypoint tttt() : bool * int =
let f(x : 'a) : 'a = x
(f(true), f(1))
+1 -1
View File
@@ -1,4 +1,4 @@
// This should include Lists.aes implicitly, since Option.aes does.
include "List.aes"
include "Option.aes"
contract Test =
-36
View File
@@ -1,36 +0,0 @@
contract MapOfMaps =
type board = map(int, map(int, string))
type map2('a, 'b, 'c) = map('a, map('b, 'c))
record state = { big1 : map2(string, string, string),
big2 : map2(string, string, string),
small1 : map(string, string),
small2 : map(string, string) }
private function empty_state() =
{ big1 = {}, big2 = {},
small1 = {}, small2 = {} }
function init() = empty_state()
function setup_state() =
let small = {["key"] = "val"}
put({ big1 = {["one"] = small},
big2 = {["two"] = small},
small1 = small,
small2 = small })
// -- Garbage collection of inner map when outer map is garbage collected
function test1_setup() =
let inner = {["key"] = "val"}
put(empty_state() { big1 = {["one"] = inner} })
function test1_execute() =
put(state{ big1 = {} })
function test1_check() =
state.big1
-29
View File
@@ -1,29 +0,0 @@
contract MapUpdater =
function update_map : (int, string, map(int, string)) => map(int, string)
contract Benchmark =
record state = { updater : MapUpdater,
map : map(int, string) }
function init(u, m) = { updater = u, map = m }
function set_updater(u) = put(state{ updater = u })
function update_map(k : int, v : string, m) = m{ [k] = v }
function update(a : int, b : int, v : string) =
if (a > b) ()
else
put(state{ map[a] = v })
update(a + 1, b, v)
function get(k) = state.map[k]
function noop() = ()
function benchmark(k, v) =
let m = state.updater.update_map(k, v, state.map)
put(state{ map = m })
m
-6
View File
@@ -1,6 +0,0 @@
contract MinimalInit =
record state = {foo : int}
function init() =
{ foo = 0 }
-7
View File
@@ -1,7 +0,0 @@
contract MultiplicationServer =
function multiply(x : int, y : int) =
switch(Call.value >= 100)
true => x * y
@@ -0,0 +1,9 @@
@compiler >= 6
include "BLS12_381.aes"
namespace BLS12_381 =
type fp = int
main contract Bug =
type number = int
@@ -0,0 +1,8 @@
namespace Nmsp =
type x = int
namespace Nmsp =
type y = string
main contract Bug =
type number = int
+14
View File
@@ -0,0 +1,14 @@
include "List.aes"
contract C =
type state = int
function any(l : list(bool)) : bool = List.foldl((||), false, l)
entrypoint init() =
let bad_application = (+)(1)
let good_application = (-)(3, 4)
let op_var = (+)
let op_var_application = op_var(3, 4)
good_application + op_var_application
-11
View File
@@ -1,11 +0,0 @@
contract OraclesErr =
public function unsafeCreateQueryThenErr(
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
let x = Oracle.query(o, q, qfee, qttl, rttl)
switch(0) 1 => ()
x // Never reached.
-22
View File
@@ -1,22 +0,0 @@
contract OraclesGas =
type fee = int
type question_t = string
type answer_t = int
public function happyPathWithAllBuiltinsAtSameHeight(
qfee : fee,
ottl : Chain.ttl,
ettl : Chain.ttl,
qttl : Chain.ttl,
rttl : Chain.ttl
) =
let question = "why"
let answer = 42
let o = Oracle.register(Contract.address, qfee, ottl) : oracle(question_t, answer_t)
Oracle.extend(o, ettl)
require(qfee =< Call.value, "insufficient value for qfee")
let q = Oracle.query(o, question, qfee, qttl, rttl)
Oracle.respond(o, q, answer)
()
-35
View File
@@ -1,35 +0,0 @@
contract Oracles =
type fee = int
type ttl = Chain.ttl
type query_t = string
type answer_t = string
type oracle_id = oracle(query_t, answer_t)
type query_id = oracle_query(query_t, answer_t)
function createQuery(o : oracle_id,
q : query_t,
qfee : fee,
qttl : ttl,
rttl : ttl) : query_id =
require(qfee =< Call.value, "insufficient value for qfee")
Oracle.query(o, q, qfee, qttl, rttl)
function respond(o : oracle_id,
q : query_id,
sign : signature,
r : answer_t) : unit =
Oracle.respond(o, q, signature = sign, r)
function getQuestion(o : oracle_id,
q : query_id) : query_t =
Oracle.get_question(o, q)
function getAnswer(o : oracle_id,
q : query_id) : option(answer_t) =
Oracle.get_answer(o, q)
+18
View File
@@ -0,0 +1,18 @@
contract Main =
function is_negative(x : int) =
if (x < 0)
true
else
false
function inc_by_one(x : int) = x + 1
function inc_by_two(x : int) = x + 2
type state = bool
entrypoint init(x : int) = x
|> inc_by_one
|> inc_by_one
|> inc_by_two
|> ((x) => x * 5)
|> is_negative
-16
View File
@@ -1,16 +0,0 @@
contract Identity =
function zip_with(f, xs, ys) =
switch((xs, ys))
(x :: xs, y :: ys) => f(x, y) :: zip_with(f, xs, ys)
_ => []
// Check that we can use zip_with at different types
function foo() =
zip_with((x, y) => x + y, [1, 2, 3], [4, 5, 6, 7])
function bar() =
zip_with((x, y) => if(x) y else 0, [true, false, true, false], [1, 2, 3])
-57
View File
@@ -1,57 +0,0 @@
contract MapServer =
function insert : (string, string, map(string, string)) => map(string, string)
function delete : (string, map(string, string)) => map(string, string)
contract PrimitiveMaps =
record state = { remote : MapServer,
map : map(string, string),
map2 : map(string, string) }
function init(r) =
let m = {}
{ remote = r, map = m, map2 = m }
function set_remote(r) = put(state{ remote = r })
function insert(k, v, m) : map(string, string) = m{ [k] = v }
function delete(k, m) : map(string, string) = Map.delete(k, m)
function remote_insert(k, v, m) =
state.remote.insert(k, v, m)
function remote_delete(k, m) =
state.remote.delete(k, m)
function get_state_map() = state.map
function set_state_map(m) = put(state{ map = m })
function clone_state() = put(state{ map2 = state.map })
function insert_state(k, v) = put(state{ map @ m = m { [k] = v } })
function delete_state(k) = put(state{ map @ m = Map.delete(k, m) })
function lookup_state(k) = Map.lookup(k, state.map)
function double_insert_state(k, v1, v2) =
put(state{ map @ m = m { [k] = v1 },
map2 @ m = m { [k] = v2 } })
function test() =
let m = {} : map(string, string)
let m1 = m { ["foo"] = "value_of_foo",
["bla"] = "value_of_bla" }
let m2 = Map.delete("foo", m1)
let m3 = m2 { ["bla"] = "new_value_of_bla" }
[Map.lookup("foo", m), Map.lookup("bla", m),
Map.lookup("foo", m1), Map.lookup("bla", m1),
Map.lookup("foo", m2), Map.lookup("bla", m2),
Map.lookup("foo", m3), Map.lookup("bla", m3)]
function return_map() =
Map.delete("goo", {["foo"] = "bar", ["goo"] = "gaa"})
function argument_map(m : map(string, string)) =
m["foo"]
@@ -0,0 +1,11 @@
contract interface Coin =
entrypoint mint : () => int
contract interface OtherCoin =
entrypoint mint : () => int
main contract Main =
function mkCoin() : Coin = ct_11111111111111111111111111111115rHyByZ
entrypoint foo() : int =
let r = mkCoin()
r.mint()
-20
View File
@@ -1,20 +0,0 @@
contract Remote1 =
function set : (int) => int
contract RemoteCall =
record state = { i : int }
function init(x) = { i = x }
function set( x : int) : int =
let old = state.i
put(state{ i = x })
old
function call(r : Remote1, x : int, g : int) : int =
r.set(gas = g, value = 10, x)
function get() = state.i
-23
View File
@@ -1,23 +0,0 @@
contract RemoteState =
record rstate = { i : int, s : string, m : map(int, int) }
function look_at(s : rstate) = ()
function return_s(big : bool) =
let x = "short"
let y = "______longer_string_at_least_32_bytes_long___________longer_string_at_least_32_bytes_long___________longer_string_at_least_32_bytes_long_____"
if(big) y else x
function return_m(big : bool) =
let x = { [1] = 2 }
let y = { [1] = 2, [3] = 4, [5] = 6 }
if(big) y else x
function get(s : rstate) = s
function get_i(s : rstate) = s.i
function get_s(s : rstate) = s.s
function get_m(s : rstate) = s.m
function fun_update_i(s : rstate, ni) = s{ i = ni }
function fun_update_s(s : rstate, ns) = s{ s = ns }
function fun_update_m(s : rstate, nm) = s{ m = nm }
function fun_update_mk(s : rstate, k, v) = s{ m = s.m{[k] = v} }
-22
View File
@@ -1,22 +0,0 @@
contract Remote =
function id : ('a) => 'a
function missing : ('a) => 'a
function wrong_type : (string) => string
contract Main =
function id(x : int) =
x
function wrong_type(x : int) =
x
function remote_id(r : Remote, x) =
r.id(x)
function remote_missing(r : Remote, x) =
r.missing(x)
function remote_wrong_type(r : Remote, x) =
r.wrong_type(x)
-21
View File
@@ -1,21 +0,0 @@
contract ValueOnErr =
function err : () => int
function ok : () => int
contract RemoteValueOnErr =
public function callErr(
r : ValueOnErr,
value : int) : int =
r.err(value = value)
public function callErrLimitGas(
r : ValueOnErr,
value : int,
gas : int) : int =
r.err(value = value, gas = gas)
public function callOk(
r : ValueOnErr,
value : int) : int =
r.ok(value = value)
+4 -12
View File
@@ -1,12 +1,4 @@
// This is a custom test file if you need to run a compiler without
// changing aeso_compiler_tests.erl
include "List.aes"
contract IntegerHolder =
type state = int
entrypoint init(x) = x
entrypoint get() = state
main contract Test =
stateful entrypoint f(c) = Chain.clone(ref=c, 123)
contract ShareTwo =
record state = {s1 : int, s2 : int}
entrypoint init() = {s1 = 0, s2 = 0}
stateful entrypoint buy() = ()
+1 -1
View File
@@ -1,4 +1,4 @@
// Builtins without named arguments can appear unapplied in both AEVM and FATE.
// Builtins without named arguments can appear unapplied.
// Named argument builtins are:
// Oracle.register
// Oracle.respond
-6
View File
@@ -1,6 +0,0 @@
contract UpfrontCharges =
record state = { b : int } // For enabling retrieval of sender balance observed inside init.
public function init() : state = { b = b() }
public function initialSenderBalance() : int = state.b
public function senderBalance() : int = b()
private function b() = Chain.balance(Call.origin)
-7
View File
@@ -1,7 +0,0 @@
contract ValueOnErr =
public function err() : int =
switch(0) 1 => 5
public function ok() : int =
11
+65
View File
@@ -0,0 +1,65 @@
// Used include
include "Pair.aes"
// Unused include
include "Triple.aes"
namespace UnusedNamespace =
function f() = 1 + g()
// Used in f
private function g() = 2
// Unused
private function h() = 3
contract Warnings =
type state = int
type unused_type = bool
entrypoint init(p) = Pair.fst(p) + Pair.snd(p)
stateful entrypoint negative_spend(to : address) = Chain.spend(to, -1)
entrypoint shadowing() =
let x = 1
let x = 2
x
entrypoint division_by_zero(x) = x / 0
stateful entrypoint unused_stateful() = 1
stateful entrypoint used_stateful(x : int) = put(x)
entrypoint unused_variables(unused_arg : int) =
let unused_var = 10
let z = 20
z
// Unused functions
function unused_function() = ()
function recursive_unused_function() = recursive_unused_function()
function called_unused_function1() = called_unused_function2()
function called_unused_function2() = called_unused_function1()
function rv() = 1
entrypoint unused_return_value() =
rv()
2
// Duplicated constraint on 'a
entrypoint
duplicated_tvar_constraint : 'a is eq, 'a is eq ; ('a, 'a) => bool
duplicated_tvar_constraint (x, y) = x == y
namespace FunctionsAsArgs =
function f() = g()
private function g() = h(inc)
private function h(fn : (int => int)) = fn(1)
// Passed as arg to h in g
private function inc(n : int) : int = n + 1
// Never used
private function dec(n : int) : int = n - 1