Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9256a7d31d | |||
| 3bbb8a9874 | |||
| 7b8957b46a | |||
| e46226a693 | |||
| b599d581ee | |||
| b3767071a8 | |||
| b0e6418161 | |||
| a894876f56 | |||
| 0af45dfd19 | |||
| c5bfcd3bdc | |||
| 85879f5380 | |||
| 8897cc6cbd | |||
| 0ec7fdc6ac | |||
| 74aff5401b | |||
| cfcf0a8a81 | |||
| ca31db7cad | |||
| 196460a607 | |||
| bf04362f9a | |||
| d4ea7d5d3b | |||
| c1c3c29393 | |||
| b474bb22cd | |||
| c04f66a00a | |||
| 37d86ad45b | |||
| 60f3a484e6 | |||
| 40c78c1707 | |||
| cf08aeee04 | |||
| a04dd6c86d | |||
| f488b35f2e | |||
| cc1de9baba | |||
| fe5f5545d3 | |||
| 98a4049f03 | |||
| 3dce0e627b | |||
| 6b46fc268b | |||
| 30bedad164 | |||
| 4d6938c741 | |||
| 10fc88a21d | |||
| 3218a2c172 |
@@ -3,7 +3,7 @@ version: 2.1
|
||||
executors:
|
||||
aebuilder:
|
||||
docker:
|
||||
- image: aeternity/builder
|
||||
- image: aeternity/builder:xenial-otp21
|
||||
user: builder
|
||||
working_directory: ~/aesophia
|
||||
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -6,8 +6,21 @@ 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)
|
||||
```
|
||||
### 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
|
||||
|
||||
@@ -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
|
||||
|
||||
+8
-14
@@ -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
|
||||
@@ -402,7 +402,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 +411,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
|
||||
|
||||
+1282
-1271
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -173,7 +173,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 +191,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 +313,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,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)
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
include "List.aes"
|
||||
|
||||
namespace Option =
|
||||
|
||||
function is_none(o : option('a)) : bool = switch(o)
|
||||
|
||||
+16
-16
@@ -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,8 +84,8 @@ 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)
|
||||
|
||||
@@ -101,15 +101,15 @@ namespace String =
|
||||
to_int_(i :: is, value, x, b) =
|
||||
switch(value(i))
|
||||
None => None
|
||||
Some(i) => to_int_(is, value, x * b + i, b)
|
||||
Some(n) => to_int_(is, value, x * b + n, 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
@@ -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
@@ -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",
|
||||
|
||||
+6
-4
@@ -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}
|
||||
@@ -83,7 +83,7 @@ from_typed_ast(Type, TypedAst) ->
|
||||
string -> do_render_aci_json(JArray)
|
||||
end.
|
||||
|
||||
encode_contract(Contract = {Head, _, {con, _, Name}, _}) when ?IS_CONTRACT_HEAD(Head) ->
|
||||
encode_contract(Contract = {Head, _, {con, _, Name}, _, _}) when ?IS_CONTRACT_HEAD(Head) ->
|
||||
C0 = #{name => encode_name(Name)},
|
||||
|
||||
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ],
|
||||
@@ -341,10 +341,12 @@ stateful(false) -> "".
|
||||
|
||||
%% #contract{Ann, Con, [Declarations]}.
|
||||
|
||||
contract_funcs({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace ->
|
||||
contract_funcs({C, _, _, _, Decls}) when ?IS_CONTRACT_HEAD(C) ->
|
||||
[ D || D <- Decls, is_fun(D)].
|
||||
|
||||
contract_types({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace ->
|
||||
contract_types({namespace, _, _, Decls}) ->
|
||||
[ D || D <- Decls, is_type(D) ];
|
||||
contract_types({C, _, _, _, Decls}) when ?IS_CONTRACT_HEAD(C) ->
|
||||
[ D || D <- Decls, is_type(D) ].
|
||||
|
||||
is_fun({letfun, _, _, _, _, _}) -> true;
|
||||
|
||||
+863
-1185
File diff suppressed because it is too large
Load Diff
@@ -326,7 +326,7 @@ get_option(Opt, Env, Default) ->
|
||||
%% -- Compilation ------------------------------------------------------------
|
||||
|
||||
-spec to_fcode(env(), aeso_syntax:ast()) -> {env(), fcode()}.
|
||||
to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest])
|
||||
to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, _Impls, Decls}|Rest])
|
||||
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
case Contract =:= contract_interface of
|
||||
false ->
|
||||
@@ -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).
|
||||
@@ -703,8 +703,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
@@ -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
@@ -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"
|
||||
|
||||
+113
-343
@@ -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.
|
||||
@@ -299,36 +238,25 @@ insert_init_function(Code, Options) ->
|
||||
|
||||
last_contract_indent(Decls) ->
|
||||
case lists:last(Decls) of
|
||||
{_, _, _, [Decl | _]} -> aeso_syntax:get_ann(col, Decl, 1) - 1;
|
||||
_ -> 0
|
||||
{_, _, _, _, [Decl | _]} -> aeso_syntax:get_ann(col, Decl, 1) - 1;
|
||||
_ -> 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,75 +316,29 @@ 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) ->
|
||||
get_decode_type(FunName, [{Contract, Ann, _, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}];
|
||||
({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
|
||||
(_) -> [] end,
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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()}).
|
||||
@@ -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}.
|
||||
@@ -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').
|
||||
|
||||
+51
-13
@@ -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}, _}} ->
|
||||
@@ -94,17 +96,29 @@ decl() ->
|
||||
choice(
|
||||
%% Contract declaration
|
||||
[ ?RULE(token(main), keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5})
|
||||
con(), tok('='), maybe_block(decl()), {contract_main, _2, _3, [], _5})
|
||||
, ?RULE(token(main), keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5, _7})
|
||||
, ?RULE(keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4})
|
||||
con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, [], _4})
|
||||
, ?RULE(keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4, _6})
|
||||
, ?RULE(keyword(contract), token(interface),
|
||||
con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5})
|
||||
con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, [], _5})
|
||||
, ?RULE(keyword(contract), token(interface),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5, _7})
|
||||
, ?RULE(token(payable), token(main), keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6}))
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, [], _6}))
|
||||
, ?RULE(token(payable), token(main), keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6, _8}))
|
||||
, ?RULE(token(payable), keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5}))
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, [], _5}))
|
||||
, ?RULE(token(payable), keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5, _7}))
|
||||
, ?RULE(token(payable), keyword(contract), token(interface),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6}))
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, [], _6}))
|
||||
, ?RULE(token(payable), keyword(contract), token(interface),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6, _8}))
|
||||
|
||||
|
||||
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
|
||||
@@ -306,17 +320,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 +347,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 +484,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
|
||||
@@ -523,7 +551,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 +697,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, _} ->
|
||||
|
||||
+6
-2
@@ -151,12 +151,16 @@ decl(D, Options) ->
|
||||
with_options(Options, fun() -> decl(D) end).
|
||||
|
||||
-spec decl(aeso_syntax:decl()) -> doc().
|
||||
decl({Con, Attrs, C, Ds}) when ?IS_CONTRACT_HEAD(Con) ->
|
||||
decl({Con, Attrs, C, Is, Ds}) when ?IS_CONTRACT_HEAD(Con) ->
|
||||
Mod = fun({Mod, true}) when Mod == payable ->
|
||||
text(atom_to_list(Mod));
|
||||
(_) -> empty() end,
|
||||
ImplsList = case Is of
|
||||
[] -> [empty()];
|
||||
_ -> [text(":"), par(punctuate(text(","), lists:map(fun name/1, Is)), 0)]
|
||||
end,
|
||||
block(follow( hsep(lists:map(Mod, Attrs) ++ [contract_head(Con)])
|
||||
, hsep(name(C), text("="))), decls(Ds));
|
||||
, hsep([name(C)] ++ ImplsList ++ [text("=")])), decls(Ds));
|
||||
decl({namespace, _, C, Ds}) ->
|
||||
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
|
||||
decl({pragma, _, Pragma}) -> pragma(Pragma);
|
||||
|
||||
+3
-3
@@ -38,9 +38,9 @@
|
||||
-type namespace_alias() :: none | con().
|
||||
-type namespace_parts() :: none | {for, [id()]} | {hiding, [id()]}.
|
||||
|
||||
-type decl() :: {contract_main, ann(), con(), [decl()]}
|
||||
| {contract_child, ann(), con(), [decl()]}
|
||||
| {contract_interface, ann(), con(), [decl()]}
|
||||
-type decl() :: {contract_main, ann(), con(), [con()], [decl()]}
|
||||
| {contract_child, ann(), con(), [con()], [decl()]}
|
||||
| {contract_interface, ann(), con(), [con()], [decl()]}
|
||||
| {namespace, ann(), con(), [decl()]}
|
||||
| {pragma, ann(), pragma()}
|
||||
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
|
||||
|
||||
@@ -0,0 +1,730 @@
|
||||
-module(aeso_type_env).
|
||||
|
||||
|
||||
-type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()] | var_args, utype()}
|
||||
| {app_t, aeso_syntax:ann(), utype(), [utype()]}
|
||||
| {tuple_t, aeso_syntax:ann(), [utype()]}
|
||||
| aeso_syntax:id() | aeso_syntax:qid()
|
||||
| aeso_syntax:con() | aeso_syntax:qcon() %% contracts
|
||||
| aeso_syntax:tvar()
|
||||
| {if_t, aeso_syntax:ann(), aeso_syntax:id(), utype(), utype()} %% Can branch on named argument (protected)
|
||||
| uvar().
|
||||
|
||||
-type uvar() :: {uvar, aeso_syntax:ann(), reference()}.
|
||||
|
||||
-type named_args_t() :: uvar() | [{named_arg_t, aeso_syntax:ann(), aeso_syntax:id(), utype(), aeso_syntax:expr()}].
|
||||
|
||||
-type type_id() :: aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon().
|
||||
|
||||
-define(is_type_id(T), element(1, T) =:= id orelse
|
||||
element(1, T) =:= qid orelse
|
||||
element(1, T) =:= con orelse
|
||||
element(1, T) =:= qcon).
|
||||
|
||||
|
||||
-type access() :: public | private | internal.
|
||||
|
||||
-type typedef() :: {[aeso_syntax:tvar()], aeso_syntax:typedef() | {contract_t, [aeso_syntax:field_t()]}}
|
||||
| {builtin, non_neg_integer()}.
|
||||
|
||||
-type type() :: aeso_syntax:type().
|
||||
-type name() :: string().
|
||||
-type qname() :: [string()].
|
||||
-type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [type()], type()}.
|
||||
|
||||
-type namespace_alias() :: none | name().
|
||||
-type namespace_parts() :: none | {for, [name()]} | {hiding, [name()]}.
|
||||
-type used_namespaces() :: [{qname(), namespace_alias(), namespace_parts()}].
|
||||
|
||||
-type type_constraints() :: none | bytes_concat | bytes_split | address_to_contract | bytecode_hash.
|
||||
|
||||
|
||||
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
|
||||
-type type_info() :: {aeso_syntax:ann(), typedef()}.
|
||||
-type var_info() :: {aeso_syntax:ann(), utype()}.
|
||||
|
||||
-type fun_env() :: [{name(), fun_info()}].
|
||||
-type type_env() :: [{name(), type_info()}].
|
||||
|
||||
|
||||
-record(field_info,
|
||||
{ ann :: aeso_syntax:ann()
|
||||
, field_t :: utype()
|
||||
, record_t :: utype()
|
||||
, kind :: contract | record }).
|
||||
|
||||
-type field_info() :: #field_info{}.
|
||||
|
||||
|
||||
-record(scope, { funs = [] :: fun_env()
|
||||
, types = [] :: type_env()
|
||||
, access = public :: access()
|
||||
, kind = namespace :: namespace | contract
|
||||
, ann = [{origin, system}] :: aeso_syntax:ann()
|
||||
}).
|
||||
|
||||
-type scope() :: #scope{}.
|
||||
|
||||
-record(env,
|
||||
{ scopes = #{ [] => #scope{}} :: #{ qname() => scope() }
|
||||
, vars = [] :: [{name(), var_info()}]
|
||||
, typevars = unrestricted :: unrestricted | [name()]
|
||||
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
|
||||
, contract_parents = #{} :: #{ name() => [name()] }
|
||||
, namespace = [] :: qname()
|
||||
, used_namespaces = [] :: used_namespaces()
|
||||
, in_pattern = false :: boolean()
|
||||
, in_guard = false :: boolean()
|
||||
, stateful = false :: boolean()
|
||||
, unify_throws = true :: boolean()
|
||||
, current_function = none :: none | aeso_syntax:id()
|
||||
, what = top :: top | namespace | contract | contract_interface
|
||||
}).
|
||||
|
||||
-type env() :: #env{}.
|
||||
|
||||
|
||||
%% -- Environment manipulation -----------------------------------------------
|
||||
|
||||
-spec push_scope(namespace | contract, aeso_syntax:con(), env()) -> env().
|
||||
push_scope(Kind, Con, Env) ->
|
||||
Ann = aeso_syntax:get_ann(Con),
|
||||
Name = name(Con),
|
||||
New = Env#env.namespace ++ [Name],
|
||||
Env#env{ namespace = New, scopes = (Env#env.scopes)#{ New => #scope{ kind = Kind, ann = Ann } } }.
|
||||
|
||||
-spec pop_scope(env()) -> env().
|
||||
pop_scope(Env) ->
|
||||
Env#env{ namespace = lists:droplast(Env#env.namespace) }.
|
||||
|
||||
-spec get_scope(env(), qname()) -> false | scope().
|
||||
get_scope(#env{ scopes = Scopes }, Name) ->
|
||||
maps:get(Name, Scopes, false).
|
||||
|
||||
-spec on_current_scope(env(), fun((scope()) -> scope())) -> env().
|
||||
on_current_scope(Env = #env{ namespace = NS, scopes = Scopes }, Fun) ->
|
||||
Scope = maps:get(NS, Scopes),
|
||||
Env#env{ scopes = Scopes#{ NS => Fun(Scope) } }.
|
||||
|
||||
-spec on_scopes(env(), fun((scope()) -> scope())) -> env().
|
||||
on_scopes(Env = #env{ scopes = Scopes }, Fun) ->
|
||||
Env#env{ scopes = maps:map(fun(_, Scope) -> Fun(Scope) end, Scopes) }.
|
||||
|
||||
-spec bind_var(aeso_syntax:id(), utype(), env()) -> env().
|
||||
bind_var({id, Ann, X}, T, Env = #env{ vars = Vars }) ->
|
||||
when_warning(warn_shadowing, fun() -> warn_potential_shadowing(Ann, X, Vars) end),
|
||||
Env#env{ vars = [{X, {Ann, T}} | Env#env.vars] }.
|
||||
|
||||
-spec bind_vars([{aeso_syntax:id(), utype()}], env()) -> env().
|
||||
bind_vars([], Env) -> Env;
|
||||
bind_vars([{X, T} | Vars], Env) ->
|
||||
bind_vars(Vars, bind_var(X, T, Env)).
|
||||
|
||||
-spec bind_tvars([aeso_syntax:tvar()], env()) -> env().
|
||||
bind_tvars(Xs, Env) ->
|
||||
Env#env{ typevars = [X || {tvar, _, X} <- Xs] }.
|
||||
|
||||
-spec check_tvar(env(), aeso_syntax:tvar()) -> aeso_syntax:tvar() | no_return().
|
||||
check_tvar(#env{ typevars = TVars}, T = {tvar, _, X}) ->
|
||||
case TVars == unrestricted orelse lists:member(X, TVars) of
|
||||
true -> ok;
|
||||
false -> type_error({unbound_type, T})
|
||||
end,
|
||||
T.
|
||||
|
||||
-spec bind_fun(name(), type() | typesig(), env()) -> env().
|
||||
bind_fun(X, Type, Env) ->
|
||||
case lookup_env(Env, term, [], [X]) of
|
||||
false -> force_bind_fun(X, Type, Env);
|
||||
{_QId, {Ann1, _}} ->
|
||||
type_error({duplicate_definition, X, [Ann1, aeso_syntax:get_ann(Type)]}),
|
||||
Env
|
||||
end.
|
||||
|
||||
-spec force_bind_fun(name(), type() | typesig(), env()) -> env().
|
||||
force_bind_fun(X, Type, Env = #env{ what = What }) ->
|
||||
Ann = aeso_syntax:get_ann(Type),
|
||||
NoCode = get_option(no_code, false),
|
||||
Entry = if X == "init", What == contract, not NoCode ->
|
||||
{reserved_init, Ann, Type};
|
||||
What == contract_interface -> {contract_fun, Ann, Type};
|
||||
true -> {Ann, Type}
|
||||
end,
|
||||
on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) ->
|
||||
Scope#scope{ funs = [{X, Entry} | Funs] }
|
||||
end).
|
||||
|
||||
-spec bind_funs([{name(), type() | typesig()}], env()) -> env().
|
||||
bind_funs([], Env) -> Env;
|
||||
bind_funs([{Id, Type} | Rest], Env) ->
|
||||
bind_funs(Rest, bind_fun(Id, Type, Env)).
|
||||
|
||||
-spec bind_type(name(), aeso_syntax:ann(), typedef(), env()) -> env().
|
||||
bind_type(X, Ann, Def, Env) ->
|
||||
on_current_scope(Env, fun(Scope = #scope{ types = Types }) ->
|
||||
Scope#scope{ types = [{X, {Ann, Def}} | Types] }
|
||||
end).
|
||||
|
||||
%% Bind state primitives
|
||||
-spec bind_state(env()) -> env().
|
||||
bind_state(Env) ->
|
||||
Ann = [{origin, system}],
|
||||
Unit = {tuple_t, Ann, []},
|
||||
State =
|
||||
case lookup_type(Env, {id, Ann, "state"}) of
|
||||
{S, _} -> {qid, Ann, S};
|
||||
false -> Unit
|
||||
end,
|
||||
Env1 = bind_funs([{"state", State},
|
||||
{"put", {type_sig, [stateful | Ann], none, [], [State], Unit}}], Env),
|
||||
|
||||
case lookup_type(Env, {id, Ann, "event"}) of
|
||||
{E, _} ->
|
||||
%% We bind Chain.event in a local 'Chain' namespace.
|
||||
Event = {qid, Ann, E},
|
||||
pop_scope(
|
||||
bind_fun("event", {fun_t, Ann, [], [Event], Unit},
|
||||
push_scope(namespace, {con, Ann, "Chain"}, Env1)));
|
||||
false -> Env1
|
||||
end.
|
||||
|
||||
-spec bind_field(name(), field_info(), env()) -> env().
|
||||
bind_field(X, Info, Env = #env{ fields = Fields }) ->
|
||||
Fields1 = maps:update_with(X, fun(Infos) -> [Info | Infos] end, [Info], Fields),
|
||||
Env#env{ fields = Fields1 }.
|
||||
|
||||
-spec bind_fields([{name(), field_info()}], env()) -> env().
|
||||
bind_fields([], Env) -> Env;
|
||||
bind_fields([{Id, Info} | Rest], Env) ->
|
||||
bind_fields(Rest, bind_field(Id, Info, Env)).
|
||||
|
||||
%% Contract entrypoints take three named arguments
|
||||
%% gas : int = Call.gas_left()
|
||||
%% value : int = 0
|
||||
%% protected : bool = false
|
||||
contract_call_type({fun_t, Ann, [], Args, Ret}) ->
|
||||
Id = fun(X) -> {id, Ann, X} end,
|
||||
Int = Id("int"),
|
||||
Typed = fun(E, T) -> {typed, Ann, E, T} end,
|
||||
Named = fun(Name, Default = {typed, _, _, T}) -> {named_arg_t, Ann, Id(Name), T, Default} end,
|
||||
{fun_t, Ann, [Named("gas", Typed({app, Ann, Typed({qid, Ann, ["Call", "gas_left"]},
|
||||
{fun_t, Ann, [], [], Int}),
|
||||
[]}, Int)),
|
||||
Named("value", Typed({int, Ann, 0}, Int)),
|
||||
Named("protected", Typed({bool, Ann, false}, Id("bool")))],
|
||||
Args, {if_t, Ann, Id("protected"), {app_t, Ann, {id, Ann, "option"}, [Ret]}, Ret}}.
|
||||
|
||||
-spec bind_contract(aeso_syntax:decl(), env()) -> env().
|
||||
bind_contract({Contract, Ann, Id, _Impls, Contents}, Env)
|
||||
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
Key = name(Id),
|
||||
Sys = [{origin, system}],
|
||||
Fields =
|
||||
[ {field_t, AnnF, Entrypoint, contract_call_type(Type)}
|
||||
|| {fun_decl, AnnF, Entrypoint, Type} <- Contents ] ++
|
||||
[ {field_t, AnnF, Entrypoint,
|
||||
contract_call_type(
|
||||
{fun_t, AnnF, [], [ArgT || {typed, _, _, ArgT} <- Args], RetT})
|
||||
}
|
||||
|| {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [{guarded, _, [], {typed, _, _, RetT}}]} <- Contents,
|
||||
Name =/= "init"
|
||||
] ++
|
||||
%% Predefined fields
|
||||
[ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ] ++
|
||||
[ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME},
|
||||
contract_call_type(
|
||||
case [ [ArgT || {typed, _, _, ArgT} <- Args]
|
||||
|| {letfun, AnnF, {id, _, "init"}, Args, _, _} <- Contents,
|
||||
aeso_syntax:get_ann(entrypoint, AnnF, false)]
|
||||
++ [ Args
|
||||
|| {fun_decl, AnnF, {id, _, "init"}, {fun_t, _, _, Args, _}} <- Contents,
|
||||
aeso_syntax:get_ann(entrypoint, AnnF, false)]
|
||||
++ [ Args
|
||||
|| {fun_decl, AnnF, {id, _, "init"}, {type_sig, _, _, _, Args, _}} <- Contents,
|
||||
aeso_syntax:get_ann(entrypoint, AnnF, false)]
|
||||
of
|
||||
[] -> {fun_t, [stateful,payable|Sys], [], [], {id, Sys, "void"}};
|
||||
[Args] -> {fun_t, [stateful,payable|Sys], [], Args, {id, Sys, "void"}}
|
||||
end
|
||||
)
|
||||
}
|
||||
],
|
||||
FieldInfo = [ {Entrypoint, #field_info{ ann = FieldAnn,
|
||||
kind = contract,
|
||||
field_t = Type,
|
||||
record_t = Id }}
|
||||
|| {field_t, _, {id, FieldAnn, Entrypoint}, Type} <- Fields ],
|
||||
bind_type(Key, Ann, {[], {contract_t, Fields}},
|
||||
bind_fields(FieldInfo, Env)).
|
||||
|
||||
%% What scopes could a given name come from?
|
||||
-spec possible_scopes(env(), qname()) -> [qname()].
|
||||
possible_scopes(#env{ namespace = Current, used_namespaces = UsedNamespaces }, Name) ->
|
||||
Qual = lists:droplast(Name),
|
||||
NewQuals = case lists:filter(fun(X) -> element(2, X) == Qual end, UsedNamespaces) of
|
||||
[] ->
|
||||
[Qual];
|
||||
Namespaces ->
|
||||
lists:map(fun(X) -> element(1, X) end, Namespaces)
|
||||
end,
|
||||
Ret1 = [ lists:sublist(Current, I) ++ Q || I <- lists:seq(0, length(Current)), Q <- NewQuals ],
|
||||
Ret2 = [ Namespace ++ Q || {Namespace, none, _} <- UsedNamespaces, Q <- NewQuals ],
|
||||
lists:usort(Ret1 ++ Ret2).
|
||||
|
||||
-spec visible_in_used_namespaces(used_namespaces(), qname()) -> boolean().
|
||||
visible_in_used_namespaces(UsedNamespaces, QName) ->
|
||||
Qual = lists:droplast(QName),
|
||||
Name = lists:last(QName),
|
||||
case lists:filter(fun({Ns, _, _}) -> Qual == Ns end, UsedNamespaces) of
|
||||
[] ->
|
||||
true;
|
||||
Namespaces ->
|
||||
IsVisible = fun(Namespace) ->
|
||||
case Namespace of
|
||||
{_, _, {for, Names}} ->
|
||||
lists:member(Name, Names);
|
||||
{_, _, {hiding, Names}} ->
|
||||
not lists:member(Name, Names);
|
||||
_ ->
|
||||
true
|
||||
end
|
||||
end,
|
||||
lists:any(IsVisible, Namespaces)
|
||||
end.
|
||||
|
||||
-spec lookup_type(env(), type_id()) -> false | {qname(), type_info()}.
|
||||
lookup_type(Env, Id) ->
|
||||
lookup_env(Env, type, aeso_syntax:get_ann(Id), qname(Id)).
|
||||
|
||||
-spec lookup_env(env(), term, aeso_syntax:ann(), qname()) -> false | {qname(), fun_info()};
|
||||
(env(), type, aeso_syntax:ann(), qname()) -> false | {qname(), type_info()}.
|
||||
lookup_env(Env, Kind, Ann, Name) ->
|
||||
Var = case Name of
|
||||
[X] when Kind == term -> proplists:get_value(X, Env#env.vars, false);
|
||||
_ -> false
|
||||
end,
|
||||
case Var of
|
||||
{Ann1, Type} -> {Name, {Ann1, Type}};
|
||||
false ->
|
||||
Names = [ Qual ++ [lists:last(Name)] || Qual <- possible_scopes(Env, Name) ],
|
||||
case [ Res || QName <- Names, Res <- [lookup_env1(Env, Kind, Ann, QName)], Res /= false] of
|
||||
[] -> false;
|
||||
[Res = {_, {AnnR, _}}] ->
|
||||
when_warning(warn_unused_includes,
|
||||
fun() ->
|
||||
%% If a file is used from a different file, we
|
||||
%% can then mark it as used
|
||||
F1 = proplists:get_value(file, Ann, no_file),
|
||||
F2 = proplists:get_value(file, AnnR, no_file),
|
||||
if
|
||||
F1 /= F2 ->
|
||||
used_include(AnnR);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end),
|
||||
Res;
|
||||
Many ->
|
||||
type_error({ambiguous_name, qid(Ann, Name), [{qid, A, Q} || {Q, {A, _}} <- Many]}),
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
-spec lookup_env1(env(), type | term, aeso_syntax:ann(), qname()) -> false | {qname(), fun_info()}.
|
||||
lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes = Scopes }, Kind, Ann, QName) ->
|
||||
Qual = lists:droplast(QName),
|
||||
Name = lists:last(QName),
|
||||
AllowPrivate = lists:prefix(Qual, Current),
|
||||
%% Get the scope
|
||||
case maps:get(Qual, Scopes, false) of
|
||||
false -> false; %% TODO: return reason for not in scope
|
||||
#scope{ funs = Funs, types = Types } ->
|
||||
Defs = case Kind of
|
||||
type -> Types;
|
||||
term -> Funs
|
||||
end,
|
||||
%% Look up the unqualified name
|
||||
case proplists:get_value(Name, Defs, false) of
|
||||
false -> false;
|
||||
{reserved_init, Ann1, Type} ->
|
||||
type_error({cannot_call_init_function, Ann}),
|
||||
{QName, {Ann1, Type}}; %% Return the type to avoid an extra not-in-scope error
|
||||
{contract_fun, Ann1, Type} ->
|
||||
type_error({contract_treated_as_namespace, Ann, QName}),
|
||||
{QName, {Ann1, Type}};
|
||||
{Ann1, _} = E ->
|
||||
%% Check that it's not private (or we can see private funs)
|
||||
case not is_private(Ann1) orelse AllowPrivate of
|
||||
true ->
|
||||
case visible_in_used_namespaces(UsedNamespaces, QName) of
|
||||
true -> {QName, E};
|
||||
false -> false
|
||||
end;
|
||||
false -> false
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
-spec lookup_record_field(env(), name()) -> [field_info()].
|
||||
lookup_record_field(Env, FieldName) ->
|
||||
maps:get(FieldName, Env#env.fields, []).
|
||||
|
||||
%% For 'create' or 'update' constraints we don't consider contract types.
|
||||
-spec lookup_record_field(env(), name(), create | project | update) -> [field_info()].
|
||||
lookup_record_field(Env, FieldName, Kind) ->
|
||||
[ Fld || Fld = #field_info{ kind = K } <- lookup_record_field(Env, FieldName),
|
||||
Kind == project orelse K /= contract ].
|
||||
|
||||
%% -- Name manipulation ------------------------------------------------------
|
||||
|
||||
-spec qname(type_id()) -> qname().
|
||||
qname({id, _, X}) -> [X];
|
||||
qname({qid, _, Xs}) -> Xs;
|
||||
qname({con, _, X}) -> [X];
|
||||
qname({qcon, _, Xs}) -> Xs.
|
||||
|
||||
-spec name(aeso_syntax:id() | aeso_syntax:con()) -> name().
|
||||
name({_, _, X}) -> X.
|
||||
|
||||
-spec qid(aeso_syntax:ann(), qname()) -> aeso_syntax:id() | aeso_syntax:qid().
|
||||
qid(Ann, [X]) -> {id, Ann, X};
|
||||
qid(Ann, Xs) -> {qid, Ann, Xs}.
|
||||
|
||||
-spec qcon(aeso_syntax:ann(), qname()) -> aeso_syntax:con() | aeso_syntax:qcon().
|
||||
qcon(Ann, [X]) -> {con, Ann, X};
|
||||
qcon(Ann, Xs) -> {qcon, Ann, Xs}.
|
||||
|
||||
-spec set_qname(qname(), type_id()) -> type_id().
|
||||
set_qname(Xs, {id, Ann, _}) -> qid(Ann, Xs);
|
||||
set_qname(Xs, {qid, Ann, _}) -> qid(Ann, Xs);
|
||||
set_qname(Xs, {con, Ann, _}) -> qcon(Ann, Xs);
|
||||
set_qname(Xs, {qcon, Ann, _}) -> qcon(Ann, Xs).
|
||||
|
||||
is_private(Ann) -> proplists:get_value(private, Ann, false).
|
||||
|
||||
%% -- The rest ---------------------------------------------------------------
|
||||
|
||||
%% Environment containing language primitives
|
||||
-spec global_env() -> env().
|
||||
global_env() ->
|
||||
Ann = [{origin, system}],
|
||||
Int = {id, Ann, "int"},
|
||||
Char = {id, Ann, "char"},
|
||||
Bool = {id, Ann, "bool"},
|
||||
String = {id, Ann, "string"},
|
||||
Address = {id, Ann, "address"},
|
||||
Hash = {id, Ann, "hash"},
|
||||
Bits = {id, Ann, "bits"},
|
||||
Bytes = fun(Len) -> {bytes_t, Ann, Len} end,
|
||||
Oracle = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle"}, [Q, R]} end,
|
||||
Query = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle_query"}, [Q, R]} end,
|
||||
Unit = {tuple_t, Ann, []},
|
||||
List = fun(T) -> {app_t, Ann, {id, Ann, "list"}, [T]} end,
|
||||
Option = fun(T) -> {app_t, Ann, {id, Ann, "option"}, [T]} end,
|
||||
Map = fun(A, B) -> {app_t, Ann, {id, Ann, "map"}, [A, B]} end,
|
||||
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
|
||||
FunC = fun(C, Ts, T) -> {type_sig, Ann, C, [], Ts, T} end,
|
||||
FunC1 = fun(C, S, T) -> {type_sig, Ann, C, [], [S], T} end,
|
||||
Fun = fun(Ts, T) -> FunC(none, Ts, T) end,
|
||||
Fun1 = fun(S, T) -> Fun([S], T) end,
|
||||
FunCN = fun(C, Named, Normal, Ret) -> {type_sig, Ann, C, Named, Normal, Ret} end,
|
||||
FunN = fun(Named, Normal, Ret) -> FunCN(none, Named, Normal, Ret) end,
|
||||
%% Lambda = fun(Ts, T) -> {fun_t, Ann, [], Ts, T} end,
|
||||
%% Lambda1 = fun(S, T) -> Lambda([S], T) end,
|
||||
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], none, [], Ts, T} end,
|
||||
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
|
||||
SignId = {id, Ann, "signature"},
|
||||
SignDef = {bytes, Ann, <<0:64/unit:8>>},
|
||||
Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}},
|
||||
SignFun = fun(Ts, T) -> {type_sig, [stateful|Ann], none, [Signature], Ts, T} end,
|
||||
TTL = {qid, Ann, ["Chain", "ttl"]},
|
||||
Pointee = {qid, Ann, ["AENS", "pointee"]},
|
||||
AENSName = {qid, Ann, ["AENS", "name"]},
|
||||
Fr = {qid, Ann, ["MCL_BLS12_381", "fr"]},
|
||||
Fp = {qid, Ann, ["MCL_BLS12_381", "fp"]},
|
||||
Fp2 = {tuple_t, Ann, [Fp, Fp]},
|
||||
G1 = {tuple_t, Ann, [Fp, Fp, Fp]},
|
||||
G2 = {tuple_t, Ann, [Fp2, Fp2, Fp2]},
|
||||
GT = {tuple_t, Ann, lists:duplicate(12, Fp)},
|
||||
Tx = {qid, Ann, ["Chain", "tx"]},
|
||||
GAMetaTx = {qid, Ann, ["Chain", "ga_meta_tx"]},
|
||||
BaseTx = {qid, Ann, ["Chain", "base_tx"]},
|
||||
PayForTx = {qid, Ann, ["Chain", "paying_for_tx"]},
|
||||
|
||||
FldT = fun(Id, T) -> {field_t, Ann, {id, Ann, Id}, T} end,
|
||||
TxFlds = [{"paying_for", Option(PayForTx)}, {"ga_metas", List(GAMetaTx)},
|
||||
{"actor", Address}, {"fee", Int}, {"ttl", Int}, {"tx", BaseTx}],
|
||||
TxType = {record_t, [FldT(N, T) || {N, T} <- TxFlds ]},
|
||||
Stateful = fun(T) -> setelement(2, T, [stateful|element(2, T)]) end,
|
||||
|
||||
Fee = Int,
|
||||
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
|
||||
|
||||
MkDefs = fun(Defs) -> [{X, {Ann, if is_integer(T) -> {builtin, T}; true -> T end}} || {X, T} <- Defs] end,
|
||||
|
||||
TopScope = #scope
|
||||
{ funs = MkDefs(
|
||||
%% Option constructors
|
||||
[{"None", Option(A)},
|
||||
{"Some", Fun1(A, Option(A))},
|
||||
%% TTL constructors
|
||||
{"RelativeTTL", Fun1(Int, TTL)},
|
||||
{"FixedTTL", Fun1(Int, TTL)},
|
||||
%% Abort
|
||||
{"abort", Fun1(String, A)},
|
||||
{"require", Fun([Bool, String], Unit)}])
|
||||
, types = MkDefs(
|
||||
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
|
||||
{"void", 0},
|
||||
{"unit", {[], {alias_t, Unit}}},
|
||||
{"hash", {[], {alias_t, Bytes(32)}}},
|
||||
{"signature", {[], {alias_t, Bytes(64)}}},
|
||||
{"bits", 0},
|
||||
{"option", 1}, {"list", 1}, {"map", 2},
|
||||
{"oracle", 2}, {"oracle_query", 2}
|
||||
]) },
|
||||
|
||||
ChainScope = #scope
|
||||
{ funs = MkDefs(
|
||||
%% Spend transaction.
|
||||
[{"spend", StateFun([Address, Int], Unit)},
|
||||
%% Chain environment
|
||||
{"balance", Fun1(Address, Int)},
|
||||
{"block_hash", Fun1(Int, Option(Hash))},
|
||||
{"coinbase", Address},
|
||||
{"timestamp", Int},
|
||||
{"block_height", Int},
|
||||
{"difficulty", Int},
|
||||
{"gas_limit", Int},
|
||||
{"bytecode_hash",FunC1(bytecode_hash, A, Option(Hash))},
|
||||
{"create", Stateful(
|
||||
FunN([ {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}}
|
||||
], var_args, A))},
|
||||
{"clone", Stateful(
|
||||
FunN([ {named_arg_t, Ann, {id, Ann, "gas"}, Int,
|
||||
{typed, Ann,
|
||||
{app, Ann,
|
||||
{typed, Ann, {qid, Ann, ["Call","gas_left"]},
|
||||
typesig_to_fun_t(Fun([], Int))
|
||||
},
|
||||
[]}, Int
|
||||
}}
|
||||
, {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}}
|
||||
, {named_arg_t, Ann, {id, Ann, "protected"}, Bool, {typed, Ann, {bool, Ann, false}, Bool}}
|
||||
, {named_arg_t, Ann, {id, Ann, "ref"}, A, undefined}
|
||||
], var_args, A))},
|
||||
%% Tx constructors
|
||||
{"GAMetaTx", Fun([Address, Int], GAMetaTx)},
|
||||
{"PayingForTx", Fun([Address, Int], PayForTx)},
|
||||
{"SpendTx", Fun([Address, Int, String], BaseTx)},
|
||||
{"OracleRegisterTx", BaseTx},
|
||||
{"OracleQueryTx", BaseTx},
|
||||
{"OracleResponseTx", BaseTx},
|
||||
{"OracleExtendTx", BaseTx},
|
||||
{"NamePreclaimTx", BaseTx},
|
||||
{"NameClaimTx", Fun([String], BaseTx)},
|
||||
{"NameUpdateTx", Fun([Hash], BaseTx)},
|
||||
{"NameRevokeTx", Fun([Hash], BaseTx)},
|
||||
{"NameTransferTx", Fun([Address, Hash], BaseTx)},
|
||||
{"ChannelCreateTx", Fun([Address], BaseTx)},
|
||||
{"ChannelDepositTx", Fun([Address, Int], BaseTx)},
|
||||
{"ChannelWithdrawTx", Fun([Address, Int], BaseTx)},
|
||||
{"ChannelForceProgressTx", Fun([Address], BaseTx)},
|
||||
{"ChannelCloseMutualTx", Fun([Address], BaseTx)},
|
||||
{"ChannelCloseSoloTx", Fun([Address], BaseTx)},
|
||||
{"ChannelSlashTx", Fun([Address], BaseTx)},
|
||||
{"ChannelSettleTx", Fun([Address], BaseTx)},
|
||||
{"ChannelSnapshotSoloTx", Fun([Address], BaseTx)},
|
||||
{"ContractCreateTx", Fun([Int], BaseTx)},
|
||||
{"ContractCallTx", Fun([Address, Int], BaseTx)},
|
||||
{"GAAttachTx", BaseTx}
|
||||
])
|
||||
, types = MkDefs([{"ttl", 0}, {"tx", {[], TxType}},
|
||||
{"base_tx", 0},
|
||||
{"paying_for_tx", 0}, {"ga_meta_tx", 0}]) },
|
||||
|
||||
ContractScope = #scope
|
||||
{ funs = MkDefs(
|
||||
[{"address", Address},
|
||||
{"creator", Address},
|
||||
{"balance", Int}]) },
|
||||
|
||||
CallScope = #scope
|
||||
{ funs = MkDefs(
|
||||
[{"origin", Address},
|
||||
{"caller", Address},
|
||||
{"value", Int},
|
||||
{"gas_price", Int},
|
||||
{"fee", Int},
|
||||
{"gas_left", Fun([], Int)}])
|
||||
},
|
||||
|
||||
OracleScope = #scope
|
||||
{ funs = MkDefs(
|
||||
[{"register", SignFun([Address, Fee, TTL], Oracle(Q, R))},
|
||||
{"expiry", Fun([Oracle(Q, R)], Fee)},
|
||||
{"query_fee", Fun([Oracle(Q, R)], Fee)},
|
||||
{"query", StateFun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))},
|
||||
{"get_question", Fun([Oracle(Q, R), Query(Q, R)], Q)},
|
||||
{"respond", SignFun([Oracle(Q, R), Query(Q, R), R], Unit)},
|
||||
{"extend", SignFun([Oracle(Q, R), TTL], Unit)},
|
||||
{"get_answer", Fun([Oracle(Q, R), Query(Q, R)], option_t(Ann, R))},
|
||||
{"check", Fun([Oracle(Q, R)], Bool)},
|
||||
{"check_query", Fun([Oracle(Q,R), Query(Q, R)], Bool)}]) },
|
||||
|
||||
AENSScope = #scope
|
||||
{ funs = MkDefs(
|
||||
[{"resolve", Fun([String, String], option_t(Ann, A))},
|
||||
{"preclaim", SignFun([Address, Hash], Unit)},
|
||||
{"claim", SignFun([Address, String, Int, Int], Unit)},
|
||||
{"transfer", SignFun([Address, Address, String], Unit)},
|
||||
{"revoke", SignFun([Address, String], Unit)},
|
||||
{"update", SignFun([Address, String, Option(TTL), Option(Int), Option(Map(String, Pointee))], Unit)},
|
||||
{"lookup", Fun([String], option_t(Ann, AENSName))},
|
||||
%% AENS pointee constructors
|
||||
{"AccountPt", Fun1(Address, Pointee)},
|
||||
{"OraclePt", Fun1(Address, Pointee)},
|
||||
{"ContractPt", Fun1(Address, Pointee)},
|
||||
{"ChannelPt", Fun1(Address, Pointee)},
|
||||
%% Name object constructor
|
||||
{"Name", Fun([Address, TTL, Map(String, Pointee)], AENSName)}
|
||||
])
|
||||
, types = MkDefs([{"pointee", 0}, {"name", 0}]) },
|
||||
|
||||
MapScope = #scope
|
||||
{ funs = MkDefs(
|
||||
[{"from_list", Fun1(List(Pair(K, V)), Map(K, V))},
|
||||
{"to_list", Fun1(Map(K, V), List(Pair(K, V)))},
|
||||
{"lookup", Fun([K, Map(K, V)], Option(V))},
|
||||
{"lookup_default", Fun([K, Map(K, V), V], V)},
|
||||
{"delete", Fun([K, Map(K, V)], Map(K, V))},
|
||||
{"member", Fun([K, Map(K, V)], Bool)},
|
||||
{"size", Fun1(Map(K, V), Int)}]) },
|
||||
|
||||
%% Crypto/Curve operations
|
||||
CryptoScope = #scope
|
||||
{ funs = MkDefs(
|
||||
[{"verify_sig", Fun([Hash, Address, SignId], Bool)},
|
||||
{"verify_sig_secp256k1", Fun([Hash, Bytes(64), SignId], Bool)},
|
||||
{"ecverify_secp256k1", Fun([Hash, Bytes(20), Bytes(65)], Bool)},
|
||||
{"ecrecover_secp256k1", Fun([Hash, Bytes(65)], Option(Bytes(20)))},
|
||||
{"sha3", Fun1(A, Hash)},
|
||||
{"sha256", Fun1(A, Hash)},
|
||||
{"blake2b", Fun1(A, Hash)}]) },
|
||||
|
||||
%% Fancy BLS12-381 crypto operations
|
||||
MCL_BLS12_381_Scope = #scope
|
||||
{ funs = MkDefs(
|
||||
[{"g1_neg", Fun1(G1, G1)},
|
||||
{"g1_norm", Fun1(G1, G1)},
|
||||
{"g1_valid", Fun1(G1, Bool)},
|
||||
{"g1_is_zero", Fun1(G1, Bool)},
|
||||
{"g1_add", Fun ([G1, G1], G1)},
|
||||
{"g1_mul", Fun ([Fr, G1], G1)},
|
||||
|
||||
{"g2_neg", Fun1(G2, G2)},
|
||||
{"g2_norm", Fun1(G2, G2)},
|
||||
{"g2_valid", Fun1(G2, Bool)},
|
||||
{"g2_is_zero", Fun1(G2, Bool)},
|
||||
{"g2_add", Fun ([G2, G2], G2)},
|
||||
{"g2_mul", Fun ([Fr, G2], G2)},
|
||||
|
||||
{"gt_inv", Fun1(GT, GT)},
|
||||
{"gt_add", Fun ([GT, GT], GT)},
|
||||
{"gt_mul", Fun ([GT, GT], GT)},
|
||||
{"gt_pow", Fun ([GT, Fr], GT)},
|
||||
{"gt_is_one", Fun1(GT, Bool)},
|
||||
{"pairing", Fun ([G1, G2], GT)},
|
||||
{"miller_loop", Fun ([G1, G2], GT)},
|
||||
{"final_exp", Fun1(GT, GT)},
|
||||
|
||||
{"int_to_fr", Fun1(Int, Fr)},
|
||||
{"int_to_fp", Fun1(Int, Fp)},
|
||||
{"fr_to_int", Fun1(Fr, Int)},
|
||||
{"fp_to_int", Fun1(Fp, Int)}
|
||||
]),
|
||||
types = MkDefs(
|
||||
[{"fr", 0}, {"fp", 0}]) },
|
||||
|
||||
%% Authentication
|
||||
AuthScope = #scope
|
||||
{ funs = MkDefs(
|
||||
[{"tx_hash", Option(Hash)},
|
||||
{"tx", Option(Tx)} ]) },
|
||||
|
||||
%% Strings
|
||||
StringScope = #scope
|
||||
{ funs = MkDefs(
|
||||
[{"length", Fun1(String, Int)},
|
||||
{"concat", Fun([String, String], String)},
|
||||
{"to_list", Fun1(String, List(Char))},
|
||||
{"from_list", Fun1(List(Char), String)},
|
||||
{"to_upper", Fun1(String, String)},
|
||||
{"to_lower", Fun1(String, String)},
|
||||
{"sha3", Fun1(String, Hash)},
|
||||
{"sha256", Fun1(String, Hash)},
|
||||
{"blake2b", Fun1(String, Hash)}
|
||||
]) },
|
||||
|
||||
%% Chars
|
||||
CharScope = #scope
|
||||
{ funs = MkDefs(
|
||||
[{"to_int", Fun1(Char, Int)},
|
||||
{"from_int", Fun1(Int, Option(Char))}]) },
|
||||
|
||||
%% Bits
|
||||
BitsScope = #scope
|
||||
{ funs = MkDefs(
|
||||
[{"set", Fun([Bits, Int], Bits)},
|
||||
{"clear", Fun([Bits, Int], Bits)},
|
||||
{"test", Fun([Bits, Int], Bool)},
|
||||
{"sum", Fun1(Bits, Int)},
|
||||
{"intersection", Fun([Bits, Bits], Bits)},
|
||||
{"union", Fun([Bits, Bits], Bits)},
|
||||
{"difference", Fun([Bits, Bits], Bits)},
|
||||
{"none", Bits},
|
||||
{"all", Bits}]) },
|
||||
|
||||
%% Bytes
|
||||
BytesScope = #scope
|
||||
{ funs = MkDefs(
|
||||
[{"to_int", Fun1(Bytes(any), Int)},
|
||||
{"to_str", Fun1(Bytes(any), String)},
|
||||
{"concat", FunC(bytes_concat, [Bytes(any), Bytes(any)], Bytes(any))},
|
||||
{"split", FunC(bytes_split, [Bytes(any)], Pair(Bytes(any), Bytes(any)))}
|
||||
]) },
|
||||
|
||||
%% Conversion
|
||||
IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) },
|
||||
AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)},
|
||||
{"to_contract", FunC(address_to_contract, [Address], A)},
|
||||
{"is_oracle", Fun1(Address, Bool)},
|
||||
{"is_contract", Fun1(Address, Bool)},
|
||||
{"is_payable", Fun1(Address, Bool)}]) },
|
||||
|
||||
|
||||
#env{ scopes =
|
||||
#{ [] => TopScope
|
||||
, ["Chain"] => ChainScope
|
||||
, ["Contract"] => ContractScope
|
||||
, ["Call"] => CallScope
|
||||
, ["Oracle"] => OracleScope
|
||||
, ["AENS"] => AENSScope
|
||||
, ["Map"] => MapScope
|
||||
, ["Auth"] => AuthScope
|
||||
, ["Crypto"] => CryptoScope
|
||||
, ["MCL_BLS12_381"] => MCL_BLS12_381_Scope
|
||||
, ["StringInternal"] => StringScope
|
||||
, ["Char"] => CharScope
|
||||
, ["Bits"] => BitsScope
|
||||
, ["Bytes"] => BytesScope
|
||||
, ["Int"] => IntScope
|
||||
, ["Address"] => AddressScope
|
||||
}
|
||||
, fields =
|
||||
maps:from_list([{N, [#field_info{ ann = [], field_t = T, record_t = Tx, kind = record }]}
|
||||
|| {N, T} <- TxFlds ])
|
||||
}.
|
||||
|
||||
option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}.
|
||||
map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
|
||||
+9
-62
@@ -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.
|
||||
|
||||
@@ -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
@@ -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).
|
||||
|
||||
@@ -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]),
|
||||
|
||||
@@ -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,24 +32,14 @@ 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()].
|
||||
|
||||
parse_args(Fun, Args) ->
|
||||
[{contract_main, _, _, [{letfun, _, _, _, _, [{guarded, _, [], {app, _, _, AST}}]}]}] =
|
||||
[{contract_main, _, _, _, [{letfun, _, _, _, _, [{guarded, _, [], {app, _, _, AST}}]}]}] =
|
||||
aeso_parser:string("main contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
|
||||
strip_ann(AST).
|
||||
|
||||
@@ -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"].
|
||||
|
||||
+556
-410
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ simple_contracts_test_() ->
|
||||
Text = "main contract Identity =\n"
|
||||
" function id(x) = x\n",
|
||||
?assertMatch(
|
||||
[{contract_main, _, {con, _, "Identity"},
|
||||
[{contract_main, _, {con, _, "Identity"}, _,
|
||||
[{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"},
|
||||
[{guarded, _, [], {id, _, "x"}}]}]}], parse_string(Text)),
|
||||
ok
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() = ()
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)}
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
contract Fail =
|
||||
|
||||
entrypoint tttt() : bool * int =
|
||||
let f(x : 'a) : 'a = x
|
||||
(f(true), f(1))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// This should include Lists.aes implicitly, since Option.aes does.
|
||||
include "List.aes"
|
||||
include "Option.aes"
|
||||
|
||||
contract Test =
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
contract MinimalInit =
|
||||
|
||||
record state = {foo : int}
|
||||
|
||||
function init() =
|
||||
{ foo = 0 }
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
contract interface Strokable =
|
||||
entrypoint stroke : () => string
|
||||
|
||||
contract Cat : Strokable =
|
||||
entrypoint stroke() = "Cat stroke"
|
||||
@@ -0,0 +1,10 @@
|
||||
contract interface II =
|
||||
entrypoint f : () => unit
|
||||
|
||||
contract interface I : II =
|
||||
entrypoint f : () => unit
|
||||
entrypoint g : () => unit
|
||||
|
||||
contract C : I =
|
||||
entrypoint f() = ()
|
||||
entrypoint g() = ()
|
||||
@@ -0,0 +1,9 @@
|
||||
contract interface I0 =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface I1 : I0 =
|
||||
entrypoint f : () => int
|
||||
entrypoint something_else : () => int
|
||||
|
||||
main contract C =
|
||||
entrypoint f(x : I1) = x.f() // Here we should know that x has f
|
||||
@@ -0,0 +1,13 @@
|
||||
contract interface X : Z =
|
||||
entrypoint x : () => int
|
||||
|
||||
contract interface Y : X =
|
||||
entrypoint y : () => int
|
||||
|
||||
contract interface Z : Y =
|
||||
entrypoint z : () => int
|
||||
|
||||
contract C : Z =
|
||||
entrypoint x() = 1
|
||||
entrypoint y() = 1
|
||||
entrypoint z() = 1
|
||||
@@ -0,0 +1,8 @@
|
||||
contract interface I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface II : I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract C : II =
|
||||
entrypoint f() = 1
|
||||
@@ -0,0 +1,9 @@
|
||||
contract interface I1 =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface I2 : I1 =
|
||||
entrypoint f : () => char
|
||||
|
||||
contract C : I2 =
|
||||
entrypoint f() = 1
|
||||
entrypoint f() = 'c'
|
||||
@@ -0,0 +1,8 @@
|
||||
contract interface I1 =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface I2 : I1 =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract C : I2 =
|
||||
entrypoint f() = 1
|
||||
@@ -0,0 +1,5 @@
|
||||
contract interface I : H =
|
||||
entrypoint f : () => unit
|
||||
|
||||
contract C =
|
||||
entrypoint g() = ()
|
||||
@@ -0,0 +1,8 @@
|
||||
contract interface I1 =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface I2 : I1 =
|
||||
entrypoint g : () => int
|
||||
|
||||
contract C : I2 =
|
||||
entrypoint g() = 1
|
||||
@@ -0,0 +1,9 @@
|
||||
contract interface I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface J =
|
||||
entrypoint g : () => char
|
||||
|
||||
contract C : I, J =
|
||||
entrypoint f() = 1
|
||||
entrypoint g() = 'c'
|
||||
@@ -0,0 +1,8 @@
|
||||
contract interface I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface J =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract C : I, J =
|
||||
entrypoint f() = 1
|
||||
@@ -0,0 +1,9 @@
|
||||
contract interface I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface J =
|
||||
entrypoint f : () => char
|
||||
|
||||
contract C : I, J =
|
||||
entrypoint f() = 1
|
||||
entrypoint f() = 'c'
|
||||
@@ -0,0 +1,2 @@
|
||||
contract C : I =
|
||||
entrypoint f() = ()
|
||||
@@ -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])
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
contract interface Creature =
|
||||
entrypoint is_alive : () => bool
|
||||
|
||||
contract interface Animal : Creature =
|
||||
entrypoint is_alive : () => bool
|
||||
entrypoint sound : () => string
|
||||
|
||||
contract Cat : Animal =
|
||||
entrypoint sound() = "meow"
|
||||
entrypoint is_alive() = true
|
||||
|
||||
main contract Main =
|
||||
entrypoint init() = ()
|
||||
|
||||
stateful function g0(_ : Creature) : Cat = Chain.create()
|
||||
stateful function f0(x : Cat) : Creature = g0(x)
|
||||
stateful function h0() =
|
||||
let a : Animal = (Chain.create() : Cat)
|
||||
let c : Creature = (Chain.create() : Cat)
|
||||
let c1 : Creature = a
|
||||
()
|
||||
|
||||
stateful function g1(x : Animal) : Cat = Chain.create()
|
||||
stateful function f1(x : Cat) : Animal = g1(x)
|
||||
|
||||
stateful function g11(x : list(Animal)) : list(Cat) = [Chain.create()]
|
||||
stateful function f11(x : list(Cat)) : list(Animal) = g11(x)
|
||||
|
||||
stateful function g12(x : Animal * Animal) : Cat * Cat = (Chain.create(), Chain.create())
|
||||
stateful function f12(x : Cat * Cat) : Animal * Animal = g12(x)
|
||||
|
||||
stateful function g13() : map(Cat, Cat) = { [Chain.create()] = Chain.create() }
|
||||
stateful function f13() : map(Animal, Animal) = g13()
|
||||
|
||||
stateful function g2(x : Cat) : Cat = Chain.create()
|
||||
stateful function f2(x : Animal) : Animal = g2(x) // fail
|
||||
|
||||
stateful function g3(x : Cat) : Animal = f1(x)
|
||||
stateful function f3(x : Cat) : Cat = g3(x) // fail
|
||||
|
||||
stateful function g4(x : (Cat => Animal)) : Cat = Chain.create()
|
||||
stateful function f4(x : (Animal => Cat)) : Animal = g4(x)
|
||||
|
||||
stateful function g44(x : list(list(Cat) => list(Animal))) : Cat = Chain.create()
|
||||
stateful function f44(x : list(list(Animal) => list(Cat))) : Animal = g44(x)
|
||||
|
||||
stateful function g5(x : (Animal => Animal)) : Cat = Chain.create()
|
||||
stateful function f5(x : (Cat => Cat)) : Animal = g5(x) // fail
|
||||
|
||||
stateful function g6() : option(Cat) = Some(Chain.create())
|
||||
stateful function f6() : option(Animal) = g6()
|
||||
stateful function h6() : option(Cat) = f6() // fail
|
||||
|
||||
type cat_type = Cat
|
||||
type animal_type = Animal
|
||||
type cat_cat_map = map(cat_type, cat_type)
|
||||
type animal_animal_map = map(animal_type, animal_type)
|
||||
|
||||
stateful function g71(x : animal_type) : cat_type = Chain.create()
|
||||
stateful function f71(x : cat_type) : animal_type = g1(x)
|
||||
|
||||
stateful function g72() : cat_cat_map = { [Chain.create()] = Chain.create() }
|
||||
stateful function f72() : animal_animal_map = g13()
|
||||
|
||||
stateful function g73() =
|
||||
let some_cat : Cat = Chain.create()
|
||||
let some_animal : Animal = some_cat
|
||||
|
||||
let some_cat_cat_map : map(Cat, Cat) = g13()
|
||||
let some_animal_animal_map : map(Animal, Animal) = some_cat_cat_map
|
||||
|
||||
let x : Animal = some_animal_animal_map[some_cat] // success
|
||||
let y : Cat = some_cat_cat_map[some_animal] // fail
|
||||
|
||||
()
|
||||
@@ -0,0 +1,135 @@
|
||||
contract interface Animal =
|
||||
entrypoint sound : () => string
|
||||
|
||||
contract Cat : Animal =
|
||||
entrypoint sound() = "meow"
|
||||
|
||||
main contract Main =
|
||||
datatype dt_contra('a) = DT_CONTRA('a => unit)
|
||||
datatype dt_co('a) = DT_CO(unit => 'a)
|
||||
datatype dt_inv('a) = DT_INV('a => 'a)
|
||||
datatype dt_biv('a) = DT_BIV(unit => unit)
|
||||
datatype dt_inv_sep('a) = DT_INV_SEP_A('a => unit) | DT_INV_SEP_B(unit => 'a)
|
||||
datatype dt_co_nest_a('a) = DT_CO_NEST_A(dt_contra('a) => unit)
|
||||
datatype dt_contra_nest_a('a) = DT_CONTRA_NEST_A(dt_co('a) => unit)
|
||||
datatype dt_contra_nest_b('a) = DT_CONTRA_NEST_B(unit => dt_contra('a))
|
||||
datatype dt_co_nest_b('a) = DT_CO_NEST_B(unit => dt_co('a))
|
||||
datatype dt_co_twice('a) = DT_CO_TWICE(('a => unit) => 'a)
|
||||
datatype dt_contra_twice('a) = DT_CONTRA_TWICE('a => 'a => unit)
|
||||
datatype dt_a_contra_b_contra('a, 'b) = DT_A_CONTRA_B_CONTRA('a => 'b => unit)
|
||||
|
||||
function f_a_to_a_to_u(_ : Animal) : (Animal => unit) = f_a_to_u
|
||||
function f_a_to_c_to_u(_ : Animal) : (Cat => unit) = f_c_to_u
|
||||
function f_c_to_a_to_u(_ : Cat) : (Animal => unit) = f_a_to_u
|
||||
function f_c_to_c_to_u(_ : Cat) : (Cat => unit) = f_c_to_u
|
||||
|
||||
function f_u_to_u(_ : unit) : unit = ()
|
||||
function f_a_to_u(_ : Animal) : unit = ()
|
||||
function f_c_to_u(_ : Cat) : unit = ()
|
||||
|
||||
function f_dt_contra_a_to_u(_ : dt_contra(Animal)) : unit = ()
|
||||
function f_dt_contra_c_to_u(_ : dt_contra(Cat)) : unit = ()
|
||||
function f_dt_co_a_to_u(_ : dt_co(Animal)) : unit = ()
|
||||
function f_dt_co_c_to_u(_ : dt_co(Cat)) : unit = ()
|
||||
function f_u_to_dt_contra_a(_ : unit) : dt_contra(Animal) = DT_CONTRA(f_a_to_u)
|
||||
function f_u_to_dt_contra_c(_ : unit) : dt_contra(Cat) = DT_CONTRA(f_c_to_u)
|
||||
|
||||
stateful function f_c() : Cat = Chain.create()
|
||||
stateful function f_a() : Animal = f_c()
|
||||
|
||||
stateful function f_u_to_a(_ : unit) : Animal = f_a()
|
||||
stateful function f_u_to_c(_ : unit) : Cat = f_c()
|
||||
stateful function f_a_to_a(_ : Animal) : Animal = f_a()
|
||||
stateful function f_a_to_c(_ : Animal) : Cat = f_c()
|
||||
stateful function f_c_to_a(_ : Cat) : Animal = f_a()
|
||||
stateful function f_c_to_c(_ : Cat) : Cat = f_c()
|
||||
|
||||
stateful function f_a_to_u_to_c(_ : (Animal => unit)) : Cat = f_c()
|
||||
stateful function f_c_to_u_to_a(_ : (Cat => unit)) : Animal = f_a()
|
||||
stateful function f_c_to_u_to_c(_ : (Cat => unit)) : Cat = f_c()
|
||||
|
||||
stateful function f_u_to_dt_co_a(_ : unit) : dt_co(Animal) = DT_CO(f_u_to_a)
|
||||
stateful function f_u_to_dt_co_c(_ : unit) : dt_co(Cat) = DT_CO(f_u_to_c)
|
||||
|
||||
stateful entrypoint init() =
|
||||
let va1 : dt_contra(Animal) = DT_CONTRA(f_a_to_u) // success
|
||||
let va2 : dt_contra(Animal) = DT_CONTRA(f_c_to_u) // fail
|
||||
let va3 : dt_contra(Cat) = DT_CONTRA(f_a_to_u) // success
|
||||
let va4 : dt_contra(Cat) = DT_CONTRA(f_c_to_u) // success
|
||||
|
||||
let vb1 : dt_co(Animal) = DT_CO(f_u_to_a) // success
|
||||
let vb2 : dt_co(Animal) = DT_CO(f_u_to_c) // success
|
||||
let vb3 : dt_co(Cat) = DT_CO(f_u_to_a) // fail
|
||||
let vb4 : dt_co(Cat) = DT_CO(f_u_to_c) // success
|
||||
|
||||
let vc1 : dt_inv(Animal) = DT_INV(f_a_to_a) // success
|
||||
let vc2 : dt_inv(Animal) = DT_INV(f_a_to_c) // success
|
||||
let vc3 : dt_inv(Animal) = DT_INV(f_c_to_a) // fail
|
||||
let vc4 : dt_inv(Animal) = DT_INV(f_c_to_c) // fail
|
||||
let vc5 : dt_inv(Cat) = DT_INV(f_a_to_a) // fail
|
||||
let vc6 : dt_inv(Cat) = DT_INV(f_a_to_c) // fail
|
||||
let vc7 : dt_inv(Cat) = DT_INV(f_c_to_a) // fail
|
||||
let vc8 : dt_inv(Cat) = DT_INV(f_c_to_c) // success
|
||||
|
||||
let vd1 : dt_biv(Animal) = DT_BIV(f_u_to_u) : dt_biv(Animal) // success
|
||||
let vd2 : dt_biv(Animal) = DT_BIV(f_u_to_u) : dt_biv(Cat) // success
|
||||
let vd3 : dt_biv(Cat) = DT_BIV(f_u_to_u) : dt_biv(Animal) // success
|
||||
let vd4 : dt_biv(Cat) = DT_BIV(f_u_to_u) : dt_biv(Cat) // success
|
||||
|
||||
let ve1 : dt_inv_sep(Animal) = DT_INV_SEP_A(f_a_to_u) // success
|
||||
let ve2 : dt_inv_sep(Animal) = DT_INV_SEP_A(f_c_to_u) // fail
|
||||
let ve3 : dt_inv_sep(Animal) = DT_INV_SEP_B(f_u_to_a) // success
|
||||
let ve4 : dt_inv_sep(Animal) = DT_INV_SEP_B(f_u_to_c) // fail
|
||||
let ve5 : dt_inv_sep(Cat) = DT_INV_SEP_A(f_a_to_u) // fail
|
||||
let ve6 : dt_inv_sep(Cat) = DT_INV_SEP_A(f_c_to_u) // success
|
||||
let ve7 : dt_inv_sep(Cat) = DT_INV_SEP_B(f_u_to_a) // fail
|
||||
let ve8 : dt_inv_sep(Cat) = DT_INV_SEP_B(f_u_to_c) // success
|
||||
|
||||
let vf1 : dt_co_nest_a(Animal) = DT_CO_NEST_A(f_dt_contra_a_to_u) // success
|
||||
let vf2 : dt_co_nest_a(Animal) = DT_CO_NEST_A(f_dt_contra_c_to_u) // success
|
||||
let vf3 : dt_co_nest_a(Cat) = DT_CO_NEST_A(f_dt_contra_a_to_u) // fail
|
||||
let vf4 : dt_co_nest_a(Cat) = DT_CO_NEST_A(f_dt_contra_c_to_u) // success
|
||||
|
||||
let vg1 : dt_contra_nest_a(Animal) = DT_CONTRA_NEST_A(f_dt_co_a_to_u) // success
|
||||
let vg2 : dt_contra_nest_a(Animal) = DT_CONTRA_NEST_A(f_dt_co_c_to_u) // fail
|
||||
let vg3 : dt_contra_nest_a(Cat) = DT_CONTRA_NEST_A(f_dt_co_a_to_u) // success
|
||||
let vg4 : dt_contra_nest_a(Cat) = DT_CONTRA_NEST_A(f_dt_co_c_to_u) // success
|
||||
|
||||
let vh1 : dt_contra_nest_b(Animal) = DT_CONTRA_NEST_B(f_u_to_dt_contra_a) // success
|
||||
let vh2 : dt_contra_nest_b(Animal) = DT_CONTRA_NEST_B(f_u_to_dt_contra_c) // fail
|
||||
let vh3 : dt_contra_nest_b(Cat) = DT_CONTRA_NEST_B(f_u_to_dt_contra_a) // success
|
||||
let vh4 : dt_contra_nest_b(Cat) = DT_CONTRA_NEST_B(f_u_to_dt_contra_c) // success
|
||||
|
||||
let vi1 : dt_co_nest_b(Animal) = DT_CO_NEST_B(f_u_to_dt_co_a) // success
|
||||
let vi2 : dt_co_nest_b(Animal) = DT_CO_NEST_B(f_u_to_dt_co_c) // success
|
||||
let vi3 : dt_co_nest_b(Cat) = DT_CO_NEST_B(f_u_to_dt_co_a) // fail
|
||||
let vi4 : dt_co_nest_b(Cat) = DT_CO_NEST_B(f_u_to_dt_co_c) // success
|
||||
|
||||
let vj1 : dt_co_twice(Animal) = DT_CO_TWICE(f_a_to_u_to_c : (Animal => unit) => Animal) : dt_co_twice(Animal) // success
|
||||
let vj2 : dt_co_twice(Animal) = DT_CO_TWICE(f_c_to_u_to_c : (Cat => unit) => Cat ) : dt_co_twice(Cat) // success
|
||||
let vj3 : dt_co_twice(Cat) = DT_CO_TWICE(f_c_to_u_to_a : (Animal => unit) => Animal) : dt_co_twice(Animal) // fail
|
||||
let vj4 : dt_co_twice(Cat) = DT_CO_TWICE(f_c_to_u_to_c : (Cat => unit) => Cat ) : dt_co_twice(Cat) // success
|
||||
|
||||
let vk01 : dt_a_contra_b_contra(Animal, Animal) = DT_A_CONTRA_B_CONTRA(f_a_to_a_to_u) // success
|
||||
let vk02 : dt_a_contra_b_contra(Animal, Animal) = DT_A_CONTRA_B_CONTRA(f_a_to_c_to_u) // fail
|
||||
let vk03 : dt_a_contra_b_contra(Animal, Animal) = DT_A_CONTRA_B_CONTRA(f_c_to_a_to_u) // fail
|
||||
let vk04 : dt_a_contra_b_contra(Animal, Animal) = DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) // fail
|
||||
let vk05 : dt_a_contra_b_contra(Animal, Cat) = DT_A_CONTRA_B_CONTRA(f_a_to_a_to_u) // success
|
||||
let vk06 : dt_a_contra_b_contra(Animal, Cat) = DT_A_CONTRA_B_CONTRA(f_a_to_c_to_u) // success
|
||||
let vk07 : dt_a_contra_b_contra(Animal, Cat) = DT_A_CONTRA_B_CONTRA(f_c_to_a_to_u) // fail
|
||||
let vk08 : dt_a_contra_b_contra(Animal, Cat) = DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) // fail
|
||||
let vk09 : dt_a_contra_b_contra(Cat, Animal) = DT_A_CONTRA_B_CONTRA(f_a_to_a_to_u) // success
|
||||
let vk10 : dt_a_contra_b_contra(Cat, Animal) = DT_A_CONTRA_B_CONTRA(f_a_to_c_to_u) // fail
|
||||
let vk11 : dt_a_contra_b_contra(Cat, Animal) = DT_A_CONTRA_B_CONTRA(f_c_to_a_to_u) // success
|
||||
let vk12 : dt_a_contra_b_contra(Cat, Animal) = DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) // fail
|
||||
let vk13 : dt_a_contra_b_contra(Cat, Cat) = DT_A_CONTRA_B_CONTRA(f_a_to_a_to_u) // success
|
||||
let vk14 : dt_a_contra_b_contra(Cat, Cat) = DT_A_CONTRA_B_CONTRA(f_a_to_c_to_u) // success
|
||||
let vk15 : dt_a_contra_b_contra(Cat, Cat) = DT_A_CONTRA_B_CONTRA(f_c_to_a_to_u) // success
|
||||
let vk16 : dt_a_contra_b_contra(Cat, Cat) = DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) // success
|
||||
|
||||
let vl1 : dt_contra_twice(Animal) = DT_CONTRA_TWICE(f_a_to_a_to_u : Animal => Animal => unit) : dt_contra_twice(Animal) // success
|
||||
let vl2 : dt_contra_twice(Animal) = DT_CONTRA_TWICE(f_a_to_c_to_u : Cat => Cat => unit) : dt_contra_twice(Cat) // fail
|
||||
let vl3 : dt_contra_twice(Cat) = DT_CONTRA_TWICE(f_a_to_a_to_u : Animal => Animal => unit) : dt_contra_twice(Animal) // success
|
||||
let vl4 : dt_contra_twice(Cat) = DT_CONTRA_TWICE(f_c_to_a_to_u : Cat => Cat => unit) : dt_contra_twice(Cat) // success
|
||||
|
||||
()
|
||||
@@ -0,0 +1,47 @@
|
||||
contract interface Animal =
|
||||
entrypoint sound : () => string
|
||||
|
||||
contract Cat : Animal =
|
||||
entrypoint sound() = "meow"
|
||||
|
||||
main contract Main =
|
||||
entrypoint oracle() = ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||
|
||||
entrypoint query() = oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||
|
||||
entrypoint init() =
|
||||
let o01 : oracle(Animal, Animal) = oracle() : oracle(Animal, Animal) // success
|
||||
let o02 : oracle(Animal, Animal) = oracle() : oracle(Animal, Cat) // success
|
||||
let o03 : oracle(Animal, Animal) = oracle() : oracle(Cat, Animal) // fail
|
||||
let o04 : oracle(Animal, Animal) = oracle() : oracle(Cat, Cat) // fail
|
||||
let o05 : oracle(Animal, Cat) = oracle() : oracle(Animal, Animal) // fail
|
||||
let o06 : oracle(Animal, Cat) = oracle() : oracle(Animal, Cat) // success
|
||||
let o07 : oracle(Animal, Cat) = oracle() : oracle(Cat, Animal) // fail
|
||||
let o08 : oracle(Animal, Cat) = oracle() : oracle(Cat, Cat) // fail
|
||||
let o09 : oracle(Cat, Animal) = oracle() : oracle(Animal, Animal) // success
|
||||
let o10 : oracle(Cat, Animal) = oracle() : oracle(Animal, Cat) // success
|
||||
let o11 : oracle(Cat, Animal) = oracle() : oracle(Cat, Animal) // success
|
||||
let o12 : oracle(Cat, Animal) = oracle() : oracle(Cat, Cat) // success
|
||||
let o13 : oracle(Cat, Cat) = oracle() : oracle(Animal, Animal) // fail
|
||||
let o14 : oracle(Cat, Cat) = oracle() : oracle(Animal, Cat) // success
|
||||
let o15 : oracle(Cat, Cat) = oracle() : oracle(Cat, Animal) // fail
|
||||
let o16 : oracle(Cat, Cat) = oracle() : oracle(Cat, Cat) // success
|
||||
|
||||
let q01 : oracle_query(Animal, Animal) = query() : oracle_query(Animal, Animal) // success
|
||||
let q02 : oracle_query(Animal, Animal) = query() : oracle_query(Animal, Cat) // success
|
||||
let q03 : oracle_query(Animal, Animal) = query() : oracle_query(Cat, Animal) // success
|
||||
let q04 : oracle_query(Animal, Animal) = query() : oracle_query(Cat, Cat) // success
|
||||
let q05 : oracle_query(Animal, Cat) = query() : oracle_query(Animal, Animal) // fail
|
||||
let q06 : oracle_query(Animal, Cat) = query() : oracle_query(Animal, Cat) // success
|
||||
let q07 : oracle_query(Animal, Cat) = query() : oracle_query(Cat, Animal) // fail
|
||||
let q08 : oracle_query(Animal, Cat) = query() : oracle_query(Cat, Cat) // success
|
||||
let q09 : oracle_query(Cat, Animal) = query() : oracle_query(Animal, Animal) // fail
|
||||
let q10 : oracle_query(Cat, Animal) = query() : oracle_query(Animal, Cat) // fail
|
||||
let q11 : oracle_query(Cat, Animal) = query() : oracle_query(Cat, Animal) // success
|
||||
let q12 : oracle_query(Cat, Animal) = query() : oracle_query(Cat, Cat) // success
|
||||
let q13 : oracle_query(Cat, Cat) = query() : oracle_query(Animal, Animal) // fail
|
||||
let q14 : oracle_query(Cat, Cat) = query() : oracle_query(Animal, Cat) // fail
|
||||
let q15 : oracle_query(Cat, Cat) = query() : oracle_query(Cat, Animal) // fail
|
||||
let q16 : oracle_query(Cat, Cat) = query() : oracle_query(Cat, Cat) // success
|
||||
|
||||
()
|
||||
@@ -0,0 +1,51 @@
|
||||
contract interface Animal =
|
||||
entrypoint sound : () => string
|
||||
|
||||
contract Cat : Animal =
|
||||
entrypoint sound() = "meow"
|
||||
|
||||
main contract Main =
|
||||
record rec_co('a) = { x : 'a ,
|
||||
y : () => 'a }
|
||||
record rec_contra('a) = { x : 'a => unit }
|
||||
record rec_inv('a) = { x : 'a => unit,
|
||||
y : () => 'a }
|
||||
record rec_biv('a) = { x : int }
|
||||
|
||||
stateful entrypoint new_cat() : Cat = Chain.create()
|
||||
stateful entrypoint new_animal() : Animal = new_cat()
|
||||
stateful entrypoint animal_to_unit(_ : Animal) : unit = ()
|
||||
stateful entrypoint cat_to_unit(_ : Cat) : unit = ()
|
||||
stateful entrypoint unit_to_animal() : Animal = new_animal()
|
||||
stateful entrypoint unit_to_cat() : Cat = new_cat()
|
||||
|
||||
stateful entrypoint init() =
|
||||
let ra : rec_co(Animal) = { x = new_animal(), y = unit_to_animal }
|
||||
let rc : rec_co(Cat) = { x = new_cat(), y = unit_to_cat }
|
||||
let r01 : rec_co(Animal) = ra // success
|
||||
let r02 : rec_co(Animal) = rc // success
|
||||
let r03 : rec_co(Cat) = ra // fail
|
||||
let r04 : rec_co(Cat) = rc // sucess
|
||||
|
||||
let ratu : rec_contra(Animal) = { x = animal_to_unit }
|
||||
let rctu : rec_contra(Cat) = { x = cat_to_unit }
|
||||
let r05 : rec_contra(Animal) = ratu // success
|
||||
let r06 : rec_contra(Animal) = rctu // fail
|
||||
let r07 : rec_contra(Cat) = ratu // success
|
||||
let r08 : rec_contra(Cat) = rctu // success
|
||||
|
||||
let rxaya : rec_inv(Animal) = { x = animal_to_unit, y = unit_to_animal }
|
||||
let rxcyc : rec_inv(Cat) = { x = cat_to_unit, y = unit_to_cat }
|
||||
let r09 : rec_inv(Animal) = rxaya // success
|
||||
let r10 : rec_inv(Animal) = rxcyc // fail
|
||||
let r11 : rec_inv(Cat) = rxaya // fail
|
||||
let r12 : rec_inv(Cat) = rxcyc // success
|
||||
|
||||
let rba : rec_biv(Animal) = { x = 1 }
|
||||
let rbc : rec_biv(Cat) = { x = 1 }
|
||||
let r13 : rec_biv(Animal) = rba // success
|
||||
let r14 : rec_biv(Animal) = rbc // success
|
||||
let r15 : rec_biv(Cat) = rba // success
|
||||
let r16 : rec_biv(Cat) = rbc // success
|
||||
|
||||
()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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} }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -9,4 +9,4 @@ contract IntegerHolder =
|
||||
entrypoint get() = state
|
||||
|
||||
main contract Test =
|
||||
stateful entrypoint f(c) = Chain.clone(ref=c, 123)
|
||||
stateful entrypoint f(c) = Chain.clone(ref=c, 123)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -1,7 +0,0 @@
|
||||
contract ValueOnErr =
|
||||
|
||||
public function err() : int =
|
||||
switch(0) 1 => 5
|
||||
|
||||
public function ok() : int =
|
||||
11
|
||||
@@ -0,0 +1,60 @@
|
||||
// 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
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user