Merge branch 'lima' into master

This commit is contained in:
radrow 2020-10-13 10:22:05 +02:00
commit ac673602b9
16 changed files with 193 additions and 96 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

@ -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)

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]),
%% 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,7 +679,7 @@ 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, []).
@ -689,7 +689,11 @@ infer(Contracts) ->
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()
@ -752,7 +756,9 @@ infer_contract_top(Env, Kind, Defs0, _Options) ->
%% 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()]) -> {env(), [aeso_syntax:decl()]}.
infer_contract(Env0, What, Defs0) -> infer_contract(Env0, What, Defs0) ->
create_type_errors(),
Defs = process_blocks(Defs0), Defs = process_blocks(Defs0),
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;
@ -805,12 +811,12 @@ process_block(Ann, [Decl | Decls]) ->
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.
-spec check_typedefs(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. -spec check_typedefs(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
@ -2449,6 +2455,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

@ -42,7 +42,8 @@
| {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 +117,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 +126,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 +217,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 +239,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 +272,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 +329,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 +405,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"
@ -84,7 +87,7 @@ test_cases(3) ->
" 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}
@ -96,6 +99,8 @@ 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 = [{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 +127,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"),
@ -173,9 +173,13 @@ compilable_contracts() ->
"protected_call" "protected_call"
]. ].
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"
].
%% Contracts that should produce type errors %% Contracts that should produce type errors
@ -681,6 +685,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"

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