Merge pull request #285 from aeternity/lima-master-merge

Lima master merge
This commit is contained in:
Radosław Rowicki 2020-10-15 19:50:48 +02:00 committed by GitHub
commit 1dfc349065
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 259 additions and 111 deletions

View File

@ -1,12 +1,11 @@
# aesophia # aesophia
This is the __sophia__ compiler for the æternity system which compiles contracts written in __sophia__ code to the æternity VM code. This is the __sophia__ compiler for the æternity system which compiles contracts written in __sophia__ to [FATE](https://github.com/aeternity/protocol/blob/master/contracts/fate.md) instructions.
It is an OTP application written in Erlang and is by default included in
[the æternity node](https://github.com/aeternity/epoch). However, it can
also be included in other systems to compile contracts coded in sophia which
can then be loaded into the æternity system.
The compiler is currently being used three places
- [The command line compiler](https://github.com/aeternity/aesophia_cli)
- [The HTTP compiler](https://github.com/aeternity/aesophia_http)
- In [Aeternity node](https://github.com/aeternity/aeternity) tests
## Documentation ## Documentation
@ -14,7 +13,6 @@ can then be loaded into the æternity system.
* [Sophia Documentation](docs/sophia.md). * [Sophia Documentation](docs/sophia.md).
* [Sophia Standard Library](docs/sophia_stdlib.md). * [Sophia Standard Library](docs/sophia_stdlib.md).
## Versioning ## Versioning
`aesophia` has a version that is only loosely connected to the version of the `aesophia` has a version that is only loosely connected to the version of the
@ -23,7 +21,6 @@ minor/patch version. The `aesophia` compiler version MUST be bumped whenever
there is a change in how byte code is generated, but it MAY also be bumped upon there is a change in how byte code is generated, but it MAY also be bumped upon
API changes etc. API changes etc.
## Interface Modules ## Interface Modules
The basic modules for interfacing the compiler: The basic modules for interfacing the compiler:

View File

@ -83,7 +83,7 @@ The main unit of code in Sophia is the *contract*.
- A contract may define a type `state` encapsulating its local - A contract may define a type `state` encapsulating its local
state. When creating a new contract the `init` entrypoint is executed and the state. When creating a new contract the `init` entrypoint is executed and the
state is initialized to its return value. state is initialized to its return value.
The language offers some primitive functions to interact with the blockchain and contracts. The language offers some primitive functions to interact with the blockchain and contracts.
Please refer to the [Chain](sophia_stdlib.md#Chain), [Contract](sophia_stdlib.md#Contract) Please refer to the [Chain](sophia_stdlib.md#Chain), [Contract](sophia_stdlib.md#Contract)
and the [Call](sophia_stdlib.md#Call) namespaces in the documentation. and the [Call](sophia_stdlib.md#Call) namespaces in the documentation.
@ -314,7 +314,7 @@ so even cyclic includes should be working without any special tinkering.
### Standard library ### Standard library
Sophia offers [standard library](sophia_stdlib.md) which exposes some Sophia offers [standard library](sophia_stdlib.md) which exposes some
primitive operations and some higher level utilities. The builtin primitive operations and some higher level utilities. The builtin
namespaces like `Chain`, `Contract`, `Map` namespaces like `Chain`, `Contract`, `Map`
are included by default and are supported internally by the compiler. are included by default and are supported internally by the compiler.
@ -481,7 +481,7 @@ Example syntax:
Lists can be constructed using the range syntax using special `..` operator: Lists can be constructed using the range syntax using special `..` operator:
``` ```
[1..4] == [1,2,3,4] [1..4] == [1,2,3,4]
``` ```
The ranges are always ascending and have step equal to 1. The ranges are always ascending and have step equal to 1.
@ -595,7 +595,7 @@ Please refer to the `Bytes` [library documentation](sophia_stdlib.md#Bytes).
### Cryptographic builins ### Cryptographic builins
Libraries [Crypto](sophia_stdlib.md#Crypto) and [String](sophia_stdlib.md#String) provide functions to Libraries [Crypto](sophia_stdlib.md#Crypto) and [String](sophia_stdlib.md#String) provide functions to
hash objects, verify signatures etc. The `hash` is a type alias for `bytes(32)`. hash objects, verify signatures etc. The `hash` is a type alias for `bytes(32)`.
#### AEVM note #### AEVM note
@ -630,7 +630,7 @@ Example for an oracle answering questions of type `string` with answers of type
contract Oracles = contract Oracles =
stateful entrypoint registerOracle(acct : address, stateful entrypoint registerOracle(acct : address,
sign : signature, // Signed oracle address + contract address sign : signature, // Signed network id + oracle address + contract address
qfee : int, qfee : int,
ttl : Chain.ttl) : oracle(string, int) = ttl : Chain.ttl) : oracle(string, int) =
Oracle.register(acct, signature = sign, qfee, ttl) Oracle.register(acct, signature = sign, qfee, ttl)
@ -651,13 +651,13 @@ contract Oracles =
Oracle.extend(o, ttl) Oracle.extend(o, ttl)
stateful entrypoint signExtendOracle(o : oracle(string, int), stateful entrypoint signExtendOracle(o : oracle(string, int),
sign : signature, // Signed oracle address + contract address sign : signature, // Signed network id + oracle address + contract address
ttl : Chain.ttl) : unit = ttl : Chain.ttl) : unit =
Oracle.extend(o, signature = sign, ttl) Oracle.extend(o, signature = sign, ttl)
stateful entrypoint respond(o : oracle(string, int), stateful entrypoint respond(o : oracle(string, int),
q : oracle_query(string, int), q : oracle_query(string, int),
sign : signature, // Signed oracle query id + contract address sign : signature, // Signed network id + oracle query id + contract address
r : int) = r : int) =
Oracle.respond(o, q, signature = sign, r) Oracle.respond(o, q, signature = sign, r)
@ -678,7 +678,7 @@ contract Oracles =
#### Sanity checks #### Sanity checks
When an Oracle literal is passed to a contract, no deep checks are performed. When an Oracle literal is passed to a contract, no deep checks are performed.
For extra safety [Oracle.check](sophia_stdlib.md#check) and [Oracle.check_query](sophia_stdlib.md#check_query) For extra safety [Oracle.check](sophia_stdlib.md#check) and [Oracle.check_query](sophia_stdlib.md#check_query)
functions are provided. functions are provided.
@ -738,7 +738,7 @@ To use events a contract must declare a datatype `event`, and events are then
logged using the `Chain.event` function: logged using the `Chain.event` function:
``` ```
datatype event datatype event
= Event1(int, int, string) = Event1(int, int, string)
| Event2(string, address) | Event2(string, address)

View File

@ -347,7 +347,7 @@ Registers new oracle answering questions of type `'a` with answers of type `'b`.
* The `acct` is the address of the oracle to register (can be the same as the contract). * The `acct` is the address of the oracle to register (can be the same as the contract).
* `signature` is a signature proving that the contract is allowed to register the account - * `signature` is a signature proving that the contract is allowed to register the account -
the account address + the contract address (concatenated as byte arrays) is the `network id` + `account address` + `contract address` (concatenated as byte arrays) is
[signed](./sophia.md#delegation-signature) with the [signed](./sophia.md#delegation-signature) with the
private key of the account, proving you have the private key of the oracle to be. If the private key of the account, proving you have the private key of the oracle to be. If the
address is the same as the contract `sign` is ignored and can be left out entirely. address is the same as the contract `sign` is ignored and can be left out entirely.
@ -381,7 +381,8 @@ Responds to the question `q` on `o`.
Unless the contract address is the same as the oracle address the `signature` Unless the contract address is the same as the oracle address the `signature`
(which is an optional, named argument) (which is an optional, named argument)
needs to be provided. Proving that we have the private key of the oracle by needs to be provided. Proving that we have the private key of the oracle by
[signing](./sophia.md#delegation-signature) the oracle query id + contract address [signing](./sophia.md#delegation-signature)
the `network id` + `oracle query id` + `contract address`
#### extend #### extend
@ -455,7 +456,8 @@ Naming System (AENS).
If `owner` is equal to `Contract.address` the signature `signature` is If `owner` is equal to `Contract.address` the signature `signature` is
ignored, and can be left out since it is a named argument. Otherwise we need ignored, and can be left out since it is a named argument. Otherwise we need
a signature to prove that we are allowed to do AENS operations on behalf of a signature to prove that we are allowed to do AENS operations on behalf of
`owner` `owner`. The [signature is tied to a network id](https://github.com/aeternity/protocol/blob/iris/consensus/consensus.md#transaction-signature),
i.e. the signature material should be prefixed by the network id.
### Types ### Types
@ -505,8 +507,8 @@ let Some(Name(owner, FixedTTL(expiry), ptrs)) = AENS.lookup("example.chain")
AENS.preclaim(owner : address, commitment_hash : hash, <signature : signature>) : unit AENS.preclaim(owner : address, commitment_hash : hash, <signature : signature>) : unit
``` ```
The [signature](./sophia.md#delegation-signature) should be over `owner address` + `Contract.address` The [signature](./sophia.md#delegation-signature) should be over
(concatenated as byte arrays). `network id` + `owner address` + `Contract.address` (concatenated as byte arrays).
#### claim #### claim
@ -514,7 +516,9 @@ The [signature](./sophia.md#delegation-signature) should be over `owner address`
AENS.claim(owner : address, name : string, salt : int, name_fee : int, <signature : signature>) : unit AENS.claim(owner : address, name : string, salt : int, name_fee : int, <signature : signature>) : unit
``` ```
The [signature](./sophia.md#delegation-signature) should be over `owner address` + `name_hash` + `Contract.address` The [signature](./sophia.md#delegation-signature) should be over
`network id` + `owner address` + `name_hash` + `Contract.address`
(concatenated as byte arrays)
using the private key of the `owner` account for signing. using the private key of the `owner` account for signing.
@ -525,7 +529,9 @@ AENS.transfer(owner : address, new_owner : address, name : string, <signature :
Transfers name to the new owner. Transfers name to the new owner.
The [signature](./sophia.md#delegation-signature) should be over `owner address` + `name_hash` + `Contract.address` The [signature](./sophia.md#delegation-signature) should be over
`network id` + `owner address` + `name_hash` + `Contract.address`
(concatenated as byte arrays)
using the private key of the `owner` account for signing. using the private key of the `owner` account for signing.
@ -536,7 +542,9 @@ AENS.revoke(owner : address, name : string, <signature : signature>) : unit
Revokes the name to extend the ownership time. Revokes the name to extend the ownership time.
The [signature](./sophia.md#delegation-signature) should be over `owner address` + `name_hash` + `Contract.address` The [signature](./sophia.md#delegation-signature) should be over
`network id` + `owner address` + `name_hash` + `Contract.address`
(concatenated as byte arrays)
using the private key of the `owner` account for signing. using the private key of the `owner` account for signing.
@ -773,6 +781,13 @@ List.last(l : list('a)) : option('a)
Returns `Some` of the last element of a list or `None` if the list is empty. Returns `Some` of the last element of a list or `None` if the list is empty.
#### contains
```
List.contains(e : 'a, l : list('a)) : bool
```
Checks if list `l` contains element `e`. Equivalent to `List.find(x => x == e, l) != None`.
#### find #### find
``` ```
List.find(p : 'a => bool, l : list('a)) : option('a) List.find(p : 'a => bool, l : list('a)) : option('a)
@ -1113,6 +1128,13 @@ Option.force(o : option('a)) : 'a
Forcefully escapes `option` wrapping assuming it is `Some`. Throws error on `None`. Forcefully escapes `option` wrapping assuming it is `Some`. Throws error on `None`.
#### contains
```
Option.contains(e : 'a, o : option('a)) : bool
```
Returns `true` if and only if `o` contains element equal to `e`. Equivalent to `Option.match(false, x => x == e, o)`.
#### on_elem #### on_elem
``` ```
Option.on_elem(o : option('a), f : 'a => unit) : unit Option.on_elem(o : option('a), f : 'a => unit) : unit

View File

@ -15,8 +15,8 @@ namespace List =
_::t => Some(t) _::t => Some(t)
function last(l : list('a)) : option('a) = switch(l) function last(l : list('a)) : option('a) = switch(l)
[] => None [] => None
[x] => Some(x) [x] => Some(x)
_::t => last(t) _::t => last(t)
function drop_last(l : list('a)) : option(list('a)) = switch(l) function drop_last(l : list('a)) : option(list('a)) = switch(l)
@ -28,6 +28,11 @@ namespace List =
h::t => h::drop_last_unsafe(t) h::t => h::drop_last_unsafe(t)
[] => abort("drop_last_unsafe: list empty") [] => abort("drop_last_unsafe: list empty")
function contains(e : 'a, l : list('a)) = switch(l)
[] => false
h::t => h == e || contains(e, t)
/** Finds first element of `l` fulfilling predicate `p` as `Some` or `None` /** Finds first element of `l` fulfilling predicate `p` as `Some` or `None`
* if no such element exists. * if no such element exists.
*/ */

View File

@ -22,7 +22,11 @@ namespace Option =
/** Assume it is `Some` /** Assume it is `Some`
*/ */
function force(o : option('a)) : 'a = default(abort("Forced None value"), o) function force(o : option('a)) : 'a = switch(o)
None => abort("Forced None value")
Some(x) => x
function contains(e : 'a, o : option('a)) = o == Some(e)
function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o) function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o)

View File

@ -14,6 +14,8 @@
, contract_interface/2 , contract_interface/2
, contract_interface/3 , contract_interface/3
, from_typed_ast/2
, render_aci_json/1 , render_aci_json/1
, json_encode_expr/1 , json_encode_expr/1
@ -23,6 +25,8 @@
-type json() :: jsx:json_term(). -type json() :: jsx:json_term().
-type json_text() :: binary(). -type json_text() :: binary().
-export_type([aci_type/0]).
%% External API %% External API
-spec file(aci_type(), string()) -> {ok, json() | string()} | {error, term()}. -spec file(aci_type(), string()) -> {ok, json() | string()} | {error, term()}.
file(Type, File) -> file(Type, File) ->
@ -65,18 +69,20 @@ do_contract_interface(Type, ContractString, Options) ->
try try
Ast = aeso_compiler:parse(ContractString, Options), Ast = aeso_compiler:parse(ContractString, Options),
%% io:format("~p\n", [Ast]), %% io:format("~p\n", [Ast]),
TypedAst = aeso_ast_infer_types:infer(Ast, [dont_unfold]), {TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
%% io:format("~p\n", [TypedAst]), %% io:format("~p\n", [TypedAst]),
JArray = [ encode_contract(C) || C <- TypedAst ], from_typed_ast(Type, TypedAst)
case Type of
json -> {ok, JArray};
string -> do_render_aci_json(JArray)
end
catch catch
throw:{error, Errors} -> {error, Errors} throw:{error, Errors} -> {error, Errors}
end. end.
from_typed_ast(Type, TypedAst) ->
JArray = [ encode_contract(C) || C <- TypedAst ],
case Type of
json -> {ok, JArray};
string -> do_render_aci_json(JArray)
end.
encode_contract(Contract = {contract, _, {con, _, Name}, _}) -> encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
C0 = #{name => encode_name(Name)}, C0 = #{name => encode_name(Name)},

View File

@ -679,17 +679,21 @@ global_env() ->
option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}. option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}.
map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}. map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
-spec infer(aeso_syntax:ast()) -> aeso_syntax:ast() | {env(), aeso_syntax:ast()}. -spec infer(aeso_syntax:ast()) -> {aeso_syntax:ast(), aeso_syntax:ast()} | {env(), aeso_syntax:ast(), aeso_syntax:ast()}.
infer(Contracts) -> infer(Contracts) ->
infer(Contracts, []). infer(Contracts, []).
-type option() :: return_env | dont_unfold | no_code | term(). -type option() :: return_env | dont_unfold | no_code | debug_mode | term().
-spec init_env(list(option())) -> env(). -spec init_env(list(option())) -> env().
init_env(_Options) -> global_env(). init_env(_Options) -> global_env().
-spec infer(aeso_syntax:ast(), list(option())) -> -spec infer(aeso_syntax:ast(), list(option())) ->
aeso_syntax:ast() | {env(), aeso_syntax:ast()}. {aeso_syntax:ast(), aeso_syntax:ast()} | {env(), aeso_syntax:ast(), aeso_syntax:ast()}.
infer([], Options) ->
create_type_errors(),
type_error({no_decls, proplists:get_value(src_file, Options, no_file)}),
destroy_and_report_type_errors(init_env(Options));
infer(Contracts, Options) -> infer(Contracts, Options) ->
ets_init(), %% Init the ETS table state ets_init(), %% Init the ETS table state
try try
@ -698,15 +702,15 @@ infer(Contracts, Options) ->
ets_new(type_vars, [set]), ets_new(type_vars, [set]),
check_modifiers(Env, Contracts), check_modifiers(Env, Contracts),
{Env1, Decls} = infer1(Env, Contracts, [], Options), {Env1, Decls} = infer1(Env, Contracts, [], Options),
{Env2, Decls2} = {Env2, DeclsFolded, DeclsUnfolded} =
case proplists:get_value(dont_unfold, Options, false) of case proplists:get_value(dont_unfold, Options, false) of
true -> {Env1, Decls}; true -> {Env1, Decls, Decls};
false -> E = on_scopes(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end), false -> E = on_scopes(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end),
{E, unfold_record_types(E, Decls)} {E, Decls, unfold_record_types(E, Decls)}
end, end,
case proplists:get_value(return_env, Options, false) of case proplists:get_value(return_env, Options, false) of
false -> Decls2; false -> {DeclsFolded, DeclsUnfolded};
true -> {Env2, Decls2} true -> {Env2, DeclsFolded, DeclsUnfolded}
end end
after after
clean_up_ets() clean_up_ets()
@ -744,15 +748,21 @@ check_scope_name_clash(Env, Kind, Name) ->
-spec infer_contract_top(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) -> -spec infer_contract_top(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) ->
{env(), [aeso_syntax:decl()]}. {env(), [aeso_syntax:decl()]}.
infer_contract_top(Env, Kind, Defs0, _Options) -> infer_contract_top(Env, Kind, Defs0, Options) ->
Defs = desugar(Defs0), Defs = desugar(Defs0),
infer_contract(Env, Kind, Defs). infer_contract(Env, Kind, Defs, Options).
%% infer_contract takes a proplist mapping global names to types, and %% infer_contract takes a proplist mapping global names to types, and
%% a list of definitions. %% a list of definitions.
-spec infer_contract(env(), main_contract | contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. -spec infer_contract(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) -> {env(), [aeso_syntax:decl()]}.
infer_contract(Env0, What, Defs0) -> infer_contract(Env0, What, Defs0, Options) ->
Defs = process_blocks(Defs0), create_type_errors(),
Defs01 = process_blocks(Defs0),
Defs = case lists:member(debug_mode, Options) of
true -> expose_internals(Defs01, What);
false -> Defs01
end,
destroy_and_report_type_errors(Env0),
Env = Env0#env{ what = What }, Env = Env0#env{ what = What },
Kind = fun({type_def, _, _, _, _}) -> type; Kind = fun({type_def, _, _, _, _}) -> type;
({letfun, _, _, _, _, _}) -> function; ({letfun, _, _, _, _, _}) -> function;
@ -799,20 +809,34 @@ process_blocks(Decls) ->
-spec process_block(aeso_syntax:ann(), [aeso_syntax:decl()]) -> [aeso_syntax:decl()]. -spec process_block(aeso_syntax:ann(), [aeso_syntax:decl()]) -> [aeso_syntax:decl()].
process_block(_, []) -> []; process_block(_, []) -> [];
process_block(_, [Decl]) -> [Decl]; process_block(_, [Decl]) -> [Decl];
process_block(Ann, [Decl | Decls]) -> process_block(_Ann, [Decl | Decls]) ->
IsThis = fun(Name) -> fun({letfun, _, {id, _, Name1}, _, _, _}) -> Name == Name1; IsThis = fun(Name) -> fun({letfun, _, {id, _, Name1}, _, _, _}) -> Name == Name1;
(_) -> false end end, (_) -> false end end,
case Decl of case Decl of
{fun_decl, Ann1, Id = {id, _, Name}, Type} -> {fun_decl, Ann1, Id = {id, _, Name}, Type} ->
{Clauses, Rest} = lists:splitwith(IsThis(Name), Decls), {Clauses, Rest} = lists:splitwith(IsThis(Name), Decls),
[{fun_clauses, Ann1, Id, Type, Clauses} | [type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest],
process_block(Ann, Rest)]; [{fun_clauses, Ann1, Id, Type, Clauses}];
{letfun, Ann1, Id = {id, _, Name}, _, _, _} -> {letfun, Ann1, Id = {id, _, Name}, _, _, _} ->
{Clauses, Rest} = lists:splitwith(IsThis(Name), [Decl | Decls]), {Clauses, Rest} = lists:splitwith(IsThis(Name), [Decl | Decls]),
[{fun_clauses, Ann1, Id, {id, [{origin, system} | Ann1], "_"}, Clauses} | [type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest],
process_block(Ann, Rest)] [{fun_clauses, Ann1, Id, {id, [{origin, system} | Ann1], "_"}, Clauses}]
end. end.
%% Turns private stuff into public stuff
expose_internals(Defs, What) ->
[ begin
Ann = element(2, Def),
NewAnn = case What of
namespace -> [A ||A <- Ann, A /= {private, true}, A /= private];
main_contract -> [{entrypoint, true}|Ann]; % minor duplication
contract -> Ann
end,
setelement(2, Def, NewAnn)
end
|| Def <- Defs
].
-spec check_typedefs(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. -spec check_typedefs(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
check_typedefs(Env = #env{ namespace = Ns }, Defs) -> check_typedefs(Env = #env{ namespace = Ns }, Defs) ->
create_type_errors(), create_type_errors(),
@ -2449,6 +2473,12 @@ mk_t_err(Pos, Msg) ->
mk_t_err(Pos, Msg, Ctxt) -> mk_t_err(Pos, Msg, Ctxt) ->
aeso_errors:new(type_error, Pos, lists:flatten(Msg), lists:flatten(Ctxt)). aeso_errors:new(type_error, Pos, lists:flatten(Msg), lists:flatten(Ctxt)).
mk_error({no_decls, File}) ->
Pos = aeso_errors:pos(File, 0, 0),
mk_t_err(Pos, "Empty contract\n");
mk_error({mismatched_decl_in_funblock, Name, Decl}) ->
Msg = io_lib:format("Mismatch in the function block. Expected implementation/type declaration of ~s function\n", [Name]),
mk_t_err(pos(Decl), Msg);
mk_error({higher_kinded_typevar, T}) -> mk_error({higher_kinded_typevar, T}) ->
Msg = io_lib:format("Type ~s is a higher kinded type variable\n" Msg = io_lib:format("Type ~s is a higher kinded type variable\n"
"(takes another type as an argument)\n", [pp(instantiate(T))] "(takes another type as an argument)\n", [pp(instantiate(T))]

View File

@ -38,11 +38,13 @@
| pp_assembler | pp_assembler
| pp_bytecode | pp_bytecode
| no_code | no_code
| keep_included | keep_included
| debug_mode
| {backend, aevm | fate} | {backend, aevm | fate}
| {include, {file_system, [string()]} | | {include, {file_system, [string()]} |
{explicit_files, #{string() => binary()}}} {explicit_files, #{string() => binary()}}}
| {src_file, string()}. | {src_file, string()}
| {aci, aeso_aci:aci_type()}.
-type options() :: [option()]. -type options() :: [option()].
-export_type([ option/0 -export_type([ option/0
@ -116,7 +118,8 @@ from_string(Backend, ContractString, Options) ->
end. end.
from_string1(aevm, ContractString, Options) -> from_string1(aevm, ContractString, Options) ->
#{icode := Icode} = string_to_code(ContractString, Options), #{ icode := Icode
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
TypeInfo = extract_type_info(Icode), TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options), Assembler = assemble(Icode, Options),
pp_assembler(aevm, Assembler, Options), pp_assembler(aevm, Assembler, Options),
@ -124,47 +127,63 @@ from_string1(aevm, ContractString, Options) ->
ByteCode = << << B:8 >> || B <- ByteCodeList >>, ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options), pp_bytecode(ByteCode, Options),
{ok, Version} = version(), {ok, Version} = version(),
{ok, #{byte_code => ByteCode, Res = #{byte_code => ByteCode,
compiler_version => Version, compiler_version => Version,
contract_source => ContractString, contract_source => ContractString,
type_info => TypeInfo, type_info => TypeInfo,
abi_version => aeb_aevm_abi:abi_version(), abi_version => aeb_aevm_abi:abi_version(),
payable => maps:get(payable, Icode) payable => maps:get(payable, Icode)
}}; },
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)};
from_string1(fate, ContractString, Options) -> from_string1(fate, ContractString, Options) ->
#{fcode := FCode} = string_to_code(ContractString, Options), #{ fcode := FCode
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
FateCode = aeso_fcode_to_fate:compile(FCode, Options), FateCode = aeso_fcode_to_fate:compile(FCode, Options),
pp_assembler(fate, FateCode, Options), pp_assembler(fate, FateCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []), ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(), {ok, Version} = version(),
{ok, #{byte_code => ByteCode, Res = #{byte_code => ByteCode,
compiler_version => Version, compiler_version => Version,
contract_source => ContractString, contract_source => ContractString,
type_info => [], type_info => [],
fate_code => FateCode, fate_code => FateCode,
abi_version => aeb_fate_abi:abi_version(), abi_version => aeb_fate_abi:abi_version(),
payable => maps:get(payable, FCode) payable => maps:get(payable, FCode)
}}. },
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
maybe_generate_aci(Result, FoldedTypedAst, Options) ->
case proplists:get_value(aci, Options) of
undefined ->
Result;
Type ->
{ok, Aci} = aeso_aci:from_typed_ast(Type, FoldedTypedAst),
maps:put(aci, Aci, Result)
end.
-spec string_to_code(string(), options()) -> map(). -spec string_to_code(string(), options()) -> map().
string_to_code(ContractString, Options) -> string_to_code(ContractString, Options) ->
Ast = parse(ContractString, Options), Ast = parse(ContractString, Options),
pp_sophia_code(Ast, Options), pp_sophia_code(Ast, Options),
pp_ast(Ast, Options), pp_ast(Ast, Options),
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]), {TypeEnv, FoldedTypedAst, UnfoldedTypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
pp_typed_ast(TypedAst, Options), pp_typed_ast(UnfoldedTypedAst, Options),
case proplists:get_value(backend, Options, aevm) of case proplists:get_value(backend, Options, aevm) of
aevm -> aevm ->
Icode = ast_to_icode(TypedAst, Options), Icode = ast_to_icode(UnfoldedTypedAst, Options),
pp_icode(Icode, Options), pp_icode(Icode, Options),
#{ icode => Icode, #{ icode => Icode
typed_ast => TypedAst, , unfolded_typed_ast => UnfoldedTypedAst
type_env => TypeEnv}; , folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv
, ast => Ast };
fate -> fate ->
Fcode = aeso_ast_to_fcode:ast_to_fcode(TypedAst, Options), Fcode = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, Options),
#{ fcode => Fcode, #{ fcode => Fcode
typed_ast => TypedAst, , unfolded_typed_ast => UnfoldedTypedAst
type_env => TypeEnv} , folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv
, ast => Ast }
end. end.
-define(CALL_NAME, "__call"). -define(CALL_NAME, "__call").
@ -199,9 +218,9 @@ check_call1(ContractString0, FunName, Args, Options) ->
case proplists:get_value(backend, Options, aevm) of case proplists:get_value(backend, Options, aevm) of
aevm -> aevm ->
%% First check the contract without the __call function %% First check the contract without the __call function
#{} = string_to_code(ContractString0, Options), #{ast := Ast} = string_to_code(ContractString0, Options),
ContractString = insert_call_function(ContractString0, ?CALL_NAME, FunName, Args, Options), ContractString = insert_call_function(Ast, ContractString0, ?CALL_NAME, FunName, Args),
#{typed_ast := TypedAst, #{unfolded_typed_ast := TypedAst,
icode := Icode} = string_to_code(ContractString, Options), icode := Icode} = string_to_code(ContractString, Options),
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst), {ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ], ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
@ -221,13 +240,14 @@ check_call1(ContractString0, FunName, Args, Options) ->
{ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms}; {ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms};
fate -> fate ->
%% First check the contract without the __call function %% First check the contract without the __call function
#{fcode := OrgFcode} = string_to_code(ContractString0, Options), #{ fcode := OrgFcode
, ast := Ast } = string_to_code(ContractString0, Options),
FateCode = aeso_fcode_to_fate:compile(OrgFcode, []), FateCode = aeso_fcode_to_fate:compile(OrgFcode, []),
%% collect all hashes and compute the first name without hash collision to %% collect all hashes and compute the first name without hash collision to
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)), SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
CallName = first_none_match(?CALL_NAME, SymbolHashes, CallName = first_none_match(?CALL_NAME, SymbolHashes,
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)), lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
ContractString = insert_call_function(ContractString0, CallName, FunName, Args, Options), ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args),
#{fcode := Fcode} = string_to_code(ContractString, Options), #{fcode := Fcode} = string_to_code(ContractString, Options),
CallArgs = arguments_of_body(CallName, FunName, Fcode), CallArgs = arguments_of_body(CallName, FunName, Fcode),
{ok, FunName, CallArgs} {ok, FunName, CallArgs}
@ -253,9 +273,8 @@ first_none_match(CallName, Hashes, [Char|Chars]) ->
end. end.
%% Add the __call function to a contract. %% Add the __call function to a contract.
-spec insert_call_function(string(), string(), string(), [string()], options()) -> string(). -spec insert_call_function(aeso_syntax:ast(), string(), string(), string(), [string()]) -> string().
insert_call_function(Code, Call, FunName, Args, Options) -> insert_call_function(Ast, Code, Call, FunName, Args) ->
Ast = parse(Code, Options),
Ind = last_contract_indent(Ast), Ind = last_contract_indent(Ast),
lists:flatten( lists:flatten(
[ Code, [ Code,
@ -311,7 +330,7 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
Options = [no_code | Options0], Options = [no_code | Options0],
try try
Code = string_to_code(ContractString, Options), Code = string_to_code(ContractString, Options),
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code, #{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
{ok, _, Type0} = get_decode_type(FunName, TypedAst), {ok, _, Type0} = get_decode_type(FunName, TypedAst),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
@ -387,7 +406,7 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
Options = [no_code | Options0], Options = [no_code | Options0],
try try
Code = string_to_code(ContractString, Options), Code = string_to_code(ContractString, Options),
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code, #{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
{ok, Args, _} = get_decode_type(FunName, TypedAst), {ok, Args, _} = get_decode_type(FunName, TypedAst),
GetType = fun({typed, _, _, T}) -> T; (T) -> T end, GetType = fun({typed, _, _, T}) -> T; (T) -> T end,

View File

@ -39,9 +39,9 @@
| {pragma, ann(), pragma()} | {pragma, ann(), pragma()}
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs | {type_decl, ann(), id(), [tvar()]} % Only for error msgs
| {type_def, ann(), id(), [tvar()], typedef()} | {type_def, ann(), id(), [tvar()], typedef()}
| {fun_decl, ann(), id(), type()} | {fun_clauses, ann(), id(), type(), [letfun() | fundecl()]}
| {fun_clauses, ann(), id(), type(), [letbind()]}
| {block, ann(), [decl()]} | {block, ann(), [decl()]}
| fundecl()
| letfun() | letfun()
| letval(). % Only for error msgs | letval(). % Only for error msgs
@ -50,8 +50,10 @@
-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}. -type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}.
-type letval() :: {letval, ann(), pat(), expr()}. -type letval() :: {letval, ann(), pat(), expr()}.
-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}. -type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}.
-type fundecl() :: {fun_decl, ann(), id(), type()}.
-type letbind() -type letbind()
:: letfun() :: letfun()
| letval(). | letval().

View File

@ -95,6 +95,8 @@ from_fate({tuple_t, _, Types}, ?FATE_TUPLE(Val))
when length(Types) == tuple_size(Val) -> when length(Types) == tuple_size(Val) ->
{tuple, [], [from_fate(Type, X) {tuple, [], [from_fate(Type, X)
|| {Type, X} <- lists:zip(Types, tuple_to_list(Val))]}; || {Type, X} <- lists:zip(Types, tuple_to_list(Val))]};
from_fate({record_t, [{field_t, _, FName, FType}]}, Val) ->
{record, [], [{field, [], [{proj, [], FName}], from_fate(FType, Val)}]};
from_fate({record_t, Fields}, ?FATE_TUPLE(Val)) from_fate({record_t, Fields}, ?FATE_TUPLE(Val))
when length(Fields) == tuple_size(Val) -> when length(Fields) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], from_fate(FType, X)} {record, [], [ {field, [], [{proj, [], FName}], from_fate(FType, X)}

View File

@ -11,7 +11,10 @@ test_contract(N) ->
{Contract,MapACI,DecACI} = test_cases(N), {Contract,MapACI,DecACI} = test_cases(N),
{ok,JSON} = aeso_aci:contract_interface(json, Contract), {ok,JSON} = aeso_aci:contract_interface(json, Contract),
?assertEqual([MapACI], JSON), ?assertEqual([MapACI], JSON),
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)). ?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)),
%% Check if the compiler provides correct aci
{ok,#{aci := JSON2}} = aeso_compiler:from_string(Contract, [{aci, json}]),
?assertEqual(JSON, JSON2).
test_cases(1) -> test_cases(1) ->
Contract = <<"payable contract C =\n" Contract = <<"payable contract C =\n"
@ -80,11 +83,11 @@ test_cases(3) ->
DecACI = <<"contract C =\n" DecACI = <<"contract C =\n"
" type state = unit\n" " type state = unit\n"
" datatype event = SingleEventDefined\n" " datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n" " datatype bert('a) = Bin('a)\n"
" entrypoint a : (C.bert(string)) => int\n">>, " entrypoint a : (C.bert(string)) => int\n">>,
{Contract,MapACI,DecACI}. {Contract,MapACI,DecACI}.
%% Rounttrip %% Roundtrip
aci_test_() -> aci_test_() ->
[{"Testing ACI generation for " ++ ContractName, [{"Testing ACI generation for " ++ ContractName,
fun() -> aci_test_contract(ContractName) end} fun() -> aci_test_contract(ContractName) end}
@ -94,8 +97,13 @@ all_contracts() -> aeso_compiler_tests:compilable_contracts().
aci_test_contract(Name) -> aci_test_contract(Name) ->
String = aeso_test_utils:read_contract(Name), String = aeso_test_utils:read_contract(Name),
Opts = [{include, {file_system, [aeso_test_utils:contract_path()]}}], Opts = case lists:member(Name, aeso_compiler_tests:debug_mode_contracts()) of
true -> [debug_mode];
false -> []
end ++ [{include, {file_system, [aeso_test_utils:contract_path()]}}],
{ok, JSON} = aeso_aci:contract_interface(json, String, Opts), {ok, JSON} = aeso_aci:contract_interface(json, String, Opts),
{ok, #{aci := JSON1}} = aeso_compiler:from_string(String, [{aci, json}, {backend, fate} | Opts]),
?assertEqual(JSON, JSON1),
io:format("JSON:\n~p\n", [JSON]), io:format("JSON:\n~p\n", [JSON]),
{ok, ContractStub} = aeso_aci:render_aci_json(JSON), {ok, ContractStub} = aeso_aci:render_aci_json(JSON),
@ -122,4 +130,3 @@ check_stub(Stub, Options) ->
_ = [ io:format("~s\n", [aeso_errors:pp(E)]) || E <- Errs ], _ = [ io:format("~s\n", [aeso_errors:pp(E)]) || E <- Errs ],
error({parse_errors, Errs}) error({parse_errors, Errs})
end. end.

View File

@ -113,6 +113,7 @@ compilable_contracts() ->
{"funargs", "traffic_light", ["Pantone(12)"]}, {"funargs", "traffic_light", ["Pantone(12)"]},
{"funargs", "tuples", ["()"]}, {"funargs", "tuples", ["()"]},
%% TODO {"funargs", "due", ["FixedTTL(1020)"]}, %% TODO {"funargs", "due", ["FixedTTL(1020)"]},
{"funargs", "singleton_rec", ["{x = 1000}"]},
{"variant_types", "init", []}, {"variant_types", "init", []},
{"basic_auth", "init", []}, {"basic_auth", "init", []},
{"address_literals", "init", []}, {"address_literals", "init", []},

View File

@ -39,7 +39,7 @@ simple_compile_test_() ->
error(ErrBin) error(ErrBin)
end end
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate], end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
not lists:member(ContractName, not_yet_compilable(Backend))] ++ not lists:member(ContractName, not_compilable_on(Backend))] ++
[ {"Test file not found error", [ {"Test file not found error",
fun() -> fun() ->
{error, Errors} = aeso_compiler:file("does_not_exist.aes"), {error, Errors} = aeso_compiler:file("does_not_exist.aes"),
@ -110,7 +110,15 @@ compile(Backend, Name) ->
compile(Backend, Name, Options) -> compile(Backend, Name, Options) ->
String = aeso_test_utils:read_contract(Name), String = aeso_test_utils:read_contract(Name),
case aeso_compiler:from_string(String, [{src_file, Name ++ ".aes"}, {backend, Backend} | Options]) of Options1 =
case lists:member(Name, debug_mode_contracts()) of
true -> [debug_mode];
false -> []
end ++
[ {src_file, Name ++ ".aes"}, {backend, Backend}
, {include, {file_system, [aeso_test_utils:contract_path()]}}
] ++ Options,
case aeso_compiler:from_string(String, Options1) of
{ok, Map} -> Map; {ok, Map} -> Map;
{error, ErrorString} when is_binary(ErrorString) -> ErrorString; {error, ErrorString} when is_binary(ErrorString) -> ErrorString;
{error, Errors} -> Errors {error, Errors} -> Errors
@ -170,12 +178,21 @@ compilable_contracts() ->
"let_patterns", "let_patterns",
"lhs_matching", "lhs_matching",
"more_strings", "more_strings",
"protected_call" "protected_call",
"hermetization_turnoff"
]. ].
not_yet_compilable(fate) -> []; not_compilable_on(fate) -> [];
not_yet_compilable(aevm) -> ["pairing_crypto", "aens_update", "basic_auth_tx", "more_strings", not_compilable_on(aevm) ->
"unapplied_builtins", "bytes_to_x", "state_handling", "protected_call"]. [ "stdlib_include", "manual_stdlib_include", "pairing_crypto"
, "aens_update", "basic_auth_tx", "more_strings"
, "unapplied_builtins", "bytes_to_x", "state_handling", "protected_call"
, "hermetization_turnoff"
].
debug_mode_contracts() ->
["hermetization_turnoff"].
%% Contracts that should produce type errors %% Contracts that should produce type errors
@ -681,6 +698,16 @@ failing_contracts() ->
" (0 : int) == (1 : int) : bool\n" " (0 : int) == (1 : int) : bool\n"
"It must be either 'true' or 'false'.">> "It must be either 'true' or 'false'.">>
]) ])
, ?TYPE_ERROR(bad_function_block,
[<<?Pos(4, 5)
"Mismatch in the function block. Expected implementation/type declaration of g function">>,
<<?Pos(5, 5)
"Mismatch in the function block. Expected implementation/type declaration of g function">>
])
, ?TYPE_ERROR(just_an_empty_file,
[<<?Pos(0, 0)
"Empty contract">>
])
, ?TYPE_ERROR(bad_number_of_args, , ?TYPE_ERROR(bad_number_of_args,
[<<?Pos(3, 39) [<<?Pos(3, 39)
"Cannot unify () => unit\n" "Cannot unify () => unit\n"
@ -856,6 +883,11 @@ validate(Contract1, Contract2) ->
ByteCode = #{ fate_code := FCode } = compile(fate, Contract1), ByteCode = #{ fate_code := FCode } = compile(fate, Contract1),
FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)), FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)),
Source = aeso_test_utils:read_contract(Contract2), Source = aeso_test_utils:read_contract(Contract2),
aeso_compiler:validate_byte_code(ByteCode#{ byte_code := FCode1 }, Source, aeso_compiler:validate_byte_code(
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]). ByteCode#{ byte_code := FCode1 }, Source,
case lists:member(Contract2, debug_mode_contracts()) of
true -> [debug_mode];
false -> []
end ++
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]).

View File

@ -0,0 +1,5 @@
contract C =
function
g(1) = 2
f(2) = 3
h(1) = 123

View File

@ -45,3 +45,8 @@ contract FunctionArguments =
entrypoint due(t : Chain.ttl) = entrypoint due(t : Chain.ttl) =
true true
record singleton_r = { x : int }
entrypoint singleton_rec(r : singleton_r) =
r.x

View File

@ -0,0 +1,11 @@
namespace M =
function mf() = mg()
function mg() = mf()
namespace N =
function nf() = ng() + M.mf() + M.mg()
private function ng() = nf() + M.mf() + M.mg()
contract C =
entrypoint f() = N.ng() + N.nf() + g()
function g() = N.ng() + N.nf() + f()

View File