From 0bea3030bc27626dde4ff634afeabdb0d9984d16 Mon Sep 17 00:00:00 2001 From: radrow Date: Wed, 7 Apr 2021 08:41:54 +0200 Subject: [PATCH 01/34] Support for CREATE, CLONE and BYTECODE_HASH --- src/aeso_aci.erl | 20 +-- src/aeso_ast_infer_types.erl | 116 +++++++++++++----- src/aeso_ast_to_fcode.erl | 92 ++++++++------ src/aeso_ast_to_icode.erl | 6 +- src/aeso_code_errors.erl | 6 +- src/aeso_compiler.erl | 5 +- src/aeso_parser.erl | 16 ++- src/aeso_pretty.erl | 10 +- src/aeso_scan.erl | 4 +- src/aeso_syntax.erl | 7 +- test/aeso_abi_tests.erl | 2 +- test/aeso_calldata_tests.erl | 4 +- test/aeso_compiler_tests.erl | 19 +-- test/aeso_parser_tests.erl | 4 +- test/contracts/__call.aes | 2 +- test/contracts/address_chain.aes | 4 +- test/contracts/address_literals.aes | 2 +- test/contracts/bad_address_literals.aes | 2 +- test/contracts/bad_protected_call.aes | 2 +- test/contracts/bad_top_level_decl.aes | 2 +- .../code_errors/bad_aens_resolve.aes | 2 +- .../code_errors/higher_order_map_keys.aes | 2 +- .../code_errors/higher_order_query_type.aes | 2 +- .../higher_order_response_type.aes | 2 +- .../last_declaration_must_be_contract.aes | 2 - .../code_errors/missing_definition.aes | 2 +- .../code_errors/polymorphic_aens_resolve.aes | 2 +- .../code_errors/polymorphic_map_keys.aes | 2 +- .../code_errors/polymorphic_query_type.aes | 2 +- .../code_errors/polymorphic_response_type.aes | 2 +- .../code_errors/unapplied_contract_call.aes | 2 +- .../unapplied_named_arg_builtin.aes | 2 +- test/contracts/complex_types.aes | 2 +- test/contracts/contract_as_namespace.aes | 2 +- test/contracts/environment.aes | 2 +- test/contracts/events.aes | 2 +- test/contracts/factorial.aes | 2 +- test/contracts/identity.aes | 5 +- test/contracts/lhs_matching.aes | 2 +- test/contracts/missing_event_type.aes | 2 +- test/contracts/multiple_contracts.aes | 10 +- test/contracts/non_functional_entrypoint.aes | 2 +- test/contracts/protected_call.aes | 2 +- test/contracts/remote_call.aes | 10 +- test/contracts/spend_test.aes | 2 +- test/contracts/state_handling.aes | 2 +- test/contracts/stateful.aes | 2 +- test/contracts/test.aes | 2 +- test/contracts/type_clash.aes | 2 +- test/contracts/unapplied_builtins.aes | 2 +- 50 files changed, 247 insertions(+), 157 deletions(-) delete mode 100644 test/contracts/code_errors/last_declaration_must_be_contract.aes diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index 24c6f49..5a1a509 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -21,6 +21,8 @@ , json_encode_expr/1 , json_encode_type/1]). +-include("aeso_utils.hrl"). + -type aci_type() :: json | string. -type json() :: jsx:json_term(). -type json_text() :: binary(). @@ -68,9 +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), - %% io:format("~p\n", [Ast]), {TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]), - %% io:format("~p\n", [TypedAst]), 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 = {contract, _, {con, _, Name}, _}) -> +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)) ], @@ -107,7 +107,7 @@ encode_contract(Contract = {contract, _, {con, _, Name}, _}) -> || F <- sort_decls(contract_funcs(Contract)), is_entrypoint(F) ], - #{contract => C3#{functions => Fdefs, payable => is_payable(Contract)}}; + #{contract => C3#{kind => Head, functions => Fdefs, payable => is_payable(Contract)}}; encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) -> Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ], #{namespace => #{name => encode_name(Name), @@ -232,13 +232,19 @@ do_render_aci_json(Json) -> {ok, list_to_binary(string:join(DecodedContracts, "\n"))}. decode_contract(#{contract := #{name := Name, + kind := Kind, payable := Payable, type_defs := Ts0, functions := Fs} = C}) -> MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end, Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++ [ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0, - [payable(Payable), "contract ", io_lib:format("~s", [Name])," =\n", + [payable(Payable), case Kind of + contract_main -> "main contract "; + contract_child -> "contract "; + contract_interface -> "contract interface " + end, + io_lib:format("~s", [Name])," =\n", decode_tdefs(Ts), decode_funcs(Fs)]; decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] -> ["namespace ", io_lib:format("~s", [Name])," =\n", @@ -332,10 +338,10 @@ payable(false) -> "". %% #contract{Ann, Con, [Declarations]}. -contract_funcs({C, _, _, Decls}) when C == contract; C == namespace -> +contract_funcs({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace -> [ D || D <- Decls, is_fun(D)]. -contract_types({C, _, _, Decls}) when C == contract; C == namespace -> +contract_types({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace -> [ D || D <- Decls, is_type(D) ]. is_fun({letfun, _, _, _, _, _}) -> true; diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 8755cb1..3f8c3ce 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -18,6 +18,8 @@ , pp_type/2 ]). +-include("aeso_utils.hrl"). + -type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()], utype()} | {app_t, aeso_syntax:ann(), utype(), [utype()]} | {tuple_t, aeso_syntax:ann(), [utype()]} @@ -123,7 +125,7 @@ , in_pattern = false :: boolean() , stateful = false :: boolean() , current_function = none :: none | aeso_syntax:id() - , what = top :: top | namespace | contract | main_contract + , what = top :: top | namespace | contract | contract_interface }). -type env() :: #env{}. @@ -191,9 +193,9 @@ bind_fun(X, Type, 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 == main_contract, not NoCode -> + Entry = if X == "init", What == contract, not NoCode -> {reserved_init, Ann, Type}; - What == contract -> {contract_fun, Ann, Type}; + What == contract_interface -> {contract_fun, Ann, Type}; true -> {Ann, Type} end, on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) -> @@ -261,13 +263,21 @@ contract_call_type({fun_t, Ann, [], Args, Ret}) -> 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, Contents}, Env) -> +bind_contract({Contract, Ann, Id, 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 ] ++ - %% Predefined fields - [ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ], + 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 || ArgT <- if is_list(Args) -> Args; true -> [Args] end], RetT}) + } + || {letfun, AnnF, Entrypoint, _Named, Args, {typed, _, _, RetT}} <- Contents + ] ++ + %% Predefined fields + [ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ], FieldInfo = [ {Entrypoint, #field_info{ ann = FieldAnn, kind = contract, field_t = Type, @@ -463,6 +473,7 @@ global_env() -> {"block_height", Int}, {"difficulty", Int}, {"gas_limit", Int}, + {"bytecode_hash", Fun1(Address, Option(Hash))}, %% Tx constructors {"GAMetaTx", Fun([Address, Int], GAMetaTx)}, {"PayingForTx", Fun([Address, Int], PayForTx)}, @@ -701,7 +712,10 @@ infer(Contracts, Options) -> create_options(Options), ets_new(type_vars, [set]), check_modifiers(Env, Contracts), - {Env1, Decls} = infer1(Env, Contracts, [], Options), + create_type_errors(), + Contracts1 = identify_main_contract(Contracts), + destroy_and_report_type_errors(Env), + {Env1, Decls} = infer1(Env, Contracts1, [], Options), {Env2, DeclsFolded, DeclsUnfolded} = case proplists:get_value(dont_unfold, Options, false) of true -> {Env1, Decls, Decls}; @@ -719,12 +733,16 @@ infer(Contracts, Options) -> -spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) -> {env(), [aeso_syntax:decl()]}. infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)}; -infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc, Options) -> +infer1(Env, [{Contract, Ann, ConName, Code} | Rest], Acc, Options) + when ?IS_CONTRACT_HEAD(Contract) -> %% do type inference on each contract independently. check_scope_name_clash(Env, contract, ConName), - What = if Rest == [] -> main_contract; true -> contract end, + What = case aeso_syntax:get_ann(interface, Ann, false) of + true -> contract_interface; + false -> contract + end, {Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), What, Code, Options), - Contract1 = {contract, Ann, ConName, Code1}, + Contract1 = {Contract, Ann, ConName, Code1}, Env2 = pop_scope(Env1), Env3 = bind_contract(Contract1, Env2), infer1(Env3, Rest, [Contract1 | Acc], Options); @@ -737,6 +755,26 @@ infer1(Env, [{pragma, _, _} | Rest], Acc, Options) -> %% Pragmas are checked in check_modifiers infer1(Env, Rest, Acc, Options). +%% Checks if the main contract is somehow defined. +%% Performs some basic sorting to make the dependencies more happy. +identify_main_contract(Contracts) -> + Childs = [C || C = {contract_child, _, _, _} <- Contracts], + Mains = [C || C = {contract_main, _, _, _} <- Contracts], + Interfaces = [C || C = {contract_interface, _, _, _} <- Contracts], + Namespaces = [N || N = {namespace, _, _, _} <- Contracts], + case Mains of + [] -> case Childs of + [] -> type_error({main_contract_undefined}); + [{contract_child, Ann, Con, Body}] -> + Interfaces ++ Namespaces ++ + [C || C = {_, _, Con1, _} <- Childs, Con1 /= Con] ++ + [{contract_main, Ann, Con, Body}]; + _ -> type_error({ambiguous_main_contract}) + end; + [_] -> Interfaces ++ Namespaces ++ Childs ++ Mains; + _ -> type_error({multiple_main_contracts}) + end. + check_scope_name_clash(Env, Kind, Name) -> case get_scope(Env, qname(Name)) of false -> ok; @@ -746,7 +784,7 @@ check_scope_name_clash(Env, Kind, Name) -> destroy_and_report_type_errors(Env) end. --spec infer_contract_top(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) -> +-spec infer_contract_top(env(), contract_interface | contract | namespace, [aeso_syntax:decl()], list(option())) -> {env(), [aeso_syntax:decl()]}. infer_contract_top(Env, Kind, Defs0, Options) -> create_type_errors(), @@ -756,7 +794,7 @@ infer_contract_top(Env, Kind, Defs0, Options) -> %% infer_contract takes a proplist mapping global names to types, and %% a list of definitions. --spec infer_contract(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) -> {env(), [aeso_syntax:decl()]}. +-spec infer_contract(env(), contract_interface | contract | namespace, [aeso_syntax:decl()], list(option())) -> {env(), [aeso_syntax:decl()]}. infer_contract(Env0, What, Defs0, Options) -> create_type_errors(), Defs01 = process_blocks(Defs0), @@ -772,19 +810,19 @@ infer_contract(Env0, What, Defs0, Options) -> ({fun_decl, _, _, _}) -> prototype; (_) -> unexpected end, - Get = fun(K) -> [ Def || Def <- Defs, Kind(Def) == K ] end, - {Env1, TypeDefs} = check_typedefs(Env, Get(type)), + Get = fun(K, In) -> [ Def || Def <- In, Kind(Def) == K ] end, + {Env1, TypeDefs} = check_typedefs(Env, Get(type, Defs)), create_type_errors(), - check_unexpected(Get(unexpected)), + check_unexpected(Get(unexpected, Defs)), Env2 = case What of - namespace -> Env1; - contract -> Env1; - main_contract -> bind_state(Env1) %% bind state and put + namespace -> Env1; + contract_interface -> Env1; + contract -> bind_state(Env1) %% bind state and put end, - {ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype) ]), + {ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype, Defs) ]), Env3 = bind_funs(ProtoSigs, Env2), - Functions = Get(function), + Functions = Get(function, Defs), %% Check for duplicates in Functions (we turn it into a map below) FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}}; ({fun_clauses, Ann, {id, _, Fun}, _, _}) -> {Fun, {tuple_t, Ann, []}} end, @@ -794,11 +832,11 @@ infer_contract(Env0, What, Defs0, Options) -> check_reserved_entrypoints(FunMap), DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_ids(Def) end, FunMap), SCCs = aeso_utils:scc(DepGraph), - %% io:format("Dependency sorted functions:\n ~p\n", [SCCs]), {Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []), %% Check that `init` doesn't read or write the state check_state_dependencies(Env4, Defs1), destroy_and_report_type_errors(Env4), + %% Add inferred types of definitions {Env4, TypeDefs ++ Decls ++ Defs1}. %% Restructure blocks into multi-clause fundefs (`fun_clauses`). @@ -830,9 +868,9 @@ 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 + namespace -> [A ||A <- Ann, A /= {private, true}, A /= private]; + contract -> [{entrypoint, true}|Ann]; % minor duplication + contract_interface -> Ann end, Def1 = setelement(2, Def, NewAnn), case Def1 of % fix inner clauses @@ -907,15 +945,16 @@ check_modifiers(Env, Contracts) -> check_modifiers_(Env, Contracts), destroy_and_report_type_errors(Env). -check_modifiers_(Env, [{contract, _, Con, Decls} | Rest]) -> - IsMain = Rest == [], +check_modifiers_(Env, [{Contract, _, Con, Decls} | Rest]) + when ?IS_CONTRACT_HEAD(Contract) -> + IsInterface = Contract =:= contract_interface, check_modifiers1(contract, Decls), case {lists:keymember(letfun, 1, Decls), [ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of {true, []} -> type_error({contract_has_no_entrypoints, Con}); - _ when not IsMain -> - case [ {Ann, Id} || {letfun, Ann, Id, _, _, _} <- Decls ] of - [{Ann, Id} | _] -> type_error({definition_in_non_main_contract, Ann, Id}); + _ when IsInterface -> + case [ {AnnF, Id} || {letfun, AnnF, Id, _, _, _} <- Decls ] of + [{AnnF, Id} | _] -> type_error({definition_in_contract_interface, AnnF, Id}); [] -> ok end; _ -> ok @@ -2653,7 +2692,7 @@ mk_error({namespace, _Pos, {con, Pos, Name}, _Def}) -> Msg = io_lib:format("Nested namespaces are not allowed\nNamespace '~s' at ~s not defined at top level.\n", [Name, pp_loc(Pos)]), mk_t_err(pos(Pos), Msg); -mk_error({contract, _Pos, {con, Pos, Name}, _Def}) -> +mk_error({Contract, _Pos, {con, Pos, Name}, _Def}) when ?IS_CONTRACT_HEAD(Contract) -> Msg = io_lib:format("Nested contracts are not allowed\nContract '~s' at ~s not defined at top level.\n", [Name, pp_loc(Pos)]), mk_t_err(pos(Pos), Msg); @@ -2728,8 +2767,8 @@ mk_error({contract_has_no_entrypoints, Con}) -> "contract functions must be declared with the 'entrypoint' keyword instead of\n" "'function'.\n", [pp_expr("", Con), pp_loc(Con)]), mk_t_err(pos(Con), Msg); -mk_error({definition_in_non_main_contract, Ann, {id, _, Id}}) -> - Msg = "Only the main contract can contain defined functions or entrypoints.\n", +mk_error({definition_in_contract_interface, Ann, {id, _, Id}}) -> + Msg = "Contract interfaces cannot contain defined functions or entrypoints.\n", Cxt = io_lib:format("Fix: replace the definition of '~s' by a type signature.\n", [Id]), mk_t_err(pos(Ann), Msg, Cxt); mk_error({unbound_type, Type}) -> @@ -2798,6 +2837,15 @@ mk_error({named_argument_must_be_literal_bool, Name, Arg}) -> mk_error({conflicting_updates_for_field, Upd, Key}) -> Msg = io_lib:format("Conflicting updates for field '~s'\n", [Key]), mk_t_err(pos(Upd), Msg); +mk_error({ambiguous_main_contract}) -> + Msg = "Could not deduce the main contract. You can point it manually with `main` keyword.", + mk_t_err(pos(0, 0), Msg); +mk_error({main_contract_undefined}) -> + Msg = "No contract defined.", + mk_t_err(pos(0, 0), Msg); +mk_error({multiple_main_contracts}) -> + Msg = "Up to one main contract can be defined.", + mk_t_err(pos(0, 0), Msg); mk_error(Err) -> Msg = io_lib:format("Unknown error: ~p\n", [Err]), mk_t_err(pos(0, 0), Msg). diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 07f4e01..27de5b3 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -12,6 +12,8 @@ -export([ast_to_fcode/2, format_fexpr/1]). -export_type([fcode/0, fexpr/0, fun_def/0]). +-include("aeso_utils.hrl"). + %% -- Type definitions ------------------------------------------------------- -type option() :: term(). @@ -136,6 +138,7 @@ -type type_env() :: #{ sophia_name() => type_def() }. -type fun_env() :: #{ sophia_name() => {fun_name(), non_neg_integer()} }. -type con_env() :: #{ sophia_name() => con_tag() }. +-type child_con_env() :: #{sophia_name() => fcode()}. -type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none} }. -type context() :: {main_contract, string()} @@ -144,16 +147,17 @@ -type state_layout() :: {tuple, [state_layout()]} | {reg, state_reg()}. --type env() :: #{ type_env := type_env(), - fun_env := fun_env(), - con_env := con_env(), - event_type => aeso_syntax:typedef(), - builtins := builtins(), - options := [option()], - state_layout => state_layout(), - context => context(), - vars => [var_name()], - functions := #{ fun_name() => fun_def() } }. +-type env() :: #{ type_env := type_env(), + fun_env := fun_env(), + con_env := con_env(), + child_con_env := child_con_env(), + event_type => aeso_syntax:typedef(), + builtins := builtins(), + options := [option()], + state_layout => state_layout(), + context => context(), + vars => [var_name()], + functions := #{ fun_name() => fun_def() } }. -define(HASH_BYTES, 32). @@ -182,6 +186,7 @@ init_env(Options) -> #{ type_env => init_type_env(), fun_env => #{}, builtins => builtins(), + child_con_env => #{}, con_env => #{["None"] => #con_tag{ tag = 0, arities = [0, 1] }, ["Some"] => #con_tag{ tag = 1, arities = [0, 1] }, ["RelativeTTL"] => #con_tag{ tag = 0, arities = [1, 1] }, @@ -308,30 +313,41 @@ get_option(Opt, Env, Default) -> %% -- Compilation ------------------------------------------------------------ -spec to_fcode(env(), aeso_syntax:ast()) -> fcode(). -to_fcode(Env, [{contract, Attrs, MainCon = {con, _, Main}, Decls}]) -> - #{ builtins := Builtins } = Env, - MainEnv = Env#{ context => {main_contract, Main}, - builtins => Builtins#{[Main, "state"] => {get_state, none}, - [Main, "put"] => {set_state, 1}, - [Main, "Chain", "event"] => {chain_event, 1}} }, - #{ functions := Funs } = Env1 = - decls_to_fcode(MainEnv, Decls), - StateType = lookup_type(Env1, [Main, "state"], [], {tuple, []}), - EventType = lookup_type(Env1, [Main, "event"], [], none), - StateLayout = state_layout(Env1), - Payable = proplists:get_value(payable, Attrs, false), - #{ contract_name => Main, - state_type => StateType, - state_layout => StateLayout, - event_type => EventType, - payable => Payable, - functions => add_init_function(Env1, MainCon, StateType, - add_event_function(Env1, EventType, Funs)) }; -to_fcode(_Env, [NotContract]) -> - fcode_error({last_declaration_must_be_contract, NotContract}); -to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) -> - Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls), - to_fcode(Env1, Code); +to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest]) + when ?IS_CONTRACT_HEAD(Contract) -> + case Contract =:= contract_interface of + false -> + + #{ builtins := Builtins } = Env, + ConEnv = Env#{ context => {main_contract, Name}, + builtins => Builtins#{[Name, "state"] => {get_state, none}, + [Name, "put"] => {set_state, 1}, + [Name, "Chain", "event"] => {chain_event, 1}} }, + #{ functions := Funs } = Env1 = + decls_to_fcode(ConEnv, Decls), + StateType = lookup_type(Env1, [Name, "state"], [], {tuple, []}), + EventType = lookup_type(Env1, [Name, "event"], [], none), + StateLayout = state_layout(Env1), + Payable = proplists:get_value(payable, Attrs, false), + ConFcode = #{ contract_name => Name, + state_type => StateType, + state_layout => StateLayout, + event_type => EventType, + payable => Payable, + functions => add_init_function(Env1, Con, StateType, + add_event_function(Env1, EventType, Funs)) }, + case Contract of + contract_main -> Rest = [], ConFcode; + contract_child -> + Env2 = add_child_con(Env1, Name, ConFcode), + to_fcode(Env2, Rest) + end; + true -> + Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls), + to_fcode(Env1, Rest) + end; +to_fcode(_Env, [NotMain = {NotMainHead, _ ,_ , _}]) when NotMainHead =/= main_contract -> + 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). @@ -341,9 +357,7 @@ decls_to_fcode(Env, Decls) -> %% First compute mapping from Sophia names to fun_names and add it to the %% environment. Env1 = add_fun_env(Env, Decls), - lists:foldl(fun(D, E) -> - R = decl_to_fcode(E, D), - R + lists:foldl(fun(D, E) -> decl_to_fcode(E, D) end, Env1, Decls). -spec decl_to_fcode(env(), aeso_syntax:decl()) -> env(). @@ -1614,6 +1628,10 @@ bind_constructors(Env = #{ con_env := ConEnv }, NewCons) -> %% -- Names -- +-spec add_child_con(env(), sophia_name(), fcode()) -> env(). +add_child_con(Env = #{child_con_env := CEnv}, Name, Fcode) -> + Env#{ child_con_env := CEnv#{Name => Fcode} }. + -spec add_fun_env(env(), [aeso_syntax:decl()]) -> env(). add_fun_env(Env = #{ context := {abstract_contract, _} }, _) -> Env; %% no functions from abstract contracts add_fun_env(Env = #{ fun_env := FunEnv }, Decls) -> diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index b1a1874..8cace86 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -14,12 +14,13 @@ -include_lib("aebytecode/include/aeb_opcodes.hrl"). -include("aeso_icode.hrl"). +-include("aeso_utils.hrl"). -spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode(). convert_typed(TypedTree, Options) -> {Payable, Name} = case lists:last(TypedTree) of - {contract, Attrs, {con, _, Con}, _} -> + {Contr, Attrs, {con, _, Con}, _} when ?IS_CONTRACT_HEAD(Contr) -> {proplists:get_value(payable, Attrs, false), Con}; Decl -> gen_error({last_declaration_must_be_contract, Decl}) @@ -29,7 +30,8 @@ convert_typed(TypedTree, Options) -> Icode = code(TypedTree, NewIcode, Options), deadcode_elimination(Icode). -code([{contract, _Attribs, Con, Code}|Rest], Icode, Options) -> +code([{Contract, _Attribs, Con, Code}|Rest], Icode, Options) + when ?IS_CONTRACT_HEAD(Contract) -> NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)), code(Rest, NewIcode, Options); code([{namespace, _Ann, Name, Code}|Rest], Icode, Options) -> diff --git a/src/aeso_code_errors.erl b/src/aeso_code_errors.erl index 05bc135..bb2568d 100644 --- a/src/aeso_code_errors.erl +++ b/src/aeso_code_errors.erl @@ -10,9 +10,9 @@ -export([format/1, pos/1]). -format({last_declaration_must_be_contract, Decl = {namespace, _, {con, _, C}, _}}) -> - Msg = io_lib:format("Expected a contract as the last declaration instead of the namespace '~s'\n", - [C]), +format({last_declaration_must_be_contract, Decl = {Kind, _, {con, _, C}, _}}) -> + Msg = io_lib:format("Expected a contract as the last declaration instead of the ~p '~s'\n", + [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)]), diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 25cb1b9..930c70c 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -28,6 +28,7 @@ -include_lib("aebytecode/include/aeb_opcodes.hrl"). -include("aeso_icode.hrl"). +-include("aeso_utils.hrl"). -type option() :: pp_sophia_code @@ -468,7 +469,7 @@ error_missing_call_function() -> Msg = "Internal error: missing '__call'-function", aeso_errors:throw(aeso_errors:new(internal_error, Msg)). -get_call_type([{contract, _, _, Defs}]) -> +get_call_type([{Contract, _, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) -> case [ {lists:last(QFunName), FunType} || {letfun, _, {id, _, ?CALL_NAME}, [], _Ret, {typed, _, @@ -482,7 +483,7 @@ get_call_type([_ | Contracts]) -> get_call_type(Contracts). -dialyzer({nowarn_function, get_decode_type/2}). -get_decode_type(FunName, [{contract, Ann, _, Defs}]) -> +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, diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index acb09c6..c29403d 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -93,8 +93,20 @@ decl() -> ?LAZY_P( choice( %% Contract declaration - [ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4}) - , ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5})) + [ ?RULE(token(main), keyword(contract), + con(), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5}) + , ?RULE(keyword(contract), + con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4}) + , ?RULE(keyword(contract), token(interface), + con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5}) + , ?RULE(token(payable), token(main), keyword(contract), + con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6})) + , ?RULE(token(payable), keyword(contract), + con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5})) + , ?RULE(token(payable), keyword(contract), token(interface), + con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6})) + + , ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4}) , ?RULE(keyword(include), str(), {include, get_ann(_1), _2}) , pragma() diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index dfbd577..6b43b4c 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -13,6 +13,8 @@ -export_type([options/0]). +-include("aeso_utils.hrl"). + -type doc() :: prettypr:document(). -type options() :: [{indent, non_neg_integer()} | show_generated]. @@ -131,6 +133,10 @@ typed(A, Type) -> false -> follow(hsep(A, text(":")), type(Type)) end. +contract_head(contract_main) -> text("main contract"); +contract_head(contract_child) -> text("contract"); +contract_head(contract_interface) -> text("contract interface"). + %% -- Exports ---------------------------------------------------------------- -spec decls([aeso_syntax:decl()], options()) -> doc(). @@ -145,11 +151,11 @@ decl(D, Options) -> with_options(Options, fun() -> decl(D) end). -spec decl(aeso_syntax:decl()) -> doc(). -decl({contract, Attrs, C, Ds}) -> +decl({Con, Attrs, C, Ds}) when ?IS_CONTRACT_HEAD(Con) -> Mod = fun({Mod, true}) when Mod == payable -> text(atom_to_list(Mod)); (_) -> empty() end, - block(follow( hsep(lists:map(Mod, Attrs) ++ [text("contract")]) + block(follow( hsep(lists:map(Mod, Attrs) ++ [contract_head(Con)]) , hsep(name(C), text("="))), decls(Ds)); decl({namespace, _, C, Ds}) -> block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds)); diff --git a/src/aeso_scan.erl b/src/aeso_scan.erl index e81757f..2c5d301 100644 --- a/src/aeso_scan.erl +++ b/src/aeso_scan.erl @@ -44,7 +44,9 @@ lexer() -> , {"[^/*]+|[/*]", skip()} ], Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function", - "stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace"], + "stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace", + "interface", "main" + ], KW = string:join(Keywords, "|"), Rules = diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index 1d01ef0..b48e4ff 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -25,7 +25,8 @@ -type ann_origin() :: system | user. -type ann_format() :: '?:' | hex | infix | prefix | elif. --type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} | stateful | private]. +-type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} + | stateful | private] | payable | main | interface. -type name() :: string(). -type id() :: {id, ann(), name()}. @@ -34,7 +35,9 @@ -type qcon() :: {qcon, ann(), [name()]}. -type tvar() :: {tvar, ann(), name()}. --type decl() :: {contract, ann(), con(), [decl()]} +-type decl() :: {contract_main, ann(), con(), [decl()]} + | {contract_child, ann(), con(), [decl()]} + | {contract_interface, ann(), con(), [decl()]} | {namespace, ann(), con(), [decl()]} | {pragma, ann(), pragma()} | {type_decl, ann(), id(), [tvar()]} % Only for error msgs diff --git a/test/aeso_abi_tests.erl b/test/aeso_abi_tests.erl index 6f1fb02..9306657 100644 --- a/test/aeso_abi_tests.erl +++ b/test/aeso_abi_tests.erl @@ -190,7 +190,7 @@ parameterized_contract(ExtraCode, FunName, Types) -> lists:flatten( ["contract Remote =\n" " entrypoint bla : () => unit\n\n" - "contract Dummy =\n", + "main contract Dummy =\n", ExtraCode, "\n", " type an_alias('a) = string * 'a\n" " record r = {x : an_alias(int), y : variant}\n" diff --git a/test/aeso_calldata_tests.erl b/test/aeso_calldata_tests.erl index c939893..58d66e3 100644 --- a/test/aeso_calldata_tests.erl +++ b/test/aeso_calldata_tests.erl @@ -59,8 +59,8 @@ calldata_aci_test_() -> end} || {ContractName, Fun, Args} <- compilable_contracts()]. parse_args(Fun, Args) -> - [{contract, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] = - aeso_parser:string("contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"), + [{contract_main, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] = + aeso_parser:string("main contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"), strip_ann(AST). strip_ann(T) when is_tuple(T) -> diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 36e70b4..3e3cfa5 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -179,17 +179,12 @@ compilable_contracts() -> "lhs_matching", "more_strings", "protected_call", - "hermetization_turnoff" + "hermetization_turnoff", + "multiple_contracts" ]. not_compilable_on(fate) -> []; -not_compilable_on(aevm) -> - [ "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" - - ]. +not_compilable_on(aevm) -> compilable_contracts(). debug_mode_contracts() -> ["hermetization_turnoff"]. @@ -635,9 +630,9 @@ failing_contracts() -> <>]) - , ?TYPE_ERROR(multiple_contracts, + , ?TYPE_ERROR(interface_with_defs, [<>]) , ?TYPE_ERROR(contract_as_namespace, [< {fate, ?Msg(File, Line, Col, ErrFATE)}]}). failing_code_gen_contracts() -> - [ ?SAME(last_declaration_must_be_contract, 1, 1, - "Expected a contract as the last declaration instead of the namespace 'LastDeclarationIsNotAContract'") - , ?SAME(missing_definition, 2, 14, + [ ?SAME(missing_definition, 2, 14, "Missing definition of function 'foo'.") , ?AEVM(polymorphic_entrypoint, 2, 17, "The argument\n" diff --git a/test/aeso_parser_tests.erl b/test/aeso_parser_tests.erl index c978b0a..f8e2eee 100644 --- a/test/aeso_parser_tests.erl +++ b/test/aeso_parser_tests.erl @@ -12,10 +12,10 @@ simple_contracts_test_() -> fun(_) -> ok end, [{"Parse a contract with an identity function.", fun() -> - Text = "contract Identity =\n" + Text = "main contract Identity =\n" " function id(x) = x\n", ?assertMatch( - [{contract, _, {con, _, "Identity"}, + [{contract_main, _, {con, _, "Identity"}, [{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"}, {id, _, "x"}}]}], parse_string(Text)), ok diff --git a/test/contracts/__call.aes b/test/contracts/__call.aes index 1e19b5b..1f90e04 100644 --- a/test/contracts/__call.aes +++ b/test/contracts/__call.aes @@ -1,5 +1,5 @@ contract Identity = - function main (x:int) = x + function main_fun (x:int) = x function __call() = 12 diff --git a/test/contracts/address_chain.aes b/test/contracts/address_chain.aes index 4c55a45..da5a111 100644 --- a/test/contracts/address_chain.aes +++ b/test/contracts/address_chain.aes @@ -1,5 +1,5 @@ -contract Remote = - entrypoint main : (int) => unit +contract interface Remote = + entrypoint main_fun : (int) => unit contract AddrChain = type o_type = oracle(string, map(string, int)) diff --git a/test/contracts/address_literals.aes b/test/contracts/address_literals.aes index 15007cf..94e7e0c 100644 --- a/test/contracts/address_literals.aes +++ b/test/contracts/address_literals.aes @@ -1,5 +1,5 @@ -contract Remote = +contract interface Remote = entrypoint foo : () => unit contract AddressLiterals = diff --git a/test/contracts/bad_address_literals.aes b/test/contracts/bad_address_literals.aes index 1750846..30eebac 100644 --- a/test/contracts/bad_address_literals.aes +++ b/test/contracts/bad_address_literals.aes @@ -1,5 +1,5 @@ -contract Remote = +contract interface Remote = entrypoint foo : () => unit contract AddressLiterals = diff --git a/test/contracts/bad_protected_call.aes b/test/contracts/bad_protected_call.aes index ba1f59c..9b7cf9d 100644 --- a/test/contracts/bad_protected_call.aes +++ b/test/contracts/bad_protected_call.aes @@ -1,4 +1,4 @@ -contract Remote = +contract interface Remote = entrypoint id : int => int contract ProtectedCall = diff --git a/test/contracts/bad_top_level_decl.aes b/test/contracts/bad_top_level_decl.aes index 5475fb6..e9053aa 100644 --- a/test/contracts/bad_top_level_decl.aes +++ b/test/contracts/bad_top_level_decl.aes @@ -1,3 +1,3 @@ function square(x) = x ^ 2 contract Main = - entrypoint main() = square(10) + entrypoint main_fun() = square(10) diff --git a/test/contracts/code_errors/bad_aens_resolve.aes b/test/contracts/code_errors/bad_aens_resolve.aes index 38d932b..8700dfb 100644 --- a/test/contracts/code_errors/bad_aens_resolve.aes +++ b/test/contracts/code_errors/bad_aens_resolve.aes @@ -5,5 +5,5 @@ contract BadAENSresolve = function fail() : t(int) = AENS.resolve("foo.aet", "whatever") - entrypoint main() = () + entrypoint main_fun() = () diff --git a/test/contracts/code_errors/higher_order_map_keys.aes b/test/contracts/code_errors/higher_order_map_keys.aes index a5fa742..98327e2 100644 --- a/test/contracts/code_errors/higher_order_map_keys.aes +++ b/test/contracts/code_errors/higher_order_map_keys.aes @@ -3,4 +3,4 @@ contract MapAsMapKey = function foo(m) : t(int => int) = {[m] = 0} - entrypoint main() = () + entrypoint main_fun() = () diff --git a/test/contracts/code_errors/higher_order_query_type.aes b/test/contracts/code_errors/higher_order_query_type.aes index 39d533f..1f4a4e6 100644 --- a/test/contracts/code_errors/higher_order_query_type.aes +++ b/test/contracts/code_errors/higher_order_query_type.aes @@ -2,4 +2,4 @@ contract HigherOrderQueryType = stateful function foo(o) : oracle_query(_, string ) = Oracle.query(o, (x) => x + 1, 100, RelativeTTL(100), RelativeTTL(100)) - entrypoint main() = () + entrypoint main_fun() = () diff --git a/test/contracts/code_errors/higher_order_response_type.aes b/test/contracts/code_errors/higher_order_response_type.aes index ae4ec93..87da03c 100644 --- a/test/contracts/code_errors/higher_order_response_type.aes +++ b/test/contracts/code_errors/higher_order_response_type.aes @@ -2,4 +2,4 @@ contract HigherOrderResponseType = stateful function foo(o, q : oracle_query(string, _)) = Oracle.respond(o, q, (x) => x + 1) - entrypoint main() = () + entrypoint main_fun() = () diff --git a/test/contracts/code_errors/last_declaration_must_be_contract.aes b/test/contracts/code_errors/last_declaration_must_be_contract.aes deleted file mode 100644 index 1c72d81..0000000 --- a/test/contracts/code_errors/last_declaration_must_be_contract.aes +++ /dev/null @@ -1,2 +0,0 @@ -namespace LastDeclarationIsNotAContract = - function add(x, y) = x + y diff --git a/test/contracts/code_errors/missing_definition.aes b/test/contracts/code_errors/missing_definition.aes index 6f7b919..5e354ec 100644 --- a/test/contracts/code_errors/missing_definition.aes +++ b/test/contracts/code_errors/missing_definition.aes @@ -1,3 +1,3 @@ contract MissingDefinition = entrypoint foo : int => int - entrypoint main() = foo(0) + entrypoint main_fun() = foo(0) diff --git a/test/contracts/code_errors/polymorphic_aens_resolve.aes b/test/contracts/code_errors/polymorphic_aens_resolve.aes index 6301743..0bd9cc8 100644 --- a/test/contracts/code_errors/polymorphic_aens_resolve.aes +++ b/test/contracts/code_errors/polymorphic_aens_resolve.aes @@ -3,5 +3,5 @@ contract PolymorphicAENSresolve = function fail() : option('a) = AENS.resolve("foo.aet", "whatever") - entrypoint main() = () + entrypoint main_fun() = () diff --git a/test/contracts/code_errors/polymorphic_map_keys.aes b/test/contracts/code_errors/polymorphic_map_keys.aes index eb64237..9821780 100644 --- a/test/contracts/code_errors/polymorphic_map_keys.aes +++ b/test/contracts/code_errors/polymorphic_map_keys.aes @@ -3,4 +3,4 @@ contract MapAsMapKey = function foo(m) : t('a) = {[m] = 0} - entrypoint main() = () + entrypoint main_fun() = () diff --git a/test/contracts/code_errors/polymorphic_query_type.aes b/test/contracts/code_errors/polymorphic_query_type.aes index 4fba99b..0f5d6ab 100644 --- a/test/contracts/code_errors/polymorphic_query_type.aes +++ b/test/contracts/code_errors/polymorphic_query_type.aes @@ -2,4 +2,4 @@ contract PolymorphicQueryType = stateful function is_oracle(o) = Oracle.check(o) - entrypoint main() = () + entrypoint main_fun() = () diff --git a/test/contracts/code_errors/polymorphic_response_type.aes b/test/contracts/code_errors/polymorphic_response_type.aes index 9b33971..3c26af5 100644 --- a/test/contracts/code_errors/polymorphic_response_type.aes +++ b/test/contracts/code_errors/polymorphic_response_type.aes @@ -2,4 +2,4 @@ contract PolymorphicResponseType = function is_oracle(o : oracle(string, 'r)) = Oracle.check(o) - entrypoint main(o : oracle(string, int)) = is_oracle(o) + entrypoint main_fun(o : oracle(string, int)) = is_oracle(o) diff --git a/test/contracts/code_errors/unapplied_contract_call.aes b/test/contracts/code_errors/unapplied_contract_call.aes index 0ef0248..78f5a12 100644 --- a/test/contracts/code_errors/unapplied_contract_call.aes +++ b/test/contracts/code_errors/unapplied_contract_call.aes @@ -1,4 +1,4 @@ -contract Remote = +contract interface Remote = entrypoint foo : int => int contract UnappliedContractCall = diff --git a/test/contracts/code_errors/unapplied_named_arg_builtin.aes b/test/contracts/code_errors/unapplied_named_arg_builtin.aes index 9b0a5ce..ebd2d1a 100644 --- a/test/contracts/code_errors/unapplied_named_arg_builtin.aes +++ b/test/contracts/code_errors/unapplied_named_arg_builtin.aes @@ -1,5 +1,5 @@ contract UnappliedNamedArgBuiltin = // Allowed in FATE, but not AEVM - stateful entrypoint main(s) = + stateful entrypoint main_fun(s) = let reg = Oracle.register reg(signature = s, Contract.address, 100, RelativeTTL(100)) : oracle(int, int) diff --git a/test/contracts/complex_types.aes b/test/contracts/complex_types.aes index 57e19d7..83b9b69 100644 --- a/test/contracts/complex_types.aes +++ b/test/contracts/complex_types.aes @@ -1,5 +1,5 @@ -contract Remote = +contract interface Remote = entrypoint up_to : (int) => list(int) entrypoint sum : (list(int)) => int entrypoint some_string : () => string diff --git a/test/contracts/contract_as_namespace.aes b/test/contracts/contract_as_namespace.aes index 489dfda..ac4de29 100644 --- a/test/contracts/contract_as_namespace.aes +++ b/test/contracts/contract_as_namespace.aes @@ -1,4 +1,4 @@ -contract Foo = +contract interface Foo = entrypoint foo : () => int contract Fail = diff --git a/test/contracts/environment.aes b/test/contracts/environment.aes index d02eec5..a05f5bf 100644 --- a/test/contracts/environment.aes +++ b/test/contracts/environment.aes @@ -1,6 +1,6 @@ // Testing primitives for accessing the block chain environment -contract Interface = +contract interface Interface = entrypoint contract_address : () => address entrypoint call_origin : () => address entrypoint call_caller : () => address diff --git a/test/contracts/events.aes b/test/contracts/events.aes index 74a3393..b63397c 100644 --- a/test/contracts/events.aes +++ b/test/contracts/events.aes @@ -1,4 +1,4 @@ -contract Remote = +contract interface Remote = entrypoint dummy : () => unit contract Events = diff --git a/test/contracts/factorial.aes b/test/contracts/factorial.aes index 88a8869..800ef07 100644 --- a/test/contracts/factorial.aes +++ b/test/contracts/factorial.aes @@ -1,6 +1,6 @@ // An implementation of the factorial function where each recursive // call is to another contract. Not the cheapest way to compute factorial. -contract FactorialServer = +contract interface FactorialServer = entrypoint fac : (int) => int contract Factorial = diff --git a/test/contracts/identity.aes b/test/contracts/identity.aes index cc5eeaf..2a639f6 100644 --- a/test/contracts/identity.aes +++ b/test/contracts/identity.aes @@ -1,3 +1,2 @@ - -contract Identity = - entrypoint main (x:int) = x +main contract Identity = + entrypoint main_fun (x:int) = x diff --git a/test/contracts/lhs_matching.aes b/test/contracts/lhs_matching.aes index 2cafa9d..98dddbb 100644 --- a/test/contracts/lhs_matching.aes +++ b/test/contracts/lhs_matching.aes @@ -16,7 +16,7 @@ contract LHSMatching = let null(_ :: _) = false !null(xs) - entrypoint main() = + entrypoint main_fun() = from_some(Some([0])) ++ append([length([true]), 2, 3], [4, 5, 6]) ++ [7 | if (local_match([false]))] diff --git a/test/contracts/missing_event_type.aes b/test/contracts/missing_event_type.aes index 8931c5e..745fcf4 100644 --- a/test/contracts/missing_event_type.aes +++ b/test/contracts/missing_event_type.aes @@ -1,3 +1,3 @@ contract MissingEventType = - entrypoint main() = + entrypoint main_fun() = Chain.event("MAIN") diff --git a/test/contracts/multiple_contracts.aes b/test/contracts/multiple_contracts.aes index 41df9b4..5d71b36 100644 --- a/test/contracts/multiple_contracts.aes +++ b/test/contracts/multiple_contracts.aes @@ -1,5 +1,7 @@ -contract ContractOne = - entrypoint foo() = "foo" +contract Child = + entrypoint + add2 : int => int + add2(x) = x + 2 -contract ContractTwo = - entrypoint bar() = "bar" +main contract Main = + entrypoint add4(x, c : Child) = c.add2(x) + 2 \ No newline at end of file diff --git a/test/contracts/non_functional_entrypoint.aes b/test/contracts/non_functional_entrypoint.aes index 4162e38..0b23b5a 100644 --- a/test/contracts/non_functional_entrypoint.aes +++ b/test/contracts/non_functional_entrypoint.aes @@ -1,4 +1,4 @@ -contract C1 = +contract interface C1 = entrypoint f : int contract C = diff --git a/test/contracts/protected_call.aes b/test/contracts/protected_call.aes index d377399..6bf09b2 100644 --- a/test/contracts/protected_call.aes +++ b/test/contracts/protected_call.aes @@ -1,4 +1,4 @@ -contract Remote = +contract interface Remote = entrypoint id : int => int contract ProtectedCall = diff --git a/test/contracts/remote_call.aes b/test/contracts/remote_call.aes index e644a0f..c5b0547 100644 --- a/test/contracts/remote_call.aes +++ b/test/contracts/remote_call.aes @@ -1,18 +1,18 @@ -contract Remote1 = - entrypoint main : (int) => int +contract interface Remote1 = + entrypoint main_fun : (int) => int -contract Remote2 = +contract interface Remote2 = entrypoint call : (Remote1, int) => int -contract Remote3 = +contract interface Remote3 = entrypoint get : () => int entrypoint tick : () => unit contract RemoteCall = stateful entrypoint call(r : Remote1, x : int) : int = - r.main(gas = 10000, value = 10, x) + r.main_fun(gas = 10000, value = 10, x) entrypoint staged_call(r1 : Remote1, r2 : Remote2, x : int) = r2.call(r1, x) diff --git a/test/contracts/spend_test.aes b/test/contracts/spend_test.aes index 7213959..0eb1666 100644 --- a/test/contracts/spend_test.aes +++ b/test/contracts/spend_test.aes @@ -1,5 +1,5 @@ -contract SpendContract = +contract interface SpendContract = entrypoint withdraw : (int) => int contract SpendTest = diff --git a/test/contracts/state_handling.aes b/test/contracts/state_handling.aes index 00c2fcb..67722ba 100644 --- a/test/contracts/state_handling.aes +++ b/test/contracts/state_handling.aes @@ -1,5 +1,5 @@ include "String.aes" -contract Remote = +contract interface Remote = record rstate = { i : int, s : string, m : map(int, int) } entrypoint look_at : (rstate) => unit diff --git a/test/contracts/stateful.aes b/test/contracts/stateful.aes index e6ea8be..7237b6e 100644 --- a/test/contracts/stateful.aes +++ b/test/contracts/stateful.aes @@ -1,5 +1,5 @@ -contract Remote = +contract interface Remote = stateful entrypoint remote_spend : (address, int) => unit entrypoint remote_pure : int => int diff --git a/test/contracts/test.aes b/test/contracts/test.aes index bd1a130..c20002f 100644 --- a/test/contracts/test.aes +++ b/test/contracts/test.aes @@ -95,7 +95,7 @@ contract Identity = function s(n) = (f,x)=>f(n(f,x)) function add(m,n) = (f,x)=>m(f,n(f,x)) - entrypoint main() = + entrypoint main_fun() = let three=s(s(s(z))) add(three,three) (((i)=>i+1),0) diff --git a/test/contracts/type_clash.aes b/test/contracts/type_clash.aes index dd20607..fb95e4c 100644 --- a/test/contracts/type_clash.aes +++ b/test/contracts/type_clash.aes @@ -1,5 +1,5 @@ -contract Remote = +contract interface Remote = type themap = map(int, string) entrypoint foo : () => themap diff --git a/test/contracts/unapplied_builtins.aes b/test/contracts/unapplied_builtins.aes index 678b085..1f2e9ae 100644 --- a/test/contracts/unapplied_builtins.aes +++ b/test/contracts/unapplied_builtins.aes @@ -9,7 +9,7 @@ // Oracle.extend include "String.aes" contract UnappliedBuiltins = - entrypoint main() = () + entrypoint main_fun() = () type o = oracle(int, int) type t = list(int * string) type m = map(int, int) -- 2.30.2 From dcb311a754f6a83c6053521ac4dc70b6f0e56470 Mon Sep 17 00:00:00 2001 From: radrow Date: Wed, 7 Apr 2021 08:42:17 +0200 Subject: [PATCH 02/34] Add missing files --- src/aeso_utils.hrl | 6 ++++++ test/contracts/interface_with_defs.aes | 5 +++++ 2 files changed, 11 insertions(+) create mode 100644 src/aeso_utils.hrl create mode 100644 test/contracts/interface_with_defs.aes diff --git a/src/aeso_utils.hrl b/src/aeso_utils.hrl new file mode 100644 index 0000000..31fd2eb --- /dev/null +++ b/src/aeso_utils.hrl @@ -0,0 +1,6 @@ +-define(IS_CONTRACT_HEAD(X), + (X =:= contract_main orelse + X =:= contract_interface orelse + X =:= contract_child + ) + ). diff --git a/test/contracts/interface_with_defs.aes b/test/contracts/interface_with_defs.aes new file mode 100644 index 0000000..8f91843 --- /dev/null +++ b/test/contracts/interface_with_defs.aes @@ -0,0 +1,5 @@ +contract interface ContractOne = + entrypoint foo() = "foo" + +contract ContractTwo = + entrypoint bar() = "bar" -- 2.30.2 From 59b9036a7bb3ff18c4d34c21e9031ac1623da2ec Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 19 Apr 2021 15:43:20 +0200 Subject: [PATCH 03/34] Pushed the clone example through the typechecker --- src/aeso_ast_infer_types.erl | 54 +++++++++++++++++++++++---- src/aeso_ast_to_fcode.erl | 13 +++++-- src/aeso_fcode_to_fate.erl | 26 ++++++++++++- test/aeso_aci_tests.erl | 72 ++++++++++++++++++------------------ test/aeso_compiler_tests.erl | 5 ++- 5 files changed, 123 insertions(+), 47 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 3f8c3ce..095d8ff 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -20,7 +20,7 @@ -include("aeso_utils.hrl"). --type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()], utype()} +-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() @@ -408,6 +408,7 @@ global_env() -> FunC = fun(C, Ts, T) -> {type_sig, Ann, C, [], Ts, 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, %% 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, @@ -473,7 +474,18 @@ global_env() -> {"block_height", Int}, {"difficulty", Int}, {"gas_limit", Int}, - {"bytecode_hash", Fun1(Address, Option(Hash))}, + {"bytecode_hash",Fun1(Address, Option(Hash))}, + {"create", FunCN(create, + [ {named_arg_t, Ann, {id, Ann, "value"}, Int, {int, Ann, 0}} + , {named_arg_t, Ann, {id, Ann, "code"}, A, undefined} + ], var_args, A)}, + {"clone", FunCN(clone, + [ {named_arg_t, Ann, {id, Ann, "gas"}, Int, + {qid, Ann, ["Call","gas_left"]}} + , {named_arg_t, Ann, {id, Ann, "value"}, Int, {int, Ann, 0}} + , {named_arg_t, Ann, {id, Ann, "protected"}, Bool, {bool, Ann, false}} + , {named_arg_t, Ann, {id, Ann, "ref"}, A, undefined} + ], var_args, A)}, %% Tx constructors {"GAMetaTx", Fun([Address, Int], GAMetaTx)}, {"PayingForTx", Fun([Address, Int], PayForTx)}, @@ -1447,8 +1459,7 @@ infer_expr(Env, {typed, As, Body, Type}) -> {typed, _, NewBody, NewType} = check_expr(Env, Body, Type1), {typed, As, NewBody, NewType}; infer_expr(Env, {app, Ann, Fun, Args0} = App) -> - NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ], - Args = Args0 -- NamedArgs, + {NamedArgs, Args} = split_args(Args0), case aeso_syntax:get_ann(format, Ann) of infix -> infer_op(Env, Ann, Fun, Args, fun infer_infix/1); @@ -1457,10 +1468,27 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> _ -> NamedArgsVar = fresh_uvar(Ann), NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ], - %% TODO: named args constraints - NewFun={typed, _, _, FunType} = infer_expr(Env, Fun), + NewFun = {typed, _, _, FunType0} = infer_expr(Env, Fun), NewArgs = [infer_expr(Env, A) || A <- Args], ArgTypes = [T || {typed, _, _, T} <- NewArgs], + FunType = + case Fun of + {qid, _, ["Chain", "clone"]} -> + {fun_t, FunAnn, NamedArgsT, var_args, RetT} = FunType0, + {typed, CAnn, Contract, ContractT} = + case [Contract || {named_arg, _, {id, _, "ref"}, Contract} <- NamedArgs1] of + [C] -> C; + _ -> type_error({clone_no_contract, Ann}) + end, + NamedArgsTNoRef = + lists:filter(fun({named_arg_t, _, {id, _, "ref"}, _, _}) -> false; (_) -> true end, NamedArgsT), + {typed, _, _, InitT} = + infer_expr(Env, {proj, CAnn, Contract, {id, [], "init"}}), + unify(Env, InitT, {fun_t, FunAnn, NamedArgsTNoRef, ArgTypes, fresh_uvar(CAnn)}, checking_init_todo), + unify(Env, RetT, ContractT, dupadupa_todo), + {fun_t, FunAnn, NamedArgsT, ArgTypes, RetT}; + _ -> FunType0 + end, GeneralResultType = fresh_uvar(Ann), ResultType = fresh_uvar(Ann), When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes}, @@ -1576,6 +1604,11 @@ infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) -> type_error({missing_body_for_let, Attrs}), infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}). +split_args(Args0) -> + NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ], + Args = Args0 -- NamedArgs, + {NamedArgs, Args}. + infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) -> CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E), check_stateful_named_arg(Env, Id, E), @@ -2348,6 +2381,8 @@ unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) -> unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, Else2}, When) -> unify(Env, Then1, Then2, When) andalso unify(Env, Else1, Else2, When); +unify1(Env, {fun_t, _, Named1, var_args, Result1}, {fun_t, _, Named2, Args2, Result2}, When) -> + error(unify_varargs); %% TODO unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) when length(Args1) == length(Args2) -> unify(Env, Named1, Named2, When) andalso @@ -2358,6 +2393,9 @@ unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, When) when length(As) == length(Bs) -> unify(Env, As, Bs, When); +unify1(Env, {named_arg_t, _, Id1, Type1, _}, {named_arg_t, _, Id2, Type2, _}, When) -> + unify1(Env, Id1, Id2, {arg_name, When}), %% TODO + unify1(Env, Type1, Type2, When); %% The grammar is a bit inconsistent about whether types without %% arguments are represented as applications to an empty list of %% parameters or not. We therefore allow them to unify. @@ -2465,7 +2503,9 @@ apply_typesig_constraint(Ann, address_to_contract, {fun_t, _, [], [_], Type}) -> apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) -> add_bytes_constraint({add_bytes, Ann, concat, A, B, C}); apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) -> - add_bytes_constraint({add_bytes, Ann, split, A, B, C}). + add_bytes_constraint({add_bytes, Ann, split, A, B, C}); +apply_typesig_constraint(Ann, clone, {fun_t, _, Named, var_args, Ret}) -> + ok. %% Dereferences all uvars and replaces the uninstantiated ones with a %% succession of tvars. diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 27de5b3..e9b01f9 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -139,7 +139,7 @@ -type fun_env() :: #{ sophia_name() => {fun_name(), non_neg_integer()} }. -type con_env() :: #{ sophia_name() => con_tag() }. -type child_con_env() :: #{sophia_name() => fcode()}. --type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none} }. +-type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none | variable} }. -type context() :: {main_contract, string()} | {namespace, string()} @@ -232,7 +232,7 @@ builtins() -> Scopes = [{[], [{"abort", 1}, {"require", 2}]}, {["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none}, {"timestamp", none}, {"block_height", none}, {"difficulty", none}, - {"gas_limit", none}]}, + {"gas_limit", none}, {"bytecode_hash", 1}, {"create", variable}, {"clone", variable}]}, {["Contract"], [{"address", none}, {"balance", none}, {"creator", none}]}, {["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none}, {"gas_left", 0}]}, @@ -692,11 +692,18 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) -> end; %% Function calls -expr_to_fcode(Env, _Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) -> +expr_to_fcode(Env, _Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, ArgsT, _}}, Args}) -> Args1 = get_named_args(NamedArgsT, Args), FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1], case expr_to_fcode(Env, Fun) of {builtin_u, B, _Ar, TypeArgs} -> builtin_to_fcode(state_layout(Env), B, FArgs ++ TypeArgs); + {builtin_u, chain_clone, _Ar} -> + case ArgsT of + [_Con|InitArgs] -> + FInitArgsT = {tuple, [type_to_fcode(Env, T) || T <- InitArgs]}, + builtin_to_fcode(state_layout(Env), chain_clone, [FInitArgsT|FArgs]); + [] -> error(wtf) %% FIXME + end; {builtin_u, B, _Ar} -> builtin_to_fcode(state_layout(Env), B, FArgs); {def_u, F, _Ar} -> {def, F, FArgs}; {remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs}; diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 623b2ec..175c183 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -555,7 +555,31 @@ builtin_to_scode(Env, aens_lookup, [_Name] = Args) -> builtin_to_scode(_Env, auth_tx_hash, []) -> [aeb_fate_ops:auth_tx_hash(?a)]; builtin_to_scode(_Env, auth_tx, []) -> - [aeb_fate_ops:auth_tx(?a)]. + [aeb_fate_ops:auth_tx(?a)]; +builtin_to_scode(Env, chain_bytecode_hash, [_Addr] = Args) -> + call_to_scode(Env, aeb_fate_ops:bytecode_hash(?a), Args); +builtin_to_scode(Env, chain_clone, + [InitArgs, _GasCap = {con,[0,1],0,[]}, Prot, Value, Contract | InitArgs]) -> + error(InitArgs), + TypeRep = xd, + call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a), + [Contract, TypeRep, Value, Prot | InitArgs] + ); +builtin_to_scode(Env, chain_clone, + [_GasCap = {con,[0,1],0,[]}, Prot, Value, Contract | InitArgs]) -> + TypeRep = xd, + call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a), + [Contract, TypeRep, Value, Prot | InitArgs] + ); +builtin_to_scode(Env, chain_create, + [_GasCap = {con,[0,1],0,[]}, Prot, Value, Contract | InitArgs]) -> + TypeRep = xd, + call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a), + [Contract, TypeRep, Value, Prot | InitArgs] + ). + + + %% -- Operators -- diff --git a/test/aeso_aci_tests.erl b/test/aeso_aci_tests.erl index d570390..3254e69 100644 --- a/test/aeso_aci_tests.erl +++ b/test/aeso_aci_tests.erl @@ -22,7 +22,8 @@ test_cases(1) -> MapACI = #{contract => #{name => <<"C">>, type_defs => [], - payable => true, + payable => true, + kind => contract_main, functions => [#{name => <<"a">>, arguments => @@ -31,56 +32,57 @@ test_cases(1) -> returns => <<"int">>, stateful => true, payable => true}]}}, - DecACI = <<"payable contract C =\n" + DecACI = <<"payable main contract C =\n" " payable entrypoint a : (int) => int\n">>, {Contract,MapACI,DecACI}; test_cases(2) -> - Contract = <<"contract C =\n" + Contract = <<"main contract C =\n" " type allan = int\n" " entrypoint a(i : allan) = i+1\n">>, MapACI = #{contract => - #{name => <<"C">>, payable => false, - type_defs => - [#{name => <<"allan">>, - typedef => <<"int">>, - vars => []}], - functions => - [#{arguments => - [#{name => <<"i">>, - type => <<"C.allan">>}], - name => <<"a">>, - returns => <<"int">>, - stateful => false, - payable => false}]}}, - DecACI = <<"contract C =\n" + #{name => <<"C">>, payable => false, + kind => contract_main, + type_defs => + [#{name => <<"allan">>, + typedef => <<"int">>, + vars => []}], + functions => + [#{arguments => + [#{name => <<"i">>, + type => <<"C.allan">>}], + name => <<"a">>, + returns => <<"int">>, + stateful => false, + payable => false}]}}, + DecACI = <<"main contract C =\n" " type allan = int\n" " entrypoint a : (C.allan) => int\n">>, {Contract,MapACI,DecACI}; test_cases(3) -> - Contract = <<"contract C =\n" + Contract = <<"main contract C =\n" " type state = unit\n" " datatype event = SingleEventDefined\n" - " datatype bert('a) = Bin('a)\n" - " entrypoint a(i : bert(string)) = 1\n">>, + " datatype bert('a) = Bin('a)\n" + " entrypoint a(i : bert(string)) = 1\n">>, MapACI = #{contract => #{functions => - [#{arguments => - [#{name => <<"i">>, - type => - #{<<"C.bert">> => [<<"string">>]}}], - name => <<"a">>,returns => <<"int">>, - stateful => false, payable => false}], - name => <<"C">>, payable => false, - event => #{variant => [#{<<"SingleEventDefined">> => []}]}, - state => <<"unit">>, + [#{arguments => + [#{name => <<"i">>, + type => + #{<<"C.bert">> => [<<"string">>]}}], + name => <<"a">>,returns => <<"int">>, + stateful => false, payable => false}], + name => <<"C">>, payable => false, kind => contract_main, + event => #{variant => [#{<<"SingleEventDefined">> => []}]}, + state => <<"unit">>, type_defs => - [#{name => <<"bert">>, - typedef => - #{variant => - [#{<<"Bin">> => [<<"'a">>]}]}, - vars => [#{name => <<"'a">>}]}]}}, - DecACI = <<"contract C =\n" + [#{name => <<"bert">>, + typedef => + #{variant => + [#{<<"Bin">> => [<<"'a">>]}]}, + vars => [#{name => <<"'a">>}]}]}}, + DecACI = <<"main contract C =\n" " type state = unit\n" " datatype event = SingleEventDefined\n" " datatype bert('a) = Bin('a)\n" diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 3e3cfa5..7131076 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -127,6 +127,7 @@ compile(Backend, Name, Options) -> %% compilable_contracts() -> [ContractName]. %% The currently compilable contracts. +compilable_contracts() -> ["clone"]; % FIXME remove compilable_contracts() -> ["complex_types", "counter", @@ -180,7 +181,8 @@ compilable_contracts() -> "more_strings", "protected_call", "hermetization_turnoff", - "multiple_contracts" + "multiple_contracts", + "clone" ]. not_compilable_on(fate) -> []; @@ -205,6 +207,7 @@ debug_mode_contracts() -> -define(TYPE_ERROR(Name, Errs), ?ERROR("Type", Name, Errs)). -define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)). +failing_contracts() -> []; % FIXME remove failing_contracts() -> {ok, V} = aeso_compiler:numeric_version(), Version = list_to_binary(string:join([integer_to_list(N) || N <- V], ".")), -- 2.30.2 From 851952987c4d47a2a92aba61fe66060dca7cc446 Mon Sep 17 00:00:00 2001 From: radrow Date: Tue, 20 Apr 2021 09:48:27 +0200 Subject: [PATCH 04/34] CLONE compiles --- rebar.config | 2 +- rebar.lock | 2 +- src/aeso_ast_infer_types.erl | 8 ++++++-- src/aeso_ast_to_fcode.erl | 12 +++++++----- src/aeso_code_errors.erl | 4 ++++ src/aeso_fcode_to_fate.erl | 21 ++++++++++----------- 6 files changed, 29 insertions(+), 20 deletions(-) diff --git a/rebar.config b/rebar.config index 7107fa8..771f30e 100644 --- a/rebar.config +++ b/rebar.config @@ -2,7 +2,7 @@ {erl_opts, [debug_info]}. -{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"7f0d309"}}} +{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"951db9f"}}} , {getopt, "1.0.1"} , {eblake2, "1.0.0"} , {jsx, {git, "https://github.com/talentdeficit/jsx.git", diff --git a/rebar.lock b/rebar.lock index 54e0e24..477ca9f 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,7 +1,7 @@ {"1.1.0", [{<<"aebytecode">>, {git,"https://github.com/aeternity/aebytecode.git", - {ref,"7f0d3090d4dc6c4d5fca7645b0c21eb0e65ad208"}}, + {ref,"951db9f38412a1f798987403d24c512d82fec8c7"}}, 0}, {<<"aeserialization">>, {git,"https://github.com/aeternity/aeserialization.git", diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 095d8ff..ea58d01 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1468,7 +1468,7 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> _ -> NamedArgsVar = fresh_uvar(Ann), NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ], - NewFun = {typed, _, _, FunType0} = infer_expr(Env, Fun), + NewFun0 = {typed, _, _, FunType0} = infer_expr(Env, Fun), NewArgs = [infer_expr(Env, A) || A <- Args], ArgTypes = [T || {typed, _, _, T} <- NewArgs], FunType = @@ -1491,6 +1491,7 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> end, GeneralResultType = fresh_uvar(Ann), ResultType = fresh_uvar(Ann), + NewFun1 = setelement(4, NewFun0, FunType), When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes}, unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When), add_named_argument_constraint( @@ -1499,7 +1500,7 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> general_type = GeneralResultType, specialized_type = ResultType, context = {check_return, App} }), - {typed, Ann, {app, Ann, NewFun, NamedArgs1 ++ NewArgs}, dereference(ResultType)} + {typed, Ann, {app, Ann, NewFun1, NamedArgs1 ++ NewArgs}, dereference(ResultType)} end; infer_expr(Env, {'if', Attrs, Cond, Then, Else}) -> NewCond = check_expr(Env, Cond, {id, Attrs, "bool"}), @@ -2381,6 +2382,9 @@ unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) -> unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, Else2}, When) -> unify(Env, Then1, Then2, When) andalso unify(Env, Else1, Else2, When); + +unify1(Env, {fun_t, _, Named1, _, Result1}, {fun_t, _, Named2, var_args, Result2}, When) -> + error(unify_varargs); %% TODO unify1(Env, {fun_t, _, Named1, var_args, Result1}, {fun_t, _, Named2, Args2, Result2}, When) -> error(unify_varargs); %% TODO unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index e9b01f9..58f7a54 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -477,6 +477,8 @@ type_to_fcode(_Env, _Sub, {bytes_t, _, N}) -> {bytes, N}; type_to_fcode(_Env, Sub, {tvar, _, X}) -> maps:get(X, Sub, {tvar, X}); +type_to_fcode(_Env, _Sub, {fun_t, Ann, _, var_args, _}) -> + fcode_error({var_args_not_set, {id, Ann, "a very suspicious function"}}); type_to_fcode(Env, Sub, {fun_t, _, Named, Args, Res}) -> FNamed = [type_to_fcode(Env, Sub, Arg) || {named_arg_t, _, _, Arg, _} <- Named], FArgs = [type_to_fcode(Env, Sub, Arg) || Arg <- Args], @@ -692,17 +694,17 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) -> end; %% Function calls -expr_to_fcode(Env, _Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, ArgsT, _}}, Args}) -> +expr_to_fcode(Env, _Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, _}}, Args}) -> Args1 = get_named_args(NamedArgsT, Args), FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1], case expr_to_fcode(Env, Fun) of {builtin_u, B, _Ar, TypeArgs} -> builtin_to_fcode(state_layout(Env), B, FArgs ++ TypeArgs); {builtin_u, chain_clone, _Ar} -> case ArgsT of - [_Con|InitArgs] -> - FInitArgsT = {tuple, [type_to_fcode(Env, T) || T <- InitArgs]}, - builtin_to_fcode(state_layout(Env), chain_clone, [FInitArgsT|FArgs]); - [] -> error(wtf) %% FIXME + var_args -> fcode_error({var_args_not_set, FunE}); + _ -> + FInitArgsT = {typerep, {tuple, [type_to_fcode(Env, T) || T <- ArgsT]}}, + builtin_to_fcode(state_layout(Env), chain_clone, [{lit, FInitArgsT}|FArgs]) end; {builtin_u, B, _Ar} -> builtin_to_fcode(state_layout(Env), B, FArgs); {def_u, F, _Ar} -> {def, F, FArgs}; diff --git a/src/aeso_code_errors.erl b/src/aeso_code_errors.erl index bb2568d..6c05ff6 100644 --- a/src/aeso_code_errors.erl +++ b/src/aeso_code_errors.erl @@ -87,6 +87,10 @@ 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", mk_err(pos(Ann), Msg, Cxt); +format({var_args_not_set, Expr}) -> + mk_err( pos(Expr), "Could not deduce type of variadic arguments" + , "When compiling " ++ pp_expr(Expr) + ); format(Err) -> mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])). diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 175c183..659fcab 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -559,17 +559,9 @@ builtin_to_scode(_Env, auth_tx, []) -> builtin_to_scode(Env, chain_bytecode_hash, [_Addr] = Args) -> call_to_scode(Env, aeb_fate_ops:bytecode_hash(?a), Args); builtin_to_scode(Env, chain_clone, - [InitArgs, _GasCap = {con,[0,1],0,[]}, Prot, Value, Contract | InitArgs]) -> - error(InitArgs), - TypeRep = xd, - call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a), - [Contract, TypeRep, Value, Prot | InitArgs] - ); -builtin_to_scode(Env, chain_clone, - [_GasCap = {con,[0,1],0,[]}, Prot, Value, Contract | InitArgs]) -> - TypeRep = xd, - call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a), - [Contract, TypeRep, Value, Prot | InitArgs] + [TypeRep, GasCap, Value, Prot, Contract | InitArgs]) -> + call_to_scode(Env, aeb_fate_ops:clone_g(?a, ?a, ?a, ?a, ?a), + [Contract, TypeRep, Value, GasCap, Prot | InitArgs] ); builtin_to_scode(Env, chain_create, [_GasCap = {con,[0,1],0,[]}, Prot, Value, Contract | InitArgs]) -> @@ -965,6 +957,10 @@ attributes(I) -> {'STR_TO_LOWER', A, B} -> Pure(A, [B]); {'CHAR_TO_INT', A, B} -> Pure(A, [B]); {'CHAR_FROM_INT', A, B} -> Pure(A, [B]); + {'CREATE', A, B, C} -> Impure(?a, [A, B, C]); + {'CLONE', A, B, C, D} -> Impure(?a, [A, B, C, D]); + {'CLONE_G', A, B, C, D, E} -> Impure(?a, [A, B, C, D, E]); + {'BYTECODE_HASH', A, B} -> Pure(A, [B]); {'ABORT', A} -> Impure(pc, A); {'EXIT', A} -> Impure(pc, A); 'NOP' -> Pure(none, []) @@ -1718,6 +1714,9 @@ split_calls(Ref, [I | Code], Acc, Blocks) when element(1, I) == 'CALL'; element(1, I) == 'CALL_R'; element(1, I) == 'CALL_GR'; element(1, I) == 'CALL_PGR'; + element(1, I) == 'CREATE'; + element(1, I) == 'CLONE'; + element(1, I) == 'CLONE_G'; element(1, I) == 'jumpif' -> split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]); split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) -> -- 2.30.2 From 545f16da65679494114aa4f9d15693546e99036d Mon Sep 17 00:00:00 2001 From: radrow Date: Tue, 20 Apr 2021 14:55:24 +0200 Subject: [PATCH 05/34] Fix dependent type in CLONE --- src/aeso_ast_infer_types.erl | 16 +++++++++------- src/aeso_ast_to_fcode.erl | 2 ++ src/aeso_code_errors.erl | 2 ++ test/aeso_compiler_tests.erl | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index ea58d01..dcd67b3 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -454,6 +454,7 @@ global_env() -> {"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)}}}, @@ -476,14 +477,14 @@ global_env() -> {"gas_limit", Int}, {"bytecode_hash",Fun1(Address, Option(Hash))}, {"create", FunCN(create, - [ {named_arg_t, Ann, {id, Ann, "value"}, Int, {int, Ann, 0}} + [ {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}} , {named_arg_t, Ann, {id, Ann, "code"}, A, undefined} ], var_args, A)}, {"clone", FunCN(clone, [ {named_arg_t, Ann, {id, Ann, "gas"}, Int, {qid, Ann, ["Call","gas_left"]}} - , {named_arg_t, Ann, {id, Ann, "value"}, Int, {int, Ann, 0}} - , {named_arg_t, Ann, {id, Ann, "protected"}, Bool, {bool, Ann, false}} + , {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 @@ -1484,9 +1485,10 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> lists:filter(fun({named_arg_t, _, {id, _, "ref"}, _, _}) -> false; (_) -> true end, NamedArgsT), {typed, _, _, InitT} = infer_expr(Env, {proj, CAnn, Contract, {id, [], "init"}}), - unify(Env, InitT, {fun_t, FunAnn, NamedArgsTNoRef, ArgTypes, fresh_uvar(CAnn)}, checking_init_todo), + unify(Env, InitT, {fun_t, FunAnn, NamedArgsTNoRef, ArgTypes, fresh_uvar(Ann)}, checking_init_todo), unify(Env, RetT, ContractT, dupadupa_todo), - {fun_t, FunAnn, NamedArgsT, ArgTypes, RetT}; + {fun_t, Ann, NamedArgsT, ArgTypes, + {if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}}; _ -> FunType0 end, GeneralResultType = fresh_uvar(Ann), @@ -1907,6 +1909,7 @@ check_named_argument_constraint(Env, general_type = GenType, specialized_type = SpecType, context = {check_return, App} }) -> + io:format("CEHCK DEP: GEN ~p, SPEC: ~p\n", [GenType, SpecType]), NamedArgsT = dereference(NamedArgsT0), case dereference(NamedArgsT0) of [_ | _] = NamedArgsT -> @@ -1931,6 +1934,7 @@ specialize_dependent_type(Env, Type) -> {typed, _, {bool, _, false}, _} -> Else; _ -> type_error({named_argument_must_be_literal_bool, Arg, Val}), + io:format("CHYUJ: ~p\n", [Val]), fresh_uvar(aeso_syntax:get_ann(Val)) end; _ -> Type %% Currently no deep dependent types @@ -2457,8 +2461,6 @@ occurs_check1(R, [H | T]) -> occurs_check(R, H) orelse occurs_check(R, T); occurs_check1(_, []) -> false. -fresh_uvar([{origin, system}]) -> - error(oh_no_you_dont); fresh_uvar(Attrs) -> {uvar, Attrs, make_ref()}. diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 58f7a54..d658309 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -475,6 +475,8 @@ type_to_fcode(Env, Sub, {record_t, Fields}) -> type_to_fcode(Env, Sub, {tuple_t, [], lists:map(FieldType, Fields)}); type_to_fcode(_Env, _Sub, {bytes_t, _, N}) -> {bytes, N}; +type_to_fcode(_Env, _Sub, {tvar, Ann, "void"}) -> + fcode_error({found_void, Ann}); type_to_fcode(_Env, Sub, {tvar, _, X}) -> maps:get(X, Sub, {tvar, X}); type_to_fcode(_Env, _Sub, {fun_t, Ann, _, var_args, _}) -> diff --git a/src/aeso_code_errors.erl b/src/aeso_code_errors.erl index 6c05ff6..74bc8b1 100644 --- a/src/aeso_code_errors.erl +++ b/src/aeso_code_errors.erl @@ -91,6 +91,8 @@ format({var_args_not_set, Expr}) -> mk_err( pos(Expr), "Could not deduce type of variadic arguments" , "When compiling " ++ pp_expr(Expr) ); +format({found_void, Ann}) -> + mk_err(pos(Ann), "Found a void-typed value.", "`void` is a restricted, uninhabited type. Did you mean `unit`?"); format(Err) -> mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])). diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 7131076..eca7383 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -182,7 +182,7 @@ compilable_contracts() -> "protected_call", "hermetization_turnoff", "multiple_contracts", - "clone" + "clone", "clone_simple" ]. not_compilable_on(fate) -> []; -- 2.30.2 From 8e6cf7ddfc0b9ff08fcb7ba8845c2136fa35f518 Mon Sep 17 00:00:00 2001 From: radrow Date: Tue, 20 Apr 2021 16:31:06 +0200 Subject: [PATCH 06/34] Bytecode hash fixes --- src/aeso_ast_infer_types.erl | 19 ++++-- src/aeso_fcode_to_fate.erl | 2 +- test/aeso_compiler_tests.erl | 2 +- test/contracts/clone.aes | 26 ++++++++ test/contracts/clone_simple.aes | 7 ++ test/contracts/test.aes | 109 +++----------------------------- 6 files changed, 57 insertions(+), 108 deletions(-) create mode 100644 test/contracts/clone.aes create mode 100644 test/contracts/clone_simple.aes diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index dcd67b3..3a43886 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -406,6 +406,7 @@ global_env() -> 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, @@ -475,7 +476,7 @@ global_env() -> {"block_height", Int}, {"difficulty", Int}, {"gas_limit", Int}, - {"bytecode_hash",Fun1(Address, Option(Hash))}, + {"bytecode_hash",FunC1(bytecode_hash, A, Option(Hash))}, {"create", FunCN(create, [ {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}} , {named_arg_t, Ann, {id, Ann, "code"}, A, undefined} @@ -1909,7 +1910,6 @@ check_named_argument_constraint(Env, general_type = GenType, specialized_type = SpecType, context = {check_return, App} }) -> - io:format("CEHCK DEP: GEN ~p, SPEC: ~p\n", [GenType, SpecType]), NamedArgsT = dereference(NamedArgsT0), case dereference(NamedArgsT0) of [_ | _] = NamedArgsT -> @@ -1934,7 +1934,6 @@ specialize_dependent_type(Env, Type) -> {typed, _, {bool, _, false}, _} -> Else; _ -> type_error({named_argument_must_be_literal_bool, Arg, Val}), - io:format("CHYUJ: ~p\n", [Val]), fresh_uvar(aeso_syntax:get_ann(Val)) end; _ -> Type %% Currently no deep dependent types @@ -2510,8 +2509,14 @@ apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) -> add_bytes_constraint({add_bytes, Ann, concat, A, B, C}); apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) -> add_bytes_constraint({add_bytes, Ann, split, A, B, C}); -apply_typesig_constraint(Ann, clone, {fun_t, _, Named, var_args, Ret}) -> - ok. +apply_typesig_constraint(Ann, clone, {fun_t, _, Named, var_args, _}) -> + [RefT] = [RefT || {named_arg_t, _, {id, _, "ref"}, RefT, _} <- Named], + constrain([#is_contract_constraint{ contract_t = RefT, + context = {clone, Ann} }]); +apply_typesig_constraint(Ann, bytecode_hash, {fun_t, _, _, [Con], _}) -> + constrain([#is_contract_constraint{ contract_t = Con, + context = {bytecode_hash, Ann} }]). + %% Dereferences all uvars and replaces the uninstantiated ones with a %% succession of tvars. @@ -2640,6 +2645,10 @@ mk_error({not_a_contract_type, Type, Cxt}) -> end, {Pos, Cxt1} = case Cxt of + {clone, Ann} -> + {pos(Ann), "when calling Chain.clone"}; + {bytecode_hash, Ann} -> + {pos(Ann), "when calling Chain.bytecode_hash"}; {contract_literal, Lit} -> {pos(Lit), io_lib:format("when checking that the contract literal\n~s\n" diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 659fcab..a525dcc 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -557,7 +557,7 @@ builtin_to_scode(_Env, auth_tx_hash, []) -> builtin_to_scode(_Env, auth_tx, []) -> [aeb_fate_ops:auth_tx(?a)]; builtin_to_scode(Env, chain_bytecode_hash, [_Addr] = Args) -> - call_to_scode(Env, aeb_fate_ops:bytecode_hash(?a), Args); + call_to_scode(Env, aeb_fate_ops:bytecode_hash(?a, ?a), Args); builtin_to_scode(Env, chain_clone, [TypeRep, GasCap, Value, Prot, Contract | InitArgs]) -> call_to_scode(Env, aeb_fate_ops:clone_g(?a, ?a, ?a, ?a, ?a), diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index eca7383..19e8630 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -127,7 +127,7 @@ compile(Backend, Name, Options) -> %% compilable_contracts() -> [ContractName]. %% The currently compilable contracts. -compilable_contracts() -> ["clone"]; % FIXME remove +compilable_contracts() -> ["test"]; % FIXME remove compilable_contracts() -> ["complex_types", "counter", diff --git a/test/contracts/clone.aes b/test/contracts/clone.aes new file mode 100644 index 0000000..d66408d --- /dev/null +++ b/test/contracts/clone.aes @@ -0,0 +1,26 @@ + +contract interface HigherOrderState = + entrypoint init : () => void + entrypoint apply : int => int + stateful entrypoint inc : int => unit + +contract interface LowerDisorderAnarchy = + entrypoint init : int => void + + +main contract C = + stateful entrypoint run_clone(s : HigherOrderState, l : LowerDisorderAnarchy) : HigherOrderState = + let s1 = Chain.clone(ref=s) + let Some(s2) = Chain.clone(ref=s, protected=true) + let None = Chain.clone(ref=l, protected=true, 123) + let s3 = Chain.clone(ref=s1) + require(s1.apply(2137) == 2137, "APPLY_S1_0") + require(s2.apply(2137) == 2137, "APPLY_S2_0") + require(s3.apply(2137) == 2137, "APPLY_S3_0") + s1.inc(1) + s2.inc(1) + s1.inc(1) + require(s1.apply(2137) == 2139, "APPLY_S1_2") + require(s2.apply(2137) == 2138, "APPLY_S2_1") + require(s3.apply(2137) == 2137, "APPLY_S3_0") + s1 \ No newline at end of file diff --git a/test/contracts/clone_simple.aes b/test/contracts/clone_simple.aes new file mode 100644 index 0000000..72333e3 --- /dev/null +++ b/test/contracts/clone_simple.aes @@ -0,0 +1,7 @@ +contract interface I = + entrypoint init : () => void + +contract C = + entrypoint f(i : I) = + let Some(c1) = Chain.clone(ref=i, protected = true) + 2 \ No newline at end of file diff --git a/test/contracts/test.aes b/test/contracts/test.aes index c20002f..4fd2d73 100644 --- a/test/contracts/test.aes +++ b/test/contracts/test.aes @@ -1,102 +1,9 @@ +// If you need a quick compiler test — this file is for your playground +contract interface Remote = + entrypoint init : int => void + entrypoint f : int => int -contract Identity = - // type xy = {x:int, y:int} - // type xz = {x:int, z:int} - // type yz = {y:int, z:int} - record point = {x:int,y:int} - record cp('a) = {color: string, p:'a} - //type intpoint = point(int) - // //if (x==42) 1 else (x*x) - // } - //let baz() = {age:3, name:(4:int)} - //let foo(a,b,c) = c - // let rec fac(n) = if((n:int)==0) 1 else (n*fac(n-1)) - // and main(x) = x::[x+1] - // let lentr(l) = lent(0,l) - // let rec len(l) = - // switch(l) { - // | [] => 0 - // | x::xs => 1+len(xs) - // } - // let lent(n,l) = - // switch (l) { - // | [] => n - // | (x::xs) => lent(n+1,xs) - // } - // let rec app(a,b) = - // switch(a) { - // | [] => b - // | (x::xs) => x::app(xs,b) - // } - // let rec revt(l,r) = - // switch(l) { - // | [] => r - // | x::xs => revt(xs,x::r) - // } - // let rev(l) = revt(l,[]) - // let main(x:int) = { - // switch(rev([1,2,3])) { - // | h::_ => h - // } - // } - //let fac(n:int) = { - // if (n==0) 1 else (n*fac(n-1)) - //} - //let main(x) = switch((12,34)) { - //| (13,_) => x - //| (_,a) => x+a - // | y => y+1 - // } - //let main(x) = ({y:0>1, x:x==0}:point(bool)) - //let main(x) = x - //let main(x) = len(1::2::[]) - //let main(x) = ((x,x):list('a)) - // let main(x) = switch("a") { - // | "b" => 0 - // | "a" => 1 - // | "c" => 2 - // } - //let main(x) = x.color+1 - //let main(x) = switch(({x:x, y:x+1}:cp(int))) { - // | {y:xx} => xx - // } - //let main(x) = {x:0, y:1, z:2} - // let id(x) = x - // let double(x) = x+x - // let pair(x) = (1,2) - // let unit(x) = () - // let tuples(x) = ((1,x),(2,3,4)) - // let singleton(x) = [x] - // let rec seq(n) = if (n==0) [] else (app(seq(n-1),[n])) - // let idString(s:string) = s - // let pairString(s:string) = (s,s) - // let revStrings(ss:list(string))=rev(ss) - // let makePoint(x,y) = {x:x, y:y} - // let getx(x) = x.x - // let updatex(p,x) = p{x:x} - // let quad(x) = {let y=x+x; let z=y+y; z;} - // let noblock(x) = {x; x} - // let unit(x) = () - // let foo(x) = switch (x) { - // | y => y+1 -// } - // let p(x) = {color:"blue", p:{x:x, y:x+1}} - //let twice(f,x) = f(f(x)) - // let twice(f,x) = f(f(x)) - // let double(x) = x+x - // let main(x) = twice((y=>y+y),x) - // let rec map(f,xs) = switch(xs) { - // | [] => [] - // | (x::ys) => f(x)::map(f,ys) - // } - // let id(x) = x - // let main(xs) = map(double,xs) - function z(f,x) = x - function s(n) = (f,x)=>f(n(f,x)) - function add(m,n) = (f,x)=>m(f,n(f,x)) - - entrypoint main_fun() = - let three=s(s(s(z))) - add(three,three) - (((i)=>i+1),0) - +contract Test = + entrypoint kek(r : Remote) = + Chain.clone(ref=r, 123) + Chain.bytecode_hash(r) \ No newline at end of file -- 2.30.2 From c8692707243d9b13f8fce85bb10580e50a869acf Mon Sep 17 00:00:00 2001 From: radrow Date: Wed, 21 Apr 2021 12:53:52 +0200 Subject: [PATCH 07/34] Refactor --- src/aeso_ast_infer_types.erl | 87 ++++++++++++++++++++++++++---------- src/aeso_code_errors.erl | 2 +- 2 files changed, 65 insertions(+), 24 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 3a43886..68362d8 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -410,6 +410,7 @@ global_env() -> 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, @@ -477,17 +478,15 @@ global_env() -> {"difficulty", Int}, {"gas_limit", Int}, {"bytecode_hash",FunC1(bytecode_hash, A, Option(Hash))}, - {"create", FunCN(create, - [ {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}} - , {named_arg_t, Ann, {id, Ann, "code"}, A, undefined} - ], var_args, A)}, - {"clone", FunCN(clone, - [ {named_arg_t, Ann, {id, Ann, "gas"}, Int, - {qid, Ann, ["Call","gas_left"]}} - , {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)}, + {"create", FunN([ {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}} + , {named_arg_t, Ann, {id, Ann, "code"}, A, undefined} + ], var_args, A)}, + {"clone", FunN([ {named_arg_t, Ann, {id, Ann, "gas"}, Int, + {qid, Ann, ["Call","gas_left"]}} + , {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)}, @@ -1475,19 +1474,21 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> ArgTypes = [T || {typed, _, _, T} <- NewArgs], FunType = case Fun of + {qid, _, ["Chain", "create"]} -> + {fun_t, _, NamedArgsT, var_args, RetT} = FunType0, + check_contract_contstruction(Env, RetT, Fun, NamedArgsT, ArgTypes, RetT), + {fun_t, Ann, NamedArgsT, ArgTypes, + {if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}}; {qid, _, ["Chain", "clone"]} -> - {fun_t, FunAnn, NamedArgsT, var_args, RetT} = FunType0, - {typed, CAnn, Contract, ContractT} = - case [Contract || {named_arg, _, {id, _, "ref"}, Contract} <- NamedArgs1] of + {fun_t, _, NamedArgsT, var_args, RetT} = FunType0, + ContractT = + case [ContractT || {named_arg, _, {id, _, "ref"}, {typed, _, _, ContractT}} <- NamedArgs1] of [C] -> C; _ -> type_error({clone_no_contract, Ann}) end, NamedArgsTNoRef = lists:filter(fun({named_arg_t, _, {id, _, "ref"}, _, _}) -> false; (_) -> true end, NamedArgsT), - {typed, _, _, InitT} = - infer_expr(Env, {proj, CAnn, Contract, {id, [], "init"}}), - unify(Env, InitT, {fun_t, FunAnn, NamedArgsTNoRef, ArgTypes, fresh_uvar(Ann)}, checking_init_todo), - unify(Env, RetT, ContractT, dupadupa_todo), + check_contract_contstruction(Env, ContractT, Fun, NamedArgsTNoRef, ArgTypes, RetT), {fun_t, Ann, NamedArgsT, ArgTypes, {if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}}; _ -> FunType0 @@ -1608,6 +1609,21 @@ infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) -> type_error({missing_body_for_let, Attrs}), infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}). +check_contract_contstruction(Env, ContractT, Fun, NamedArgsT, ArgTypes, RetT) -> + Ann = aeso_syntax:get_ann(Fun), + InitT = fresh_uvar(Ann), + unify(Env, InitT, {fun_t, Ann, NamedArgsT, ArgTypes, fresh_uvar(Ann)}, {checking_init_args, Ann, ContractT, ArgTypes}), + unify(Env, RetT, ContractT, {return_contract, Fun, ContractT}), + constrain([ #field_constraint{ + record_t = unfold_types_in_type(Env, ContractT), + field = {id, Ann, "init"}, + field_t = InitT, + kind = project, + context = {var_args, Fun} } + , #is_contract_constraint{ contract_t = ContractT, + context = {var_args, Fun} } + ]). + split_args(Args0) -> NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ], Args = Args0 -- NamedArgs, @@ -2386,10 +2402,10 @@ unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, unify(Env, Then1, Then2, When) andalso unify(Env, Else1, Else2, When); -unify1(Env, {fun_t, _, Named1, _, Result1}, {fun_t, _, Named2, var_args, Result2}, When) -> - error(unify_varargs); %% TODO -unify1(Env, {fun_t, _, Named1, var_args, Result1}, {fun_t, _, Named2, Args2, Result2}, When) -> - error(unify_varargs); %% TODO +unify1(_Env, {fun_t, _, _, _, _}, {fun_t, _, _, var_args, _}, When) -> + type_error({unify_varargs, When}); +unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, When) -> + error({unify_varargs, When}); unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) when length(Args1) == length(Args2) -> unify(Env, Named1, Named2, When) andalso @@ -2401,7 +2417,7 @@ unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, When) when length(As) == length(Bs) -> unify(Env, As, Bs, When); unify1(Env, {named_arg_t, _, Id1, Type1, _}, {named_arg_t, _, Id2, Type2, _}, When) -> - unify1(Env, Id1, Id2, {arg_name, When}), %% TODO + unify1(Env, Id1, Id2, {arg_name, Id1, Id2, When}), unify1(Env, Type1, Type2, When); %% The grammar is a bit inconsistent about whether types without %% arguments are represented as applications to an empty list of @@ -2901,6 +2917,10 @@ mk_error({main_contract_undefined}) -> mk_error({multiple_main_contracts}) -> Msg = "Up to one main contract can be defined.", mk_t_err(pos(0, 0), Msg); +mk_error({unify_varargs, When}) -> + Msg = io_lib:format("Cannot unify variable argument list.\n"), + {Pos, Ctxt} = pp_when(When), + mk_t_err(Pos, Msg, Ctxt); mk_error(Err) -> Msg = io_lib:format("Unknown error: ~p\n", [Err]), mk_t_err(pos(0, 0), Msg). @@ -3011,6 +3031,23 @@ pp_when({check_named_arg_constraint, C}) -> Err = io_lib:format("when checking named argument\n~s\nagainst inferred type\n~s", [pp_typed(" ", Arg, Type), pp_type(" ", C#named_argument_constraint.type)]), {pos(Arg), Err}; +pp_when({checking_init_args, Ann, Con0, ArgTypes0}) -> + Con = instantiate(Con0), + ArgTypes = instantiate(ArgTypes0), + {pos(Ann), + io_lib:format("when checking arguments of ~s's init entrypoint to match\n(~s)", + [pp_type(Con), string:join([pp_type(A) || A <- ArgTypes], ", ")]) + }; +pp_when({return_contract, App, Con0}) -> + Con = instantiate(Con0), + {pos(App) + , io_lib:format("when checking that expression returns contract of type\n~s", [pp_type(" ", Con)]) + }; +pp_when({arg_name, Id1, Id2, When}) -> + {Pos, Ctx} = pp_when(When), + {Pos + , io_lib:format("when unifying names of named arguments: ~s and ~s\n~s", [pp_expr(Id1), pp_expr(Id2), Ctx]) + }; pp_when(unknown) -> {pos(0,0), ""}. -spec pp_why_record(why_record()) -> {pos(), iolist()}. @@ -3041,9 +3078,13 @@ pp_typed(Label, {typed, _, Expr, _}, Type) -> pp_typed(Label, Expr, Type) -> pp_expr(Label, {typed, [], Expr, Type}). +pp_expr(Expr) -> + pp_expr("", Expr). pp_expr(Label, Expr) -> prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:expr(Expr, [show_generated]))). +pp_type(Type) -> + pp_type("", Type). pp_type(Label, Type) -> prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated]))). diff --git a/src/aeso_code_errors.erl b/src/aeso_code_errors.erl index 74bc8b1..f12db23 100644 --- a/src/aeso_code_errors.erl +++ b/src/aeso_code_errors.erl @@ -88,7 +88,7 @@ format({higher_order_state, {type_def, Ann, _, _, State}}) -> Cxt = "The state cannot contain functions in the AEVM. Use FATE if you need this.\n", mk_err(pos(Ann), Msg, Cxt); format({var_args_not_set, Expr}) -> - mk_err( pos(Expr), "Could not deduce type of variadic arguments" + mk_err( pos(Expr), "Could not deduce type of variable arguments list" , "When compiling " ++ pp_expr(Expr) ); format({found_void, Ann}) -> -- 2.30.2 From fdcfcd25a22383088cd90c029bcd99342a9cff84 Mon Sep 17 00:00:00 2001 From: radrow Date: Wed, 21 Apr 2021 12:54:43 +0200 Subject: [PATCH 08/34] Refactor 2 --- src/aeso_ast_infer_types.erl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 68362d8..f5cb902 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1476,7 +1476,7 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> case Fun of {qid, _, ["Chain", "create"]} -> {fun_t, _, NamedArgsT, var_args, RetT} = FunType0, - check_contract_contstruction(Env, RetT, Fun, NamedArgsT, ArgTypes, RetT), + check_contract_construction(Env, RetT, Fun, NamedArgsT, ArgTypes, RetT), {fun_t, Ann, NamedArgsT, ArgTypes, {if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}}; {qid, _, ["Chain", "clone"]} -> @@ -1488,15 +1488,15 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> end, NamedArgsTNoRef = lists:filter(fun({named_arg_t, _, {id, _, "ref"}, _, _}) -> false; (_) -> true end, NamedArgsT), - check_contract_contstruction(Env, ContractT, Fun, NamedArgsTNoRef, ArgTypes, RetT), + check_contract_construction(Env, ContractT, Fun, NamedArgsTNoRef, ArgTypes, RetT), {fun_t, Ann, NamedArgsT, ArgTypes, {if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}}; _ -> FunType0 end, - GeneralResultType = fresh_uvar(Ann), - ResultType = fresh_uvar(Ann), NewFun1 = setelement(4, NewFun0, FunType), When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes}, + GeneralResultType = fresh_uvar(Ann), + ResultType = fresh_uvar(Ann), unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When), add_named_argument_constraint( #dependent_type_constraint{ named_args_t = NamedArgsVar, @@ -1609,7 +1609,7 @@ infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) -> type_error({missing_body_for_let, Attrs}), infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}). -check_contract_contstruction(Env, ContractT, Fun, NamedArgsT, ArgTypes, RetT) -> +check_contract_construction(Env, ContractT, Fun, NamedArgsT, ArgTypes, RetT) -> Ann = aeso_syntax:get_ann(Fun), InitT = fresh_uvar(Ann), unify(Env, InitT, {fun_t, Ann, NamedArgsT, ArgTypes, fresh_uvar(Ann)}, {checking_init_args, Ann, ContractT, ArgTypes}), -- 2.30.2 From b13a3a5d53eee9fc888ffae2e1519bba7cfa52e4 Mon Sep 17 00:00:00 2001 From: radrow Date: Wed, 21 Apr 2021 13:01:48 +0200 Subject: [PATCH 09/34] move some logic away --- src/aeso_ast_infer_types.erl | 49 +++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index f5cb902..fca36b4 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1469,31 +1469,10 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) -> _ -> NamedArgsVar = fresh_uvar(Ann), NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ], - NewFun0 = {typed, _, _, FunType0} = infer_expr(Env, Fun), + NewFun0 = infer_expr(Env, Fun), NewArgs = [infer_expr(Env, A) || A <- Args], ArgTypes = [T || {typed, _, _, T} <- NewArgs], - FunType = - case Fun of - {qid, _, ["Chain", "create"]} -> - {fun_t, _, NamedArgsT, var_args, RetT} = FunType0, - check_contract_construction(Env, RetT, Fun, NamedArgsT, ArgTypes, RetT), - {fun_t, Ann, NamedArgsT, ArgTypes, - {if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}}; - {qid, _, ["Chain", "clone"]} -> - {fun_t, _, NamedArgsT, var_args, RetT} = FunType0, - ContractT = - case [ContractT || {named_arg, _, {id, _, "ref"}, {typed, _, _, ContractT}} <- NamedArgs1] of - [C] -> C; - _ -> type_error({clone_no_contract, Ann}) - end, - NamedArgsTNoRef = - lists:filter(fun({named_arg_t, _, {id, _, "ref"}, _, _}) -> false; (_) -> true end, NamedArgsT), - check_contract_construction(Env, ContractT, Fun, NamedArgsTNoRef, ArgTypes, RetT), - {fun_t, Ann, NamedArgsT, ArgTypes, - {if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}}; - _ -> FunType0 - end, - NewFun1 = setelement(4, NewFun0, FunType), + NewFun1 = {typed, _, _, FunType} = infer_var_args_fun(Env, NewFun0, NamedArgs1, ArgTypes), When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes}, GeneralResultType = fresh_uvar(Ann), ResultType = fresh_uvar(Ann), @@ -1609,6 +1588,30 @@ infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) -> type_error({missing_body_for_let, Attrs}), infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}). +infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) -> + FunType = + case Fun of + {qid, _, ["Chain", "create"]} -> + {fun_t, _, NamedArgsT, var_args, RetT} = FunType0, + check_contract_construction(Env, RetT, Fun, NamedArgsT, ArgTypes, RetT), + {fun_t, Ann, NamedArgsT, ArgTypes, + {if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}}; + {qid, _, ["Chain", "clone"]} -> + {fun_t, _, NamedArgsT, var_args, RetT} = FunType0, + ContractT = + case [ContractT || {named_arg, _, {id, _, "ref"}, {typed, _, _, ContractT}} <- NamedArgs] of + [C] -> C; + _ -> type_error({clone_no_contract, Ann}) + end, + NamedArgsTNoRef = + lists:filter(fun({named_arg_t, _, {id, _, "ref"}, _, _}) -> false; (_) -> true end, NamedArgsT), + check_contract_construction(Env, ContractT, Fun, NamedArgsTNoRef, ArgTypes, RetT), + {fun_t, Ann, NamedArgsT, ArgTypes, + {if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}}; + _ -> FunType0 + end, + {typed, Ann, Fun, FunType}. + check_contract_construction(Env, ContractT, Fun, NamedArgsT, ArgTypes, RetT) -> Ann = aeso_syntax:get_ann(Fun), InitT = fresh_uvar(Ann), -- 2.30.2 From 6a46bb74aba001d05b61d7e835506b8d1666cf6e Mon Sep 17 00:00:00 2001 From: radrow Date: Wed, 21 Apr 2021 16:21:41 +0200 Subject: [PATCH 10/34] Fixed some error messages. Type inference of child contract still does some random shit\n(mistakes arguments with result type) --- src/aeso_ast_infer_types.erl | 64 +++++++++++++++++++++++------------- test/contracts/test.aes | 14 ++++---- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index fca36b4..4cdf902 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -41,6 +41,7 @@ element(1, T) =:= qcon). -type why_record() :: aeso_syntax:field(aeso_syntax:expr()) + | {var_args, aeso_syntax:ann(), aeso_syntax:expr()} | {proj, aeso_syntax:ann(), aeso_syntax:expr(), aeso_syntax:id()}. -type pos() :: aeso_errors:pos(). @@ -479,7 +480,6 @@ global_env() -> {"gas_limit", Int}, {"bytecode_hash",FunC1(bytecode_hash, A, Option(Hash))}, {"create", FunN([ {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}} - , {named_arg_t, Ann, {id, Ann, "code"}, A, undefined} ], var_args, A)}, {"clone", FunN([ {named_arg_t, Ann, {id, Ann, "gas"}, Int, {qid, Ann, ["Call","gas_left"]}} @@ -1593,9 +1593,10 @@ infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) -> case Fun of {qid, _, ["Chain", "create"]} -> {fun_t, _, NamedArgsT, var_args, RetT} = FunType0, - check_contract_construction(Env, RetT, Fun, NamedArgsT, ArgTypes, RetT), - {fun_t, Ann, NamedArgsT, ArgTypes, - {if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}}; + GasCapMock = {named_arg_t, Ann, {id, Ann, "gas"}, {id, Ann, "int"}, {int, Ann, 0}}, + ProtectedMock = {named_arg_t, Ann, {id, Ann, "protected"}, {id, Ann, "bool"}, {bool, Ann, false}}, + check_contract_construction(Env, RetT, Fun, [GasCapMock, ProtectedMock|NamedArgsT], ArgTypes, RetT), + {fun_t, Ann, NamedArgsT, ArgTypes, RetT}; {qid, _, ["Chain", "clone"]} -> {fun_t, _, NamedArgsT, var_args, RetT} = FunType0, ContractT = @@ -1617,14 +1618,15 @@ check_contract_construction(Env, ContractT, Fun, NamedArgsT, ArgTypes, RetT) -> InitT = fresh_uvar(Ann), unify(Env, InitT, {fun_t, Ann, NamedArgsT, ArgTypes, fresh_uvar(Ann)}, {checking_init_args, Ann, ContractT, ArgTypes}), unify(Env, RetT, ContractT, {return_contract, Fun, ContractT}), + io:format("DEREF: ~p\n", [dereference_deep(InitT)]), constrain([ #field_constraint{ record_t = unfold_types_in_type(Env, ContractT), field = {id, Ann, "init"}, field_t = InitT, kind = project, - context = {var_args, Fun} } + context = {var_args, Ann, Fun} } , #is_contract_constraint{ contract_t = ContractT, - context = {var_args, Fun} } + context = {var_args, Ann, Fun} } ]). split_args(Args0) -> @@ -1909,7 +1911,7 @@ solve_named_argument_constraints(Env, Constraints0) -> [ C || C <- dereference_deep(Constraints0), unsolved == check_named_argument_constraint(Env, C) ]. -%% If false, a type error have been emitted, so it's safe to drop the constraint. +%% If false, a type error has been emitted, so it's safe to drop the constraint. -spec check_named_argument_constraint(env(), named_argument_constraint()) -> true | false | unsolved. check_named_argument_constraint(_Env, #named_argument_constraint{ args = {uvar, _, _} }) -> unsolved; @@ -2410,8 +2412,14 @@ unify1(_Env, {fun_t, _, _, _, _}, {fun_t, _, _, var_args, _}, When) -> unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, When) -> error({unify_varargs, When}); unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) - when length(Args1) == length(Args2) -> - unify(Env, Named1, Named2, When) andalso + when length(Args1) == length(Args2) -> + Named1_ = if is_list(Named1) -> lists:keysort(3, Named1); + true -> Named1 + end, + Named2_ = if is_list(Named2) -> lists:keysort(3, Named2); + true -> Named2 + end, + unify(Env, Named1_, Named2_, When) andalso unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When); unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When) when length(Args1) == length(Args2), Tag == id orelse Tag == qid -> @@ -2528,10 +2536,6 @@ apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) -> add_bytes_constraint({add_bytes, Ann, concat, A, B, C}); apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) -> add_bytes_constraint({add_bytes, Ann, split, A, B, C}); -apply_typesig_constraint(Ann, clone, {fun_t, _, Named, var_args, _}) -> - [RefT] = [RefT || {named_arg_t, _, {id, _, "ref"}, RefT, _} <- Named], - constrain([#is_contract_constraint{ contract_t = RefT, - context = {clone, Ann} }]); apply_typesig_constraint(Ann, bytecode_hash, {fun_t, _, _, [Con], _}) -> constrain([#is_contract_constraint{ contract_t = Con, context = {bytecode_hash, Ann} }]). @@ -2664,10 +2668,9 @@ mk_error({not_a_contract_type, Type, Cxt}) -> end, {Pos, Cxt1} = case Cxt of - {clone, Ann} -> - {pos(Ann), "when calling Chain.clone"}; - {bytecode_hash, Ann} -> - {pos(Ann), "when calling Chain.bytecode_hash"}; + {var_args, Ann, Fun} -> + {pos(Ann), + io_lib:format("when calling variadic function\n~s\n", [pp_expr(" ", Fun)])}; {contract_literal, Lit} -> {pos(Lit), io_lib:format("when checking that the contract literal\n~s\n" @@ -2959,6 +2962,12 @@ pp_when({field_constraint, FieldType0, InferredType0, Fld}) -> InferredType = instantiate(InferredType0), {pos(Fld), case Fld of + {var_args, _Ann, _Fun} -> + io_lib:format("when checking contract construction of type\n~s (at ~s)\nagainst the expected type\n~s\n", + [pp_type(" ", FieldType), + pp_loc(Fld), + pp_type(" ", InferredType) + ]); {field, _Ann, LV, Id, E} -> io_lib:format("when checking the assignment of the field\n~s (at ~s)\nto the old value ~s and the new value\n~s\n", [pp_typed(" ", {lvalue, [], LV}, FieldType), @@ -2981,6 +2990,13 @@ pp_when({record_constraint, RecType0, InferredType0, Fld}) -> InferredType = instantiate(InferredType0), {Pos, WhyRec} = pp_why_record(Fld), case Fld of + {var_args, _Ann, _Fun} -> + {Pos, + io_lib:format("when checking that contract construction of type\n~s\n~s\n" + "matches the expected type\n~s\n", + [pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)] + ) + }; {field, _Ann, _LV, _Id, _E} -> {Pos, io_lib:format("when checking that the record type\n~s\n~s\n" @@ -3051,17 +3067,21 @@ pp_when({arg_name, Id1, Id2, When}) -> {Pos , io_lib:format("when unifying names of named arguments: ~s and ~s\n~s", [pp_expr(Id1), pp_expr(Id2), Ctx]) }; +pp_when({var_args, Ann, Fun}) -> + {pos(Ann) + , io_lib:format("when resolving arguments of variadic function\n~s\n", [pp_expr(" ", Fun)]) + }; pp_when(unknown) -> {pos(0,0), ""}. -spec pp_why_record(why_record()) -> {pos(), iolist()}. -pp_why_record(Fld = {field, _Ann, LV, _Id, _E}) -> - {pos(Fld), - io_lib:format("arising from an assignment of the field ~s (at ~s)", - [pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])}; +pp_why_record({var_args, Ann, Fun}) -> + {pos(Ann), + io_lib:format("arising from resolution of variadic function ~s (at ~s)", + [pp_expr("", Fun), pp_loc(Fun)])}; pp_why_record(Fld = {field, _Ann, LV, _E}) -> {pos(Fld), io_lib:format("arising from an assignment of the field ~s (at ~s)", - [pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])}; + [pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])}; pp_why_record({proj, _Ann, Rec, FldName}) -> {pos(Rec), io_lib:format("arising from the projection of the field ~s (at ~s)", diff --git a/test/contracts/test.aes b/test/contracts/test.aes index 4fd2d73..6431d88 100644 --- a/test/contracts/test.aes +++ b/test/contracts/test.aes @@ -1,9 +1,9 @@ // If you need a quick compiler test — this file is for your playground -contract interface Remote = - entrypoint init : int => void - entrypoint f : int => int +contract Chuj = + type state = bool +// entrypoint init : (int, bool) => void + entrypoint init(x : int, y : bool) = if(x < 0) abort("xD") else true -contract Test = - entrypoint kek(r : Remote) = - Chain.clone(ref=r, 123) - Chain.bytecode_hash(r) \ No newline at end of file +main contract Test = + stateful entrypoint kek() = + Chain.create(value=3, 123, 555) : Chuj -- 2.30.2 From c47169a4a4078f9739a1243bd80b0d5ab6d11e2d Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 26 Apr 2021 14:04:34 +0200 Subject: [PATCH 11/34] CREATE sometimes compiles and sometimes not --- src/aeso_ast_infer_types.erl | 7 ++- src/aeso_ast_to_fcode.erl | 28 +++++++-- src/aeso_compiler.erl | 6 +- src/aeso_fcode_to_fate.erl | 108 +++++++++++++++++++++-------------- test/contracts/test.aes | 25 +++++--- 5 files changed, 113 insertions(+), 61 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 4cdf902..d305875 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -267,15 +267,16 @@ contract_call_type({fun_t, Ann, [], Args, Ret}) -> bind_contract({Contract, Ann, Id, Contents}, Env) when ?IS_CONTRACT_HEAD(Contract) -> Key = name(Id), + io:format("BIND CONTRACT ~p\n", [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 || ArgT <- if is_list(Args) -> Args; true -> [Args] end], RetT}) + {fun_t, AnnF, [], [ArgT || {typed, _, _, ArgT} <- if is_list(Args) -> Args; true -> [Args] end], RetT}) } - || {letfun, AnnF, Entrypoint, _Named, Args, {typed, _, _, RetT}} <- Contents + || {letfun, AnnF, Entrypoint, Args, _Type, {typed, _, _, RetT}} <- Contents ] ++ %% Predefined fields [ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ], @@ -1595,6 +1596,7 @@ infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) -> {fun_t, _, NamedArgsT, var_args, RetT} = FunType0, GasCapMock = {named_arg_t, Ann, {id, Ann, "gas"}, {id, Ann, "int"}, {int, Ann, 0}}, ProtectedMock = {named_arg_t, Ann, {id, Ann, "protected"}, {id, Ann, "bool"}, {bool, Ann, false}}, + check_contract_construction(Env, RetT, Fun, [GasCapMock, ProtectedMock|NamedArgsT], ArgTypes, RetT), {fun_t, Ann, NamedArgsT, ArgTypes, RetT}; {qid, _, ["Chain", "clone"]} -> @@ -1618,7 +1620,6 @@ check_contract_construction(Env, ContractT, Fun, NamedArgsT, ArgTypes, RetT) -> InitT = fresh_uvar(Ann), unify(Env, InitT, {fun_t, Ann, NamedArgsT, ArgTypes, fresh_uvar(Ann)}, {checking_init_args, Ann, ContractT, ArgTypes}), unify(Env, RetT, ContractT, {return_contract, Fun, ContractT}), - io:format("DEREF: ~p\n", [dereference_deep(InitT)]), constrain([ #field_constraint{ record_t = unfold_types_in_type(Env, ContractT), field = {id, Ann, "init"}, diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index d658309..e6b290b 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -55,6 +55,7 @@ | {oracle_pubkey, binary()} | {oracle_query_id, binary()} | {bool, false | true} + | {contract_code, string()} %% for CREATE, by name | {typerep, ftype()}. -type fexpr() :: {lit, flit()} @@ -167,15 +168,24 @@ %% and produces Fate intermediate code. -spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> fcode(). ast_to_fcode(Code, Options) -> - Verbose = lists:member(pp_fcode, Options), init_fresh_names(), - FCode1 = to_fcode(init_env(Options), Code), + {Env1, FCode1} = to_fcode(init_env(Options), Code), + FCode2 = optimize(FCode1, Options), + Env2 = Env1#{ child_con_env := + maps:map( + fun (_, FC) -> optimize(FC, Options) end, + maps:get(child_con_env, Env1) + )}, + clear_fresh_names(), + {Env2, FCode2}. + +optimize(FCode1, Options) -> + Verbose = lists:member(pp_fcode, Options), [io:format("-- Before lambda lifting --\n~s\n\n", [format_fcode(FCode1)]) || Verbose], FCode2 = optimize_fcode(FCode1), [ io:format("-- After optimization --\n~s\n\n", [format_fcode(FCode2)]) || Verbose, FCode2 /= FCode1 ], FCode3 = lambda_lift(FCode2), [ io:format("-- After lambda lifting --\n~s\n\n", [format_fcode(FCode3)]) || Verbose, FCode3 /= FCode2 ], - clear_fresh_names(), FCode3. %% -- Environment ------------------------------------------------------------ @@ -337,7 +347,7 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest]) functions => add_init_function(Env1, Con, StateType, add_event_function(Env1, EventType, Funs)) }, case Contract of - contract_main -> Rest = [], ConFcode; + contract_main -> Rest = [], {Env1, ConFcode}; contract_child -> Env2 = add_child_con(Env1, Name, ConFcode), to_fcode(Env2, Rest) @@ -696,7 +706,7 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) -> end; %% Function calls -expr_to_fcode(Env, _Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, _}}, Args}) -> +expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, _}}, Args}) -> Args1 = get_named_args(NamedArgsT, Args), FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1], case expr_to_fcode(Env, Fun) of @@ -708,6 +718,14 @@ expr_to_fcode(Env, _Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, FInitArgsT = {typerep, {tuple, [type_to_fcode(Env, T) || T <- ArgsT]}}, builtin_to_fcode(state_layout(Env), chain_clone, [{lit, FInitArgsT}|FArgs]) end; + {builtin_u, chain_create, _Ar} -> + case {ArgsT, Type} of + {var_args, _} -> fcode_error({var_args_not_set, FunE}); + {_, {con, _, Contract}} -> + FInitArgsT = {typerep, {tuple, [type_to_fcode(Env, T) || T <- ArgsT]}}, + builtin_to_fcode(state_layout(Env), chain_clone, [{lit, {contract_code, Contract}}, {lit, FInitArgsT}|FArgs]); + {_, _} -> fcode_error({not_a_contract_type, Type}) + end; {builtin_u, B, _Ar} -> builtin_to_fcode(state_layout(Env), B, FArgs); {def_u, F, _Ar} -> {def, F, FArgs}; {remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs}; diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 930c70c..6a3c576 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -138,8 +138,9 @@ from_string1(aevm, ContractString, Options) -> {ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}; from_string1(fate, ContractString, Options) -> #{ fcode := FCode + , fcode_env := #{child_con_env := ChildContracts} , folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options), - FateCode = aeso_fcode_to_fate:compile(FCode, Options), + FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, Options), pp_assembler(fate, FateCode, Options), ByteCode = aeb_fate_code:serialize(FateCode, []), {ok, Version} = version(), @@ -179,8 +180,9 @@ string_to_code(ContractString, Options) -> , type_env => TypeEnv , ast => Ast }; fate -> - Fcode = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, Options), + {Env, Fcode} = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, Options), #{ fcode => Fcode + , fcode_env => Env , unfolded_typed_ast => UnfoldedTypedAst , folded_typed_ast => FoldedTypedAst , type_env => TypeEnv diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index a525dcc..cf25dd7 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -9,7 +9,7 @@ %%%------------------------------------------------------------------- -module(aeso_fcode_to_fate). --export([compile/2, term_to_fate/1]). +-export([compile/2, compile/3, term_to_fate/1, term_to_fate/2]). -ifdef(TEST). -export([optimize_fun/4, to_basic_blocks/1]). @@ -45,7 +45,7 @@ -define(s(N), {store, N}). -define(void, {var, 9999}). --record(env, { contract, vars = [], locals = [], current_function, tailpos = true }). +-record(env, { contract, vars = [], locals = [], current_function, tailpos = true, child_contracts = #{}, options = []}). %% -- Debugging -------------------------------------------------------------- @@ -70,9 +70,11 @@ code_error(Err) -> %% @doc Main entry point. compile(FCode, Options) -> + compile(#{}, FCode, Options). +compile(ChildContracts, FCode, Options) -> #{ contract_name := ContractName, functions := Functions } = FCode, - SFuns = functions_to_scode(ContractName, Functions, Options), + SFuns = functions_to_scode(ChildContracts, ContractName, Functions, Options), SFuns1 = optimize_scode(SFuns, Options), FateCode = to_basic_blocks(SFuns1), ?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]), @@ -85,19 +87,20 @@ make_function_name(event) -> <<"Chain.event">>; make_function_name({entrypoint, Name}) -> Name; make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, ".")). -functions_to_scode(ContractName, Functions, Options) -> +functions_to_scode(ChildContracts, ContractName, Functions, Options) -> FunNames = maps:keys(Functions), maps:from_list( - [ {make_function_name(Name), function_to_scode(ContractName, FunNames, Name, Attrs, Args, Body, Type, Options)} + [ {make_function_name(Name), function_to_scode(ChildContracts, ContractName, FunNames, Name, Attrs, Args, Body, Type, Options)} || {Name, #{args := Args, body := Body, attrs := Attrs, return := Type}} <- maps:to_list(Functions)]). -function_to_scode(ContractName, Functions, Name, Attrs0, Args, Body, ResType, _Options) -> +function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, Options) -> {ArgTypes, ResType1} = typesig_to_scode(Args, ResType), Attrs = Attrs0 -- [stateful], %% Only track private and payable from here. - SCode = to_scode(init_env(ContractName, Functions, Name, Args), Body), + Env = init_env(ChildContracts, ContractName, Functions, Name, Args, Options), + SCode = to_scode(Env, Body), {Attrs, {ArgTypes, ResType1}, SCode}. -define(tvars, '$tvars'). @@ -142,11 +145,13 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts). %% -- Environment functions -- -init_env(ContractName, FunNames, Name, Args) -> +init_env(ChildContracts, ContractName, FunNames, Name, Args, Options) -> #env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ], contract = ContractName, + child_contracts = ChildContracts, locals = FunNames, current_function = Name, + options = Options, tailpos = true }. next_var(#env{ vars = Vars }) -> @@ -169,7 +174,7 @@ lookup_var(#env{vars = Vars}, X) -> %% -- The compiler -- -lit_to_fate(L) -> +lit_to_fate(Env, L) -> case L of {int, N} -> aeb_fate_data:make_integer(N); {string, S} -> aeb_fate_data:make_string(S); @@ -179,63 +184,79 @@ lit_to_fate(L) -> {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} -> + FCode = maps:get(C, Env#env.child_contracts), + SCode = compile(Env#env.child_contracts, FCode, Env#env.options), + ByteCode = aeb_fate_code:serialize(SCode, []), + {ok, Version} = aeso_compiler:version(), + Code = #{byte_code => ByteCode, + compiler_version => Version, + contract_source => "child_contract_src_placeholder", + type_info => [], + fate_code => SCode, + abi_version => aeb_fate_abi:abi_version(), + payable => maps:get(payable, FCode) + }, + Serialized = aeser_contract_code:serialize(Code), + aeb_fate_data:make_contract_bytearray(Serialized); {typerep, T} -> aeb_fate_data:make_typerep(type_to_scode(T)) end. -term_to_fate(E) -> term_to_fate(#{}, E). +term_to_fate(E) -> term_to_fate(#env{}, #{}, E). +term_to_fate(GlobEnv, E) -> term_to_fate(GlobEnv, #{}, E). -term_to_fate(_Env, {lit, L}) -> - lit_to_fate(L); +term_to_fate(GlobEnv, _Env, {lit, L}) -> + lit_to_fate(GlobEnv, L); %% negative literals are parsed as 0 - N -term_to_fate(_Env, {op, '-', [{lit, {int, 0}}, {lit, {int, N}}]}) -> +term_to_fate(_GlobEnv, _Env, {op, '-', [{lit, {int, 0}}, {lit, {int, N}}]}) -> aeb_fate_data:make_integer(-N); -term_to_fate(_Env, nil) -> +term_to_fate(_GlobEnv, _Env, nil) -> aeb_fate_data:make_list([]); -term_to_fate(Env, {op, '::', [Hd, Tl]}) -> +term_to_fate(GlobEnv, Env, {op, '::', [Hd, Tl]}) -> %% The Tl will translate into a list, because FATE lists are just lists - [term_to_fate(Env, Hd) | term_to_fate(Env, Tl)]; -term_to_fate(Env, {tuple, As}) -> - aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(Env, A) || A<-As])); -term_to_fate(Env, {con, Ar, I, As}) -> - FateAs = [ term_to_fate(Env, A) || A <- As ], + [term_to_fate(GlobEnv, Env, Hd) | term_to_fate(GlobEnv, Env, Tl)]; +term_to_fate(GlobEnv, Env, {tuple, As}) -> + aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(GlobEnv, Env, A) || A<-As])); +term_to_fate(GlobEnv, Env, {con, Ar, I, As}) -> + FateAs = [ term_to_fate(GlobEnv, Env, A) || A <- As ], aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs)); -term_to_fate(_Env, {builtin, bits_all, []}) -> +term_to_fate(_GlobEnv, _Env, {builtin, bits_all, []}) -> aeb_fate_data:make_bits(-1); -term_to_fate(_Env, {builtin, bits_none, []}) -> +term_to_fate(_GlobEnv, _Env, {builtin, bits_none, []}) -> aeb_fate_data:make_bits(0); -term_to_fate(_Env, {op, bits_set, [B, I]}) -> - {bits, N} = term_to_fate(B), - J = term_to_fate(I), +term_to_fate(GlobEnv, _Env, {op, bits_set, [B, I]}) -> + {bits, N} = term_to_fate(GlobEnv, B), + J = term_to_fate(GlobEnv, I), {bits, N bor (1 bsl J)}; -term_to_fate(_Env, {op, bits_clear, [B, I]}) -> - {bits, N} = term_to_fate(B), - J = term_to_fate(I), +term_to_fate(GlobEnv, _Env, {op, bits_clear, [B, I]}) -> + {bits, N} = term_to_fate(GlobEnv, B), + J = term_to_fate(GlobEnv, I), {bits, N band bnot (1 bsl J)}; -term_to_fate(Env, {'let', X, E, Body}) -> - Env1 = Env#{ X => term_to_fate(Env, E) }, - term_to_fate(Env1, Body); -term_to_fate(Env, {var, X}) -> +term_to_fate(GlobEnv, Env, {'let', X, E, Body}) -> + Env1 = Env#{ X => term_to_fate(GlobEnv, Env, E) }, + term_to_fate(GlobEnv, Env1, Body); +term_to_fate(_GlobEnv, Env, {var, X}) -> case maps:get(X, Env, undefined) of undefined -> throw(not_a_fate_value); V -> V end; -term_to_fate(_Env, {builtin, map_empty, []}) -> +term_to_fate(_GlobEnv, _Env, {builtin, map_empty, []}) -> aeb_fate_data:make_map(#{}); -term_to_fate(Env, {op, map_set, [M, K, V]}) -> - Map = term_to_fate(Env, M), - Map#{term_to_fate(Env, K) => term_to_fate(Env, V)}; -term_to_fate(_Env, _) -> +term_to_fate(GlobEnv, Env, {op, map_set, [M, K, V]}) -> + Map = term_to_fate(GlobEnv, Env, M), + Map#{term_to_fate(GlobEnv, Env, K) => term_to_fate(GlobEnv, Env, V)}; +term_to_fate(_GlobEnv, _Env, _) -> throw(not_a_fate_value). to_scode(Env, T) -> - try term_to_fate(T) of + try term_to_fate(Env, T) of V -> [push(?i(V))] catch throw:not_a_fate_value -> to_scode1(Env, T) end. -to_scode1(_Env, {lit, L}) -> - [push(?i(lit_to_fate(L)))]; +to_scode1(Env, {lit, L}) -> + [push(?i(lit_to_fate(Env, L)))]; to_scode1(_Env, nil) -> [aeb_fate_ops:nil(?a)]; @@ -564,10 +585,9 @@ builtin_to_scode(Env, chain_clone, [Contract, TypeRep, Value, GasCap, Prot | InitArgs] ); builtin_to_scode(Env, chain_create, - [_GasCap = {con,[0,1],0,[]}, Prot, Value, Contract | InitArgs]) -> - TypeRep = xd, - call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a), - [Contract, TypeRep, Value, Prot | InitArgs] + [ Code, TypeRep | InitArgs]) -> + call_to_scode(Env, aeb_fate_ops:create(?a, ?a, ?a), + [Code, TypeRep | InitArgs] ). diff --git a/test/contracts/test.aes b/test/contracts/test.aes index 6431d88..e22cfb6 100644 --- a/test/contracts/test.aes +++ b/test/contracts/test.aes @@ -1,9 +1,20 @@ // If you need a quick compiler test — this file is for your playground -contract Chuj = - type state = bool -// entrypoint init : (int, bool) => void - entrypoint init(x : int, y : bool) = if(x < 0) abort("xD") else true +contract IntegerAdder = + entrypoint init() = unit + entrypoint addIntegers(x, y) = x + y -main contract Test = - stateful entrypoint kek() = - Chain.create(value=3, 123, 555) : Chuj +contract IntegerAdderHolder = + type state = IntegerAdder + entrypoint init() = Chain.create() : IntegerAdder + entrypoint get() = state + +namespace IntegerAdderFactory = + function new() = + let i = Chain.create() : IntegerAdderHolder + i.get() + +main contract EnterpriseContract = + entrypoint calculateSomething(x) = + let adder = IntegerAdderFactory.new() + adder.addIntegers(x, 2137) + \ No newline at end of file -- 2.30.2 From 614c60f04a1f9ffc6c03ec31d07c2599b95afa71 Mon Sep 17 00:00:00 2001 From: radrow Date: Tue, 27 Apr 2021 13:11:32 +0200 Subject: [PATCH 12/34] Fix some scoping/constraint issues --- src/aeso_ast_infer_types.erl | 58 +++++++++++++++++++----------------- src/aeso_ast_to_fcode.erl | 2 +- src/aeso_fcode_to_fate.erl | 4 +-- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index d305875..4c86a9c 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -267,16 +267,21 @@ contract_call_type({fun_t, Ann, [], Args, Ret}) -> bind_contract({Contract, Ann, Id, Contents}, Env) when ?IS_CONTRACT_HEAD(Contract) -> Key = name(Id), - io:format("BIND CONTRACT ~p\n", [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} <- if is_list(Args) -> Args; true -> [Args] end], RetT}) - } - || {letfun, AnnF, Entrypoint, Args, _Type, {typed, _, _, RetT}} <- Contents + [ begin + AnnF1 = case {Contract, Name} of + {contract_child, "init"} -> [stateful,payable|AnnF]; %% for create/clone + _ -> AnnF + end, + {field_t, AnnF1, Entrypoint, + contract_call_type( + {fun_t, AnnF1, [], [ArgT || {typed, _, _, ArgT} <- if is_list(Args) -> Args; true -> [Args] end], RetT}) + } + end + || {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, {typed, _, _, RetT}} <- Contents ] ++ %% Predefined fields [ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ], @@ -439,6 +444,7 @@ global_env() -> 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"]), @@ -480,14 +486,16 @@ global_env() -> {"difficulty", Int}, {"gas_limit", Int}, {"bytecode_hash",FunC1(bytecode_hash, A, Option(Hash))}, - {"create", FunN([ {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}} - ], var_args, A)}, - {"clone", FunN([ {named_arg_t, Ann, {id, Ann, "gas"}, Int, - {qid, Ann, ["Call","gas_left"]}} - , {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)}, + {"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, + {qid, Ann, ["Call","gas_left"]}} + , {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)}, @@ -751,9 +759,10 @@ infer1(Env, [{Contract, Ann, ConName, Code} | Rest], Acc, Options) when ?IS_CONTRACT_HEAD(Contract) -> %% do type inference on each contract independently. check_scope_name_clash(Env, contract, ConName), - What = case aeso_syntax:get_ann(interface, Ann, false) of - true -> contract_interface; - false -> contract + What = case Contract of + contract_main -> contract; + contract_child -> contract; + contract_interface -> contract_interface end, {Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), What, Code, Options), Contract1 = {Contract, Ann, ConName, Code1}, @@ -769,23 +778,18 @@ infer1(Env, [{pragma, _, _} | Rest], Acc, Options) -> %% Pragmas are checked in check_modifiers infer1(Env, Rest, Acc, Options). -%% Checks if the main contract is somehow defined. -%% Performs some basic sorting to make the dependencies more happy. +%% Asserts that the main contract is somehow defined. identify_main_contract(Contracts) -> - Childs = [C || C = {contract_child, _, _, _} <- Contracts], + Children = [C || C = {contract_child, _, _, _} <- Contracts], Mains = [C || C = {contract_main, _, _, _} <- Contracts], - Interfaces = [C || C = {contract_interface, _, _, _} <- Contracts], - Namespaces = [N || N = {namespace, _, _, _} <- Contracts], case Mains of - [] -> case Childs of + [] -> case Children of [] -> type_error({main_contract_undefined}); [{contract_child, Ann, Con, Body}] -> - Interfaces ++ Namespaces ++ - [C || C = {_, _, Con1, _} <- Childs, Con1 /= Con] ++ - [{contract_main, Ann, Con, Body}]; + (Contracts -- Children) ++ [{contract_main, Ann, Con, Body}]; _ -> type_error({ambiguous_main_contract}) end; - [_] -> Interfaces ++ Namespaces ++ Childs ++ Mains; + [_] -> Contracts; _ -> type_error({multiple_main_contracts}) end. diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index e6b290b..dc03304 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -723,7 +723,7 @@ expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, {var_args, _} -> fcode_error({var_args_not_set, FunE}); {_, {con, _, Contract}} -> FInitArgsT = {typerep, {tuple, [type_to_fcode(Env, T) || T <- ArgsT]}}, - builtin_to_fcode(state_layout(Env), chain_clone, [{lit, {contract_code, Contract}}, {lit, FInitArgsT}|FArgs]); + builtin_to_fcode(state_layout(Env), chain_create, [{lit, {contract_code, Contract}}, {lit, FInitArgsT}|FArgs]); {_, _} -> fcode_error({not_a_contract_type, Type}) end; {builtin_u, B, _Ar} -> builtin_to_fcode(state_layout(Env), B, FArgs); diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index cf25dd7..ba4ade7 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -585,9 +585,9 @@ builtin_to_scode(Env, chain_clone, [Contract, TypeRep, Value, GasCap, Prot | InitArgs] ); builtin_to_scode(Env, chain_create, - [ Code, TypeRep | InitArgs]) -> + [ Code, TypeRep, Value | InitArgs]) -> call_to_scode(Env, aeb_fate_ops:create(?a, ?a, ?a), - [Code, TypeRep | InitArgs] + [Code, TypeRep, Value | InitArgs] ). -- 2.30.2 From c8853b2103de4202ba8b0a48bad72634716bcea5 Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 10 May 2021 15:04:59 +0200 Subject: [PATCH 13/34] works, needs cleanup --- src/aeso_ast_infer_types.erl | 43 ++++++++++++++++++++---------------- src/aeso_ast_to_fcode.erl | 3 ++- src/aeso_fcode_to_fate.erl | 1 + test/aeso_compiler_tests.erl | 3 ++- test/contracts/test.aes | 14 +++++++++--- 5 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 4c86a9c..fcb30a2 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -133,6 +133,7 @@ -define(PRINT_TYPES(Fmt, Args), when_option(pp_types, fun () -> io:format(Fmt, Args) end)). +-define(CONSTRUCTOR_MOCK_NAME, "#__constructor__#"). %% -- Environment manipulation ----------------------------------------------- @@ -271,20 +272,25 @@ bind_contract({Contract, Ann, Id, Contents}, Env) Fields = [ {field_t, AnnF, Entrypoint, contract_call_type(Type)} || {fun_decl, AnnF, Entrypoint, Type} <- Contents ] ++ - [ begin - AnnF1 = case {Contract, Name} of - {contract_child, "init"} -> [stateful,payable|AnnF]; %% for create/clone - _ -> AnnF - end, - {field_t, AnnF1, Entrypoint, - contract_call_type( - {fun_t, AnnF1, [], [ArgT || {typed, _, _, ArgT} <- if is_list(Args) -> Args; true -> [Args] end], RetT}) - } - end - || {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, {typed, _, _, RetT}} <- Contents + [ {field_t, AnnF, Entrypoint, + contract_call_type( + {fun_t, AnnF, [], [ArgT || {typed, _, _, ArgT} <- Args], RetT}) + } + || {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, {typed, _, _, RetT}} <- Contents, + Name =/= "init" ] ++ %% Predefined fields - [ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ], + [ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ] ++ + [ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME}, + contract_call_type( + case [ {fun_t, [payable|Sys], [], [ArgT || {typed, _, _, ArgT} <- Args], {id, Sys, "void"}} + || {letfun, _, {id, _, "init"}, Args, _, _} <- Contents] of + [] -> {fun_t, [payable|Sys], [], [], {id, Sys, "void"}}; + [T] -> T + end + ) + } + ], FieldInfo = [ {Entrypoint, #field_info{ ann = FieldAnn, kind = contract, field_t = Type, @@ -1626,7 +1632,7 @@ check_contract_construction(Env, ContractT, Fun, NamedArgsT, ArgTypes, RetT) -> unify(Env, RetT, ContractT, {return_contract, Fun, ContractT}), constrain([ #field_constraint{ record_t = unfold_types_in_type(Env, ContractT), - field = {id, Ann, "init"}, + field = {id, Ann, ?CONSTRUCTOR_MOCK_NAME}, field_t = InitT, kind = project, context = {var_args, Ann, Fun} } @@ -2418,12 +2424,11 @@ unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, When) -> error({unify_varargs, When}); unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) when length(Args1) == length(Args2) -> - Named1_ = if is_list(Named1) -> lists:keysort(3, Named1); - true -> Named1 - end, - Named2_ = if is_list(Named2) -> lists:keysort(3, Named2); - true -> Named2 - end, + {Named1_, Named2_} = + if is_list(Named1) andalso is_list(Named2) -> + {lists:keysort(3, Named1), lists:keysort(3, Named2)}; + true -> {Named1, Named2} + end, unify(Env, Named1_, Named2_, When) andalso unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When); unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When) diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index dc03304..11aa761 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -707,6 +707,7 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) -> %% Function calls expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, _}}, Args}) -> + io:format("Named args: ~p\n", [[N||{named_arg_t, _, {id, _, N}, _, _} <- NamedArgsT]]), Args1 = get_named_args(NamedArgsT, Args), FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1], case expr_to_fcode(Env, Fun) of @@ -728,7 +729,7 @@ expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, end; {builtin_u, B, _Ar} -> builtin_to_fcode(state_layout(Env), B, FArgs); {def_u, F, _Ar} -> {def, F, FArgs}; - {remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs}; + {remote_u, RArgsT, RRetT, Ct, RFun} -> {remote, RArgsT, RRetT, Ct, RFun, FArgs}; FFun -> %% FFun is a closure, with first component the function name and %% second component the environment diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index ba4ade7..ba548f5 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -328,6 +328,7 @@ to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value, Protected | Args]}) - {ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT), ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})), RetType = ?i(aeb_fate_data:make_typerep(RetType0)), + io:format("GAS: ~p, VALUE: ~p, PROT: ~p\n", [Gas, Value, Protected]), case Protected of {lit, {bool, false}} -> case Gas of diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 19e8630..e7bbd97 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -26,13 +26,14 @@ run_test(Test) -> simple_compile_test_() -> [ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend", fun() -> - case compile(Backend, ContractName) of + case compile(Backend, ContractName, [pp_assembler]) of #{byte_code := ByteCode, contract_source := _, type_info := _} when Backend == aevm -> ?assertMatch(Code when is_binary(Code), ByteCode); #{fate_code := Code} when Backend == fate -> Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)), + error(xd), ?assertMatch({X, X}, {Code1, Code}); ErrBin -> io:format("\n~s", [ErrBin]), diff --git a/test/contracts/test.aes b/test/contracts/test.aes index e22cfb6..016860d 100644 --- a/test/contracts/test.aes +++ b/test/contracts/test.aes @@ -1,6 +1,14 @@ -// If you need a quick compiler test — this file is for your playground +contract T = + entrypoint f(x, y) = 3 +main contract IntegerAdderFactory = + stateful payable entrypoint calculateSomething(x, t) = +// let t = Chain.create(value = 5555) : T + t.f(protected = true, gas = 999999999, value = 777777, 111, 222) +// t.f() + +/* contract IntegerAdder = - entrypoint init() = unit + entrypoint init() = () entrypoint addIntegers(x, y) = x + y contract IntegerAdderHolder = @@ -17,4 +25,4 @@ main contract EnterpriseContract = entrypoint calculateSomething(x) = let adder = IntegerAdderFactory.new() adder.addIntegers(x, 2137) - \ No newline at end of file +*/ \ No newline at end of file -- 2.30.2 From 5c43be22b9ebda27bd9ce0b910d7ec86c55e9523 Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 10 May 2021 16:17:53 +0200 Subject: [PATCH 14/34] cleanup --- src/aeso_ast_infer_types.erl | 8 ++++++-- src/aeso_ast_to_fcode.erl | 1 - src/aeso_fcode_to_fate.erl | 1 - test/aeso_compiler_tests.erl | 4 +--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index fcb30a2..88e7e43 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -283,9 +283,9 @@ bind_contract({Contract, Ann, Id, Contents}, Env) [ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ] ++ [ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME}, contract_call_type( - case [ {fun_t, [payable|Sys], [], [ArgT || {typed, _, _, ArgT} <- Args], {id, Sys, "void"}} + case [ {fun_t, [stateful,payable|Sys], [], [ArgT || {typed, _, _, ArgT} <- Args], {id, Sys, "void"}} || {letfun, _, {id, _, "init"}, Args, _, _} <- Contents] of - [] -> {fun_t, [payable|Sys], [], [], {id, Sys, "void"}}; + [] -> {fun_t, [stateful,payable|Sys], [], [], {id, Sys, "void"}}; [T] -> T end ) @@ -3092,6 +3092,10 @@ pp_why_record(Fld = {field, _Ann, LV, _E}) -> {pos(Fld), io_lib:format("arising from an assignment of the field ~s (at ~s)", [pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])}; +pp_why_record(Fld = {field, _Ann, LV, _Alias, _E}) -> + {pos(Fld), + io_lib:format("arising from an assignment of the field ~s (at ~s)", + [pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])}; pp_why_record({proj, _Ann, Rec, FldName}) -> {pos(Rec), io_lib:format("arising from the projection of the field ~s (at ~s)", diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 11aa761..54db9e0 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -707,7 +707,6 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) -> %% Function calls expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, _}}, Args}) -> - io:format("Named args: ~p\n", [[N||{named_arg_t, _, {id, _, N}, _, _} <- NamedArgsT]]), Args1 = get_named_args(NamedArgsT, Args), FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1], case expr_to_fcode(Env, Fun) of diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index ba548f5..ba4ade7 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -328,7 +328,6 @@ to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value, Protected | Args]}) - {ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT), ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})), RetType = ?i(aeb_fate_data:make_typerep(RetType0)), - io:format("GAS: ~p, VALUE: ~p, PROT: ~p\n", [Gas, Value, Protected]), case Protected of {lit, {bool, false}} -> case Gas of diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index e7bbd97..feb75d2 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -26,14 +26,13 @@ run_test(Test) -> simple_compile_test_() -> [ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend", fun() -> - case compile(Backend, ContractName, [pp_assembler]) of + case compile(Backend, ContractName) of #{byte_code := ByteCode, contract_source := _, type_info := _} when Backend == aevm -> ?assertMatch(Code when is_binary(Code), ByteCode); #{fate_code := Code} when Backend == fate -> Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)), - error(xd), ?assertMatch({X, X}, {Code1, Code}); ErrBin -> io:format("\n~s", [ErrBin]), @@ -208,7 +207,6 @@ debug_mode_contracts() -> -define(TYPE_ERROR(Name, Errs), ?ERROR("Type", Name, Errs)). -define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)). -failing_contracts() -> []; % FIXME remove failing_contracts() -> {ok, V} = aeso_compiler:numeric_version(), Version = list_to_binary(string:join([integer_to_list(N) || N <- V], ".")), -- 2.30.2 From b1b2dc849ab975fe9e3202c3024bcad8dab76b5c Mon Sep 17 00:00:00 2001 From: radrow Date: Tue, 11 May 2021 11:39:33 +0200 Subject: [PATCH 15/34] Fix some tests. Remove optimization of singleton tuples --- priv/stdlib/List.aes | 9 ++++++--- src/aeso_ast_infer_types.erl | 9 ++++++--- src/aeso_ast_to_fcode.erl | 4 ++-- src/aeso_fcode_to_fate.erl | 4 ---- test/aeso_compiler_tests.erl | 6 +++--- test/contracts/clone.aes | 4 +++- test/contracts/clone_simple.aes | 2 +- test/contracts/test.aes | 36 ++++++++++----------------------- 8 files changed, 32 insertions(+), 42 deletions(-) diff --git a/priv/stdlib/List.aes b/priv/stdlib/List.aes index faf853c..9493037 100644 --- a/priv/stdlib/List.aes +++ b/priv/stdlib/List.aes @@ -208,10 +208,13 @@ namespace List = [] => false h::t => if(p(h)) true else any(p, t) - function sum(l : list(int)) : int = foldl ((a, b) => a + b, 0, l) - - function product(l : list(int)) : int = foldl((a, b) => a * b, 1, l) + function sum(l : list(int)) : int = switch(l) + [] => 0 + h::t => h + sum(t) + function product(l : list(int)) : int = switch(l) + [] => 1 + h::t => h * sum(t) /** Zips two list by applying bimapping function on respective elements. * Drops the tail of the longer list. diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 88e7e43..f673607 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -283,10 +283,13 @@ bind_contract({Contract, Ann, Id, Contents}, Env) [ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ] ++ [ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME}, contract_call_type( - case [ {fun_t, [stateful,payable|Sys], [], [ArgT || {typed, _, _, ArgT} <- Args], {id, Sys, "void"}} - || {letfun, _, {id, _, "init"}, Args, _, _} <- Contents] of + case [ [ArgT || {typed, _, _, ArgT} <- Args] + || {letfun, _, {id, _, "init"}, Args, _, _} <- Contents] + ++ [ Args || {fun_decl, _, {id, _, "init"}, {fun_t, _, _, Args, _}} <- Contents] + ++ [ Args || {fun_decl, _, {id, _, "init"}, {type_sig, _, _, _, Args, _}} <- Contents] + of [] -> {fun_t, [stateful,payable|Sys], [], [], {id, Sys, "void"}}; - [T] -> T + [Args] -> {fun_t, [stateful,payable|Sys], [], Args, {id, Sys, "void"}} end ) } diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 54db9e0..9d596d4 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -715,14 +715,14 @@ expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, case ArgsT of var_args -> fcode_error({var_args_not_set, FunE}); _ -> - FInitArgsT = {typerep, {tuple, [type_to_fcode(Env, T) || T <- ArgsT]}}, + FInitArgsT = aeb_fate_data:make_typerep({tuple, [type_to_fcode(Env, T) || T <- ArgsT]}), builtin_to_fcode(state_layout(Env), chain_clone, [{lit, FInitArgsT}|FArgs]) end; {builtin_u, chain_create, _Ar} -> case {ArgsT, Type} of {var_args, _} -> fcode_error({var_args_not_set, FunE}); {_, {con, _, Contract}} -> - FInitArgsT = {typerep, {tuple, [type_to_fcode(Env, T) || T <- ArgsT]}}, + FInitArgsT = aeb_fate_data:make_typerep({tuple, [type_to_fcode(Env, T) || T <- ArgsT]}), builtin_to_fcode(state_layout(Env), chain_create, [{lit, {contract_code, Contract}}, {lit, FInitArgsT}|FArgs]); {_, _} -> fcode_error({not_a_contract_type, Type}) end; diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index ba4ade7..f27405a 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -125,7 +125,6 @@ type_to_scode(bits) -> bits; type_to_scode(any) -> any; type_to_scode({variant, Cons}) -> {variant, [{tuple, types_to_scode(Con)} || Con <- Cons]}; type_to_scode({list, Type}) -> {list, type_to_scode(Type)}; -type_to_scode({tuple, [Type]}) -> type_to_scode(Type); type_to_scode({tuple, Types}) -> {tuple, types_to_scode(Types)}; type_to_scode({map, Key, Val}) -> {map, type_to_scode(Key), type_to_scode(Val)}; type_to_scode({function, _Args, _Res}) -> {tuple, [string, any]}; @@ -590,9 +589,6 @@ builtin_to_scode(Env, chain_create, [Code, TypeRep, Value | InitArgs] ). - - - %% -- Operators -- op_to_scode('+') -> aeb_fate_ops:add(?a, ?a, ?a); diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index feb75d2..2691b03 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -127,9 +127,9 @@ compile(Backend, Name, Options) -> %% compilable_contracts() -> [ContractName]. %% The currently compilable contracts. -compilable_contracts() -> ["test"]; % FIXME remove compilable_contracts() -> - ["complex_types", + ["test", + "complex_types", "counter", "dutch_auction", "environment", @@ -182,7 +182,7 @@ compilable_contracts() -> "protected_call", "hermetization_turnoff", "multiple_contracts", - "clone", "clone_simple" + "clone", "clone_simple", "create" ]. not_compilable_on(fate) -> []; diff --git a/test/contracts/clone.aes b/test/contracts/clone.aes index d66408d..0b3b04a 100644 --- a/test/contracts/clone.aes +++ b/test/contracts/clone.aes @@ -5,13 +5,15 @@ contract interface HigherOrderState = stateful entrypoint inc : int => unit contract interface LowerDisorderAnarchy = - entrypoint init : int => void + entrypoint init : (int) => void main contract C = + // both `s` and `l` should be of type `HigherOrderState` in this test stateful entrypoint run_clone(s : HigherOrderState, l : LowerDisorderAnarchy) : HigherOrderState = let s1 = Chain.clone(ref=s) let Some(s2) = Chain.clone(ref=s, protected=true) + let None = Chain.clone(ref=s, protected=true, gas=1) let None = Chain.clone(ref=l, protected=true, 123) let s3 = Chain.clone(ref=s1) require(s1.apply(2137) == 2137, "APPLY_S1_0") diff --git a/test/contracts/clone_simple.aes b/test/contracts/clone_simple.aes index 72333e3..f51fa64 100644 --- a/test/contracts/clone_simple.aes +++ b/test/contracts/clone_simple.aes @@ -2,6 +2,6 @@ contract interface I = entrypoint init : () => void contract C = - entrypoint f(i : I) = + stateful entrypoint f(i : I) = let Some(c1) = Chain.clone(ref=i, protected = true) 2 \ No newline at end of file diff --git a/test/contracts/test.aes b/test/contracts/test.aes index 016860d..2a0aab7 100644 --- a/test/contracts/test.aes +++ b/test/contracts/test.aes @@ -1,28 +1,14 @@ -contract T = - entrypoint f(x, y) = 3 -main contract IntegerAdderFactory = - stateful payable entrypoint calculateSomething(x, t) = -// let t = Chain.create(value = 5555) : T - t.f(protected = true, gas = 999999999, value = 777777, 111, 222) -// t.f() +include "List.aes" -/* -contract IntegerAdder = - entrypoint init() = () - entrypoint addIntegers(x, y) = x + y - -contract IntegerAdderHolder = - type state = IntegerAdder - entrypoint init() = Chain.create() : IntegerAdder +contract IntegerHolder = + type state = int + entrypoint init(x) = x entrypoint get() = state -namespace IntegerAdderFactory = - function new() = - let i = Chain.create() : IntegerAdderHolder - i.get() - -main contract EnterpriseContract = - entrypoint calculateSomething(x) = - let adder = IntegerAdderFactory.new() - adder.addIntegers(x, 2137) -*/ \ No newline at end of file +main contract IntegerCollection = + record state = {template: IntegerHolder, payload: list(IntegerHolder)} + stateful entrypoint init() = {template = Chain.create(0), payload = []} + stateful entrypoint add(x) = + put(state{payload @ p = Chain.clone(ref=state.template, x) :: p}) + x + entrypoint sum() = List.sum(List.map((h) => h.get(), state.payload)) \ No newline at end of file -- 2.30.2 From 9b1b36f4c1a4cc8d7c1cc92cbf4e96a86c6836dd Mon Sep 17 00:00:00 2001 From: radrow Date: Tue, 11 May 2021 14:03:20 +0200 Subject: [PATCH 16/34] Fix default argument for clone --- src/aeso_ast_infer_types.erl | 8 +++++++- src/aeso_fcode_to_fate.erl | 15 ++++++++++++--- test/aeso_compiler_tests.erl | 4 ++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index f673607..f0fee08 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -500,7 +500,13 @@ global_env() -> ], var_args, A))}, {"clone", Stateful( FunN([ {named_arg_t, Ann, {id, Ann, "gas"}, Int, - {qid, Ann, ["Call","gas_left"]}} + {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} diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index f27405a..a296001 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -580,9 +580,18 @@ builtin_to_scode(Env, chain_bytecode_hash, [_Addr] = Args) -> call_to_scode(Env, aeb_fate_ops:bytecode_hash(?a, ?a), Args); builtin_to_scode(Env, chain_clone, [TypeRep, GasCap, Value, Prot, Contract | InitArgs]) -> - call_to_scode(Env, aeb_fate_ops:clone_g(?a, ?a, ?a, ?a, ?a), - [Contract, TypeRep, Value, GasCap, Prot | InitArgs] - ); + case GasCap of + {builtin, call_gas_left, _} -> + call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a), + [Contract, TypeRep, Value, Prot | InitArgs] + ); + _ -> + io:format("\n\n************* GAS CAP: ~p\n\n", [GasCap]), + call_to_scode(Env, aeb_fate_ops:clone_g(?a, ?a, ?a, ?a, ?a), + [Contract, TypeRep, Value, GasCap, Prot | InitArgs] + ) + end; + builtin_to_scode(Env, chain_create, [ Code, TypeRep, Value | InitArgs]) -> call_to_scode(Env, aeb_fate_ops:create(?a, ?a, ?a), diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 2691b03..d51be87 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -26,14 +26,14 @@ run_test(Test) -> simple_compile_test_() -> [ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend", fun() -> - case compile(Backend, ContractName) of + case compile(Backend, ContractName, [pp_assembler]) of #{byte_code := ByteCode, contract_source := _, type_info := _} when Backend == aevm -> ?assertMatch(Code when is_binary(Code), ByteCode); #{fate_code := Code} when Backend == fate -> Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)), - ?assertMatch({X, X}, {Code1, Code}); + ?assertMatch({X, X}, {Code1, Code}), error(xd); ErrBin -> io:format("\n~s", [ErrBin]), error(ErrBin) -- 2.30.2 From a77261c1d6667dd3d57320618f3f9788c58e9259 Mon Sep 17 00:00:00 2001 From: radrow Date: Tue, 11 May 2021 14:19:59 +0200 Subject: [PATCH 17/34] Cleanup --- src/aeso_ast_to_fcode.erl | 5 +++-- test/aeso_compiler_tests.erl | 4 ++-- test/contracts/create.aes | 28 ++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 test/contracts/create.aes diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 9d596d4..c51cce5 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -327,12 +327,12 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest]) when ?IS_CONTRACT_HEAD(Contract) -> case Contract =:= contract_interface of false -> - #{ builtins := Builtins } = Env, ConEnv = Env#{ context => {main_contract, Name}, builtins => Builtins#{[Name, "state"] => {get_state, none}, [Name, "put"] => {set_state, 1}, [Name, "Chain", "event"] => {chain_event, 1}} }, + #{ functions := PrevFuns } = ConEnv, #{ functions := Funs } = Env1 = decls_to_fcode(ConEnv, Decls), StateType = lookup_type(Env1, [Name, "state"], [], {tuple, []}), @@ -350,7 +350,8 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest]) contract_main -> Rest = [], {Env1, ConFcode}; contract_child -> Env2 = add_child_con(Env1, Name, ConFcode), - to_fcode(Env2, Rest) + Env3 = Env2#{ functions := PrevFuns }, + to_fcode(Env3, Rest) end; true -> Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls), diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index d51be87..2691b03 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -26,14 +26,14 @@ run_test(Test) -> simple_compile_test_() -> [ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend", fun() -> - case compile(Backend, ContractName, [pp_assembler]) of + case compile(Backend, ContractName) of #{byte_code := ByteCode, contract_source := _, type_info := _} when Backend == aevm -> ?assertMatch(Code when is_binary(Code), ByteCode); #{fate_code := Code} when Backend == fate -> Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)), - ?assertMatch({X, X}, {Code1, Code}), error(xd); + ?assertMatch({X, X}, {Code1, Code}); ErrBin -> io:format("\n~s", [ErrBin]), error(ErrBin) diff --git a/test/contracts/create.aes b/test/contracts/create.aes new file mode 100644 index 0000000..5886b20 --- /dev/null +++ b/test/contracts/create.aes @@ -0,0 +1,28 @@ +contract IntegerAdder = + entrypoint init() = () + entrypoint addIntegers(x, y) = x + y + +contract IntegerAdderHolder = + type state = IntegerAdder + stateful entrypoint init() = Chain.create() : IntegerAdder + entrypoint get() = state + +contract IntegerAdderFactory = + entrypoint init() = () + stateful entrypoint new() = + let i = Chain.create() : IntegerAdderHolder + i.get() + +payable contract ValueAdder = + entrypoint init() = () + stateful entrypoint addValue(x) = + let integerAdderFactory = Chain.create() + let adder = integerAdderFactory.new() + adder.addIntegers(x, Contract.balance) + +main contract EnterpriseContract = + entrypoint init() = () + stateful payable entrypoint increaseByThree(x) = + require(Call.value >= 3, "Price for addition = 3AEtto, insufficient funds") + let threeAdder = Chain.create(value = 3) + threeAdder.addValue(x) -- 2.30.2 From 821fe63d036bba64a54355d21a2ceeeaedcbe49b Mon Sep 17 00:00:00 2001 From: radrow Date: Tue, 11 May 2021 14:28:31 +0200 Subject: [PATCH 18/34] CHANGELOG --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 188389f..1907bdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Child contracts +- `Chain.clone` +- `Chain.create` +- `Chain.bytecode_hash` +- Minor support for variadic functions ### Changed +- Contract interfaces must be now invocated by `contract interface` keywords +- `main` keyword to indicate the main contract in case there are child contracts around +- `List.sum` and `List.product` no longer use `List.foldl` ### Removed +- Singleton tuples are no longer unwrapped (this was causing problems with arity 1 init functions) ## [5.0.0] 2021-04-30 ### Added -- 2.30.2 From 7898d2a17d158f33d115b4581c169a836caa5db1 Mon Sep 17 00:00:00 2001 From: radrow Date: Tue, 11 May 2021 14:30:13 +0200 Subject: [PATCH 19/34] Mention void type --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1907bdc..da38577 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Chain.create` - `Chain.bytecode_hash` - Minor support for variadic functions +- `void` type that represents an empty type ### Changed - Contract interfaces must be now invocated by `contract interface` keywords - `main` keyword to indicate the main contract in case there are child contracts around -- 2.30.2 From 6789b739a5af93fce17504c4c8ebceaa07a124f8 Mon Sep 17 00:00:00 2001 From: radrow Date: Wed, 12 May 2021 20:09:52 +0200 Subject: [PATCH 20/34] Address review, fix some dialyzer errors --- src/aeso_ast_infer_types.erl | 77 +++++++++++++++++++++--------------- src/aeso_ast_to_fcode.erl | 39 ++++++++++-------- src/aeso_code_errors.erl | 2 +- src/aeso_compiler.erl | 2 +- src/aeso_fcode_to_fate.erl | 14 +++---- src/aeso_syntax.erl | 2 +- test/aeso_aci_tests.erl | 25 +++++++----- test/aeso_compiler_tests.erl | 36 +++++++++++------ test/contracts/clone.aes | 2 +- test/contracts/test.aes | 12 +++--- 10 files changed, 123 insertions(+), 88 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index f0fee08..12aeb71 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -76,7 +76,9 @@ -record(is_contract_constraint, { contract_t :: utype(), context :: {contract_literal, aeso_syntax:expr()} | - {address_to_contract, aeso_syntax:ann()} + {address_to_contract, aeso_syntax:ann()} | + {bytecode_hash, aeso_syntax:ann()} | + {var_args, aeso_syntax:ann(), aeso_syntax:expr()} }). -type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}. @@ -99,7 +101,7 @@ -type qname() :: [string()]. -type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [type()], type()}. --type type_constraints() :: none | bytes_concat | bytes_split | address_to_contract. +-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()}. @@ -284,9 +286,14 @@ bind_contract({Contract, Ann, Id, Contents}, Env) [ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME}, contract_call_type( case [ [ArgT || {typed, _, _, ArgT} <- Args] - || {letfun, _, {id, _, "init"}, Args, _, _} <- Contents] - ++ [ Args || {fun_decl, _, {id, _, "init"}, {fun_t, _, _, Args, _}} <- Contents] - ++ [ Args || {fun_decl, _, {id, _, "init"}, {type_sig, _, _, _, Args, _}} <- Contents] + || {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"}} @@ -804,7 +811,7 @@ identify_main_contract(Contracts) -> (Contracts -- Children) ++ [{contract_main, Ann, Con, Body}]; _ -> type_error({ambiguous_main_contract}) end; - [_] -> Contracts; + [_] -> (Contracts -- Mains) ++ Mains; %% Move to the end _ -> type_error({multiple_main_contracts}) end. @@ -1615,15 +1622,20 @@ infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) -> {fun_t, _, NamedArgsT, var_args, RetT} = FunType0, GasCapMock = {named_arg_t, Ann, {id, Ann, "gas"}, {id, Ann, "int"}, {int, Ann, 0}}, ProtectedMock = {named_arg_t, Ann, {id, Ann, "protected"}, {id, Ann, "bool"}, {bool, Ann, false}}, - - check_contract_construction(Env, RetT, Fun, [GasCapMock, ProtectedMock|NamedArgsT], ArgTypes, RetT), + NamedArgsT1 = case NamedArgsT of + [Value|Rest] -> [GasCapMock, Value, ProtectedMock|Rest]; + % generally type error, but will be caught + _ -> [GasCapMock, ProtectedMock|NamedArgsT] + end, + check_contract_construction(Env, RetT, Fun, NamedArgsT1, ArgTypes, RetT), {fun_t, Ann, NamedArgsT, ArgTypes, RetT}; {qid, _, ["Chain", "clone"]} -> {fun_t, _, NamedArgsT, var_args, RetT} = FunType0, ContractT = case [ContractT || {named_arg, _, {id, _, "ref"}, {typed, _, _, ContractT}} <- NamedArgs] of [C] -> C; - _ -> type_error({clone_no_contract, Ann}) + _ -> type_error({clone_no_contract, Ann}), + fresh_uvar(Ann) end, NamedArgsTNoRef = lists:filter(fun({named_arg_t, _, {id, _, "ref"}, _, _}) -> false; (_) -> true end, NamedArgsT), @@ -1634,20 +1646,23 @@ infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) -> end, {typed, Ann, Fun, FunType}. +-spec check_contract_construction(env(), utype(), utype(), named_args_t(), [utype()], utype()) -> ok. check_contract_construction(Env, ContractT, Fun, NamedArgsT, ArgTypes, RetT) -> Ann = aeso_syntax:get_ann(Fun), InitT = fresh_uvar(Ann), unify(Env, InitT, {fun_t, Ann, NamedArgsT, ArgTypes, fresh_uvar(Ann)}, {checking_init_args, Ann, ContractT, ArgTypes}), unify(Env, RetT, ContractT, {return_contract, Fun, ContractT}), - constrain([ #field_constraint{ - record_t = unfold_types_in_type(Env, ContractT), - field = {id, Ann, ?CONSTRUCTOR_MOCK_NAME}, - field_t = InitT, - kind = project, - context = {var_args, Ann, Fun} } - , #is_contract_constraint{ contract_t = ContractT, - context = {var_args, Ann, Fun} } - ]). + constrain( + [ #field_constraint{ + record_t = unfold_types_in_type(Env, ContractT), + field = {id, Ann, ?CONSTRUCTOR_MOCK_NAME}, + field_t = InitT, + kind = project, + context = {var_args, Ann, Fun} } + , #is_contract_constraint{ contract_t = ContractT, + context = {var_args, Ann, Fun} } + ]), + ok. split_args(Args0) -> NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ], @@ -2430,15 +2445,10 @@ unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, unify1(_Env, {fun_t, _, _, _, _}, {fun_t, _, _, var_args, _}, When) -> type_error({unify_varargs, When}); unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, When) -> - error({unify_varargs, When}); + type_error({unify_varargs, When}); unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) when length(Args1) == length(Args2) -> - {Named1_, Named2_} = - if is_list(Named1) andalso is_list(Named2) -> - {lists:keysort(3, Named1), lists:keysort(3, Named2)}; - true -> {Named1, Named2} - end, - unify(Env, Named1_, Named2_, When) andalso + unify(Env, Named1, Named2, When) andalso unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When); unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When) when length(Args1) == length(Args2), Tag == id orelse Tag == qid -> @@ -2934,18 +2944,21 @@ mk_error({conflicting_updates_for_field, Upd, Key}) -> Msg = io_lib:format("Conflicting updates for field '~s'\n", [Key]), mk_t_err(pos(Upd), Msg); mk_error({ambiguous_main_contract}) -> - Msg = "Could not deduce the main contract. You can point it manually with `main` keyword.", + Msg = "Could not deduce the main contract. You can point it out manually with the `main` keyword.", mk_t_err(pos(0, 0), Msg); mk_error({main_contract_undefined}) -> - Msg = "No contract defined.", + Msg = "No contract defined.\n", mk_t_err(pos(0, 0), Msg); mk_error({multiple_main_contracts}) -> - Msg = "Up to one main contract can be defined.", + Msg = "Only one main contract can be defined.\n", mk_t_err(pos(0, 0), Msg); mk_error({unify_varargs, When}) -> - Msg = io_lib:format("Cannot unify variable argument list.\n"), + Msg = "Cannot unify variable argument list.\n", {Pos, Ctxt} = pp_when(When), mk_t_err(Pos, Msg, Ctxt); +mk_error({clone_no_contract, Ann}) -> + Msg = "Chain.clone requires `ref` named argument of contract type.\n", + mk_t_err(pos(Ann), Msg); mk_error(Err) -> Msg = io_lib:format("Unknown error: ~p\n", [Err]), mk_t_err(pos(0, 0), Msg). @@ -3096,15 +3109,15 @@ pp_when(unknown) -> {pos(0,0), ""}. pp_why_record({var_args, Ann, Fun}) -> {pos(Ann), io_lib:format("arising from resolution of variadic function ~s (at ~s)", - [pp_expr("", Fun), pp_loc(Fun)])}; + [pp_expr(Fun), pp_loc(Fun)])}; pp_why_record(Fld = {field, _Ann, LV, _E}) -> {pos(Fld), io_lib:format("arising from an assignment of the field ~s (at ~s)", - [pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])}; + [pp_expr({lvalue, [], LV}), pp_loc(Fld)])}; pp_why_record(Fld = {field, _Ann, LV, _Alias, _E}) -> {pos(Fld), io_lib:format("arising from an assignment of the field ~s (at ~s)", - [pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])}; + [pp_expr({lvalue, [], LV}), pp_loc(Fld)])}; pp_why_record({proj, _Ann, Rec, FldName}) -> {pos(Rec), io_lib:format("arising from the projection of the field ~s (at ~s)", diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index c51cce5..8199297 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -142,7 +142,7 @@ -type child_con_env() :: #{sophia_name() => fcode()}. -type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none | variable} }. --type context() :: {main_contract, string()} +-type context() :: {contract_def, string()} | {namespace, string()} | {abstract_contract, string()}. @@ -158,7 +158,8 @@ state_layout => state_layout(), context => context(), vars => [var_name()], - functions := #{ fun_name() => fun_def() } }. + functions := #{ fun_name() => fun_def() } + }. -define(HASH_BYTES, 32). @@ -166,7 +167,7 @@ %% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2 %% and produces Fate intermediate code. --spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> fcode(). +-spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> {env(), fcode()}. ast_to_fcode(Code, Options) -> init_fresh_names(), {Env1, FCode1} = to_fcode(init_env(Options), Code), @@ -232,7 +233,8 @@ init_env(Options) -> ["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities } }, options => Options, - functions => #{} }. + functions => #{} + }. -spec builtins() -> builtins(). builtins() -> @@ -322,13 +324,13 @@ get_option(Opt, Env, Default) -> %% -- Compilation ------------------------------------------------------------ --spec to_fcode(env(), aeso_syntax:ast()) -> fcode(). +-spec to_fcode(env(), aeso_syntax:ast()) -> {env(), fcode()}. to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest]) when ?IS_CONTRACT_HEAD(Contract) -> case Contract =:= contract_interface of false -> #{ builtins := Builtins } = Env, - ConEnv = Env#{ context => {main_contract, Name}, + ConEnv = Env#{ context => {contract_def, Name}, builtins => Builtins#{[Name, "state"] => {get_state, none}, [Name, "put"] => {set_state, 1}, [Name, "Chain", "event"] => {chain_event, 1}} }, @@ -357,8 +359,8 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest]) Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls), to_fcode(Env1, Rest) end; -to_fcode(_Env, [NotMain = {NotMainHead, _ ,_ , _}]) when NotMainHead =/= main_contract -> - fcode_error({last_declaration_must_be_main_contract, NotMain}); +to_fcode(_Env, [NotMain = {NotMainHead, _ ,_ , _}]) when NotMainHead =/= contract_def -> + fcode_error({last_declaration_must_be_contract_def, NotMain}); to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) -> Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls), to_fcode(Env1, Code). @@ -372,7 +374,7 @@ decls_to_fcode(Env, Decls) -> end, Env1, Decls). -spec decl_to_fcode(env(), aeso_syntax:decl()) -> env(). -decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, _, Id, _}) -> +decl_to_fcode(Env = #{context := {contract_def, _}}, {fun_decl, _, Id, _}) -> case is_no_code(Env) of false -> fcode_error({missing_definition, Id}); true -> Env @@ -435,7 +437,7 @@ typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) -> Env3 = compute_state_layout(Env2, Name, FDef), bind_type(Env3, Q, FDef). -compute_state_layout(Env = #{ context := {main_contract, _} }, "state", Type) -> +compute_state_layout(Env = #{ context := {contract_def, _} }, "state", Type) -> NoLayout = get_option(no_flatten_state, Env), Layout = case Type([]) of @@ -461,7 +463,7 @@ compute_state_layout(R, [H | T]) -> compute_state_layout(R, _) -> {R + 1, {reg, R}}. -check_state_and_event_types(#{ context := {main_contract, _} }, Id, [_ | _]) -> +check_state_and_event_types(#{ context := {contract_def, _} }, Id, [_ | _]) -> case Id of {id, _, "state"} -> fcode_error({parameterized_state, Id}); {id, _, "event"} -> fcode_error({parameterized_event, Id}); @@ -1676,7 +1678,7 @@ add_fun_env(Env = #{ fun_env := FunEnv }, Decls) -> make_fun_name(#{ context := Context }, Ann, Name) -> Entrypoint = proplists:get_value(entrypoint, Ann, false), case Context of - {main_contract, Main} -> + {contract_def, Main} -> if Entrypoint -> {entrypoint, list_to_binary(Name)}; true -> {local_fun, [Main, Name]} end; @@ -1688,7 +1690,7 @@ make_fun_name(#{ context := Context }, Ann, Name) -> current_namespace(#{ context := Cxt }) -> case Cxt of {abstract_contract, Con} -> Con; - {main_contract, Con} -> Con; + {contract_def, Con} -> Con; {namespace, NS} -> NS end. @@ -2035,8 +2037,11 @@ internal_error(Error) -> %% -- Pretty printing -------------------------------------------------------- format_fcode(#{ functions := Funs }) -> - prettypr:format(pp_above( - [ pp_fun(Name, Def) || {Name, Def} <- maps:to_list(Funs) ])). + prettypr:format(format_funs(Funs)). + +format_funs(Funs) -> + pp_above( + [ pp_fun(Name, Def) || {Name, Def} <- maps:to_list(Funs) ]). format_fexpr(E) -> prettypr:format(pp_fexpr(E)). @@ -2153,7 +2158,9 @@ pp_fexpr({set_state, R, A}) -> pp_call(pp_text("set_state"), [{lit, {int, R}}, A]); pp_fexpr({get_state, R}) -> pp_call(pp_text("get_state"), [{lit, {int, R}}]); -pp_fexpr({switch, Split}) -> pp_split(Split). +pp_fexpr({switch, Split}) -> pp_split(Split); +pp_fexpr({contract_code, Contract}) -> + pp_beside(pp_text("contract "), pp_text(Contract)). pp_call(Fun, Args) -> pp_beside(Fun, pp_fexpr({tuple, Args})). diff --git a/src/aeso_code_errors.erl b/src/aeso_code_errors.erl index f12db23..cc547b7 100644 --- a/src/aeso_code_errors.erl +++ b/src/aeso_code_errors.erl @@ -11,7 +11,7 @@ -export([format/1, pos/1]). format({last_declaration_must_be_contract, Decl = {Kind, _, {con, _, C}, _}}) -> - Msg = io_lib:format("Expected a 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'\n", [Kind, C]), mk_err(pos(Decl), Msg); format({missing_init_function, Con}) -> diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 6a3c576..4a69687 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -180,7 +180,7 @@ string_to_code(ContractString, Options) -> , type_env => TypeEnv , ast => Ast }; fate -> - {Env, Fcode} = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, Options), + {Env, Fcode} = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, [{original_src, ContractString}|Options]), #{ fcode => Fcode , fcode_env => Env , unfolded_typed_ast => UnfoldedTypedAst diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index a296001..2186018 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -184,15 +184,16 @@ lit_to_fate(Env, L) -> {oracle_pubkey, K} -> aeb_fate_data:make_oracle(K); {oracle_query_id, H} -> aeb_fate_data:make_oracle_query(H); {contract_code, C} -> - FCode = maps:get(C, Env#env.child_contracts), - SCode = compile(Env#env.child_contracts, FCode, Env#env.options), - ByteCode = aeb_fate_code:serialize(SCode, []), + 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, []), {ok, Version} = aeso_compiler:version(), + OriginalSourceCode = proplists:get_value(original_src, Options, ""), Code = #{byte_code => ByteCode, compiler_version => Version, - contract_source => "child_contract_src_placeholder", + source_hash => crypto:hash(sha256, OriginalSourceCode ++ [0] ++ C), type_info => [], - fate_code => SCode, abi_version => aeb_fate_abi:abi_version(), payable => maps:get(payable, FCode) }, @@ -586,7 +587,6 @@ builtin_to_scode(Env, chain_clone, [Contract, TypeRep, Value, Prot | InitArgs] ); _ -> - io:format("\n\n************* GAS CAP: ~p\n\n", [GasCap]), call_to_scode(Env, aeb_fate_ops:clone_g(?a, ?a, ?a, ?a, ?a), [Contract, TypeRep, Value, GasCap, Prot | InitArgs] ) @@ -985,7 +985,7 @@ attributes(I) -> {'CREATE', A, B, C} -> Impure(?a, [A, B, C]); {'CLONE', A, B, C, D} -> Impure(?a, [A, B, C, D]); {'CLONE_G', A, B, C, D, E} -> Impure(?a, [A, B, C, D, E]); - {'BYTECODE_HASH', A, B} -> Pure(A, [B]); + {'BYTECODE_HASH', A, B} -> Impure(A, [B]); {'ABORT', A} -> Impure(pc, A); {'EXIT', A} -> Impure(pc, A); 'NOP' -> Pure(none, []) diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index b48e4ff..cab8edc 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -26,7 +26,7 @@ -type ann_format() :: '?:' | hex | infix | prefix | elif. -type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} - | stateful | private] | payable | main | interface. + | stateful | private | payable | main | interface]. -type name() :: string(). -type id() :: {id, ann(), name()}. diff --git a/test/aeso_aci_tests.erl b/test/aeso_aci_tests.erl index 3254e69..46db9e8 100644 --- a/test/aeso_aci_tests.erl +++ b/test/aeso_aci_tests.erl @@ -103,17 +103,24 @@ aci_test_contract(Name) -> true -> [debug_mode]; false -> [] end ++ [{include, {file_system, [aeso_test_utils:contract_path()]}}], - {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), + JSON = case aeso_aci:contract_interface(json, String, Opts) of + {ok, J} -> J; + {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 + {ok, #{aci := JSON1}} -> + ?assertEqual(JSON, JSON1), + io:format("JSON:\n~p\n", [JSON]), + {ok, ContractStub} = aeso_aci:render_aci_json(JSON), - io:format("JSON:\n~p\n", [JSON]), - {ok, ContractStub} = aeso_aci:render_aci_json(JSON), + io:format("STUB:\n~s\n", [ContractStub]), + check_stub(ContractStub, [{src_file, Name}]), - io:format("STUB:\n~s\n", [ContractStub]), - check_stub(ContractStub, [{src_file, Name}]), - - ok. + ok; + {error, ErrorString} when is_binary(ErrorString) -> error(ErrorString); + {error, Error} -> aeso_compiler_tests:print_and_throw(Error) + end. check_stub(Stub, Options) -> try aeso_parser:string(binary_to_list(Stub), Options) of diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 2691b03..83282d1 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -34,9 +34,7 @@ simple_compile_test_() -> #{fate_code := Code} when Backend == fate -> Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)), ?assertMatch({X, X}, {Code1, Code}); - ErrBin -> - io:format("\n~s", [ErrBin]), - error(ErrBin) + Error -> print_and_throw(Error) end end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate], not lists:member(ContractName, not_compilable_on(Backend))] ++ @@ -878,14 +876,26 @@ validation_fails() -> "Byte code contract is not payable, but source code contract is.">>]}]. validate(Contract1, Contract2) -> - ByteCode = #{ fate_code := FCode } = compile(fate, Contract1), - FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)), - Source = aeso_test_utils:read_contract(Contract2), - aeso_compiler:validate_byte_code( - 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()]}}]). + case compile(fate, Contract1) of + ByteCode = #{ fate_code := FCode } -> + FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)), + Source = aeso_test_utils:read_contract(Contract2), + aeso_compiler:validate_byte_code( + 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()]}}]); + Error -> print_and_throw(Error) + end. +print_and_throw(Err) -> + case Err of + ErrBin when is_binary(ErrBin) -> + io:format("\n~s", [ErrBin]), + error(ErrBin); + Errors -> + io:format("Compilation error:\n~s", [string:join([aeso_errors:pp(E) || E <- Errors], "\n\n")]), + error(compilation_error) + end. diff --git a/test/contracts/clone.aes b/test/contracts/clone.aes index 0b3b04a..26c41d4 100644 --- a/test/contracts/clone.aes +++ b/test/contracts/clone.aes @@ -14,7 +14,7 @@ main contract C = let s1 = Chain.clone(ref=s) let Some(s2) = Chain.clone(ref=s, protected=true) let None = Chain.clone(ref=s, protected=true, gas=1) - let None = Chain.clone(ref=l, protected=true, 123) + let None = Chain.clone(ref=l, protected=true, 123) // since it should be HigherOrderState underneath let s3 = Chain.clone(ref=s1) require(s1.apply(2137) == 2137, "APPLY_S1_0") require(s2.apply(2137) == 2137, "APPLY_S2_0") diff --git a/test/contracts/test.aes b/test/contracts/test.aes index 2a0aab7..2b3783e 100644 --- a/test/contracts/test.aes +++ b/test/contracts/test.aes @@ -1,3 +1,6 @@ +// This is a custom test file if you need to run a compiler without +// changing aeso_compiler_tests.erl + include "List.aes" contract IntegerHolder = @@ -5,10 +8,5 @@ contract IntegerHolder = entrypoint init(x) = x entrypoint get() = state -main contract IntegerCollection = - record state = {template: IntegerHolder, payload: list(IntegerHolder)} - stateful entrypoint init() = {template = Chain.create(0), payload = []} - stateful entrypoint add(x) = - put(state{payload @ p = Chain.clone(ref=state.template, x) :: p}) - x - entrypoint sum() = List.sum(List.map((h) => h.get(), state.payload)) \ No newline at end of file +main contract Test = + stateful entrypoint f(c) = Chain.clone(ref=c, 123) \ No newline at end of file -- 2.30.2 From b9d018ec35058415719570f0ddc485d7b45750b0 Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 17 May 2021 08:55:40 +0200 Subject: [PATCH 21/34] Please dialyzer --- src/aeso_ast_to_fcode.erl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 8199297..3a965a9 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -346,17 +346,18 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest]) state_layout => StateLayout, event_type => EventType, payable => Payable, - functions => add_init_function(Env1, Con, StateType, - add_event_function(Env1, EventType, Funs)) }, + functions => add_init_function( + Env1, Con, StateType, + add_event_function(Env1, EventType, Funs)) }, case Contract of - contract_main -> Rest = [], {Env1, ConFcode}; + contract_main -> [] = Rest, {Env1, ConFcode}; contract_child -> Env2 = add_child_con(Env1, Name, ConFcode), Env3 = Env2#{ functions := PrevFuns }, to_fcode(Env3, Rest) end; true -> - Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls), + Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Name} }, Decls), to_fcode(Env1, Rest) end; to_fcode(_Env, [NotMain = {NotMainHead, _ ,_ , _}]) when NotMainHead =/= contract_def -> -- 2.30.2 From 01051408788254641432f9958683d1a691eccf16 Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 17 May 2021 09:42:53 +0200 Subject: [PATCH 22/34] Fix failing tests --- src/aeso_ast_to_fcode.erl | 6 ++++-- src/aeso_code_errors.erl | 2 +- src/aeso_fcode_to_fate.erl | 15 +++++++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 3a965a9..835d4d0 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -719,14 +719,16 @@ expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, case ArgsT of var_args -> fcode_error({var_args_not_set, FunE}); _ -> - FInitArgsT = aeb_fate_data:make_typerep({tuple, [type_to_fcode(Env, T) || T <- ArgsT]}), + %% Here we little cheat on the typechecker, but this inconsistency + %% is to be solved in `aeso_fcode_to_fate:type_to_scode/1` + FInitArgsT = aeb_fate_data:make_typerep([type_to_fcode(Env, T) || T <- ArgsT]), builtin_to_fcode(state_layout(Env), chain_clone, [{lit, FInitArgsT}|FArgs]) end; {builtin_u, chain_create, _Ar} -> case {ArgsT, Type} of {var_args, _} -> fcode_error({var_args_not_set, FunE}); {_, {con, _, Contract}} -> - FInitArgsT = aeb_fate_data:make_typerep({tuple, [type_to_fcode(Env, T) || T <- ArgsT]}), + FInitArgsT = aeb_fate_data:make_typerep([type_to_fcode(Env, T) || T <- ArgsT]), builtin_to_fcode(state_layout(Env), chain_create, [{lit, {contract_code, Contract}}, {lit, FInitArgsT}|FArgs]); {_, _} -> fcode_error({not_a_contract_type, Type}) end; diff --git a/src/aeso_code_errors.erl b/src/aeso_code_errors.erl index cc547b7..b3d1308 100644 --- a/src/aeso_code_errors.erl +++ b/src/aeso_code_errors.erl @@ -10,7 +10,7 @@ -export([format/1, pos/1]). -format({last_declaration_must_be_contract, Decl = {Kind, _, {con, _, C}, _}}) -> +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", [Kind, C]), mk_err(pos(Decl), Msg); diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 2186018..e93da66 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -125,6 +125,7 @@ type_to_scode(bits) -> bits; type_to_scode(any) -> any; type_to_scode({variant, Cons}) -> {variant, [{tuple, types_to_scode(Con)} || Con <- Cons]}; type_to_scode({list, Type}) -> {list, type_to_scode(Type)}; +type_to_scode({tuple, [Type]}) -> type_to_scode(Type); type_to_scode({tuple, Types}) -> {tuple, types_to_scode(Types)}; type_to_scode({map, Key, Val}) -> {map, type_to_scode(Key), type_to_scode(Val)}; type_to_scode({function, _Args, _Res}) -> {tuple, [string, any]}; @@ -135,7 +136,9 @@ type_to_scode({tvar, X}) -> put(?tvars, {I + 1, Vars#{ X => I }}), {tvar, I}; J -> {tvar, J} - end. + end; +type_to_scode(L) when is_list(L) -> {tuple, types_to_scode(L)}. + types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts). @@ -580,22 +583,22 @@ builtin_to_scode(_Env, auth_tx, []) -> builtin_to_scode(Env, chain_bytecode_hash, [_Addr] = Args) -> call_to_scode(Env, aeb_fate_ops:bytecode_hash(?a, ?a), Args); builtin_to_scode(Env, chain_clone, - [TypeRep, GasCap, Value, Prot, Contract | InitArgs]) -> + [InitArgsT, GasCap, Value, Prot, Contract | InitArgs]) -> case GasCap of {builtin, call_gas_left, _} -> call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a), - [Contract, TypeRep, Value, Prot | InitArgs] + [Contract, InitArgsT, Value, Prot | InitArgs] ); _ -> call_to_scode(Env, aeb_fate_ops:clone_g(?a, ?a, ?a, ?a, ?a), - [Contract, TypeRep, Value, GasCap, Prot | InitArgs] + [Contract, InitArgsT, Value, GasCap, Prot | InitArgs] ) end; builtin_to_scode(Env, chain_create, - [ Code, TypeRep, Value | InitArgs]) -> + [ Code, InitArgsT, Value | InitArgs]) -> call_to_scode(Env, aeb_fate_ops:create(?a, ?a, ?a), - [Code, TypeRep, Value | InitArgs] + [Code, InitArgsT, Value | InitArgs] ). %% -- Operators -- -- 2.30.2 From 3ea2de8dbeee27c0873d2d4a065be4b4811b6057 Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 17 May 2021 13:15:00 +0200 Subject: [PATCH 23/34] Write negative tests --- CHANGELOG.md | 1 - src/aeso_ast_infer_types.erl | 69 +++++++++++++------ test/aeso_compiler_tests.erl | 45 ++++++++++-- test/contracts/ambiguous_main.aes | 5 ++ .../code_errors/child_with_decls.aes | 5 ++ test/contracts/factories_type_errors.aes | 24 +++++++ test/contracts/multiple_main_contracts.aes | 5 ++ test/contracts/no_main_contract.aes | 2 + 8 files changed, 130 insertions(+), 26 deletions(-) create mode 100644 test/contracts/ambiguous_main.aes create mode 100644 test/contracts/code_errors/child_with_decls.aes create mode 100644 test/contracts/factories_type_errors.aes create mode 100644 test/contracts/multiple_main_contracts.aes create mode 100644 test/contracts/no_main_contract.aes diff --git a/CHANGELOG.md b/CHANGELOG.md index da38577..5aa5a90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `main` keyword to indicate the main contract in case there are child contracts around - `List.sum` and `List.product` no longer use `List.foldl` ### Removed -- Singleton tuples are no longer unwrapped (this was causing problems with arity 1 init functions) ## [5.0.0] 2021-04-30 ### Added diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 12aeb71..bd52d1b 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -78,7 +78,8 @@ context :: {contract_literal, aeso_syntax:expr()} | {address_to_contract, aeso_syntax:ann()} | {bytecode_hash, aeso_syntax:ann()} | - {var_args, aeso_syntax:ann(), aeso_syntax:expr()} + {var_args, aeso_syntax:ann(), aeso_syntax:expr()}, + force_def = false :: boolean() }). -type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}. @@ -754,10 +755,11 @@ infer(Contracts, Options) -> try Env = init_env(Options), create_options(Options), + ets_new(defined_contracts, [bag]), ets_new(type_vars, [set]), check_modifiers(Env, Contracts), create_type_errors(), - Contracts1 = identify_main_contract(Contracts), + Contracts1 = identify_main_contract(Contracts, Options), destroy_and_report_type_errors(Env), {Env1, Decls} = infer1(Env, Contracts1, [], Options), {Env2, DeclsFolded, DeclsUnfolded} = @@ -786,6 +788,10 @@ infer1(Env, [{Contract, Ann, ConName, Code} | Rest], Acc, Options) contract_child -> contract; contract_interface -> contract_interface end, + case What of + contract -> ets_insert(defined_contracts, {qname(ConName)}); + contract_interface -> ok + end, {Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), What, Code, Options), Contract1 = {Contract, Ann, ConName, Code1}, Env2 = pop_scope(Env1), @@ -801,18 +807,22 @@ infer1(Env, [{pragma, _, _} | Rest], Acc, Options) -> infer1(Env, Rest, Acc, Options). %% Asserts that the main contract is somehow defined. -identify_main_contract(Contracts) -> +identify_main_contract(Contracts, Options) -> Children = [C || C = {contract_child, _, _, _} <- Contracts], Mains = [C || C = {contract_main, _, _, _} <- Contracts], case Mains of [] -> case Children of - [] -> type_error({main_contract_undefined}); + [] -> type_error( + {main_contract_undefined, + [{file, File} || {src_file, File} <- Options]}); [{contract_child, Ann, Con, Body}] -> (Contracts -- Children) ++ [{contract_main, Ann, Con, Body}]; - _ -> type_error({ambiguous_main_contract}) + [H|_] -> type_error({ambiguous_main_contract, + aeso_syntax:get_ann(H)}) end; [_] -> (Contracts -- Mains) ++ Mains; %% Move to the end - _ -> type_error({multiple_main_contracts}) + [H|_] -> type_error({multiple_main_contracts, + aeso_syntax:get_ann(H)}) end. check_scope_name_clash(Env, Kind, Name) -> @@ -1627,7 +1637,7 @@ infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) -> % generally type error, but will be caught _ -> [GasCapMock, ProtectedMock|NamedArgsT] end, - check_contract_construction(Env, RetT, Fun, NamedArgsT1, ArgTypes, RetT), + check_contract_construction(Env, true, RetT, Fun, NamedArgsT1, ArgTypes, RetT), {fun_t, Ann, NamedArgsT, ArgTypes, RetT}; {qid, _, ["Chain", "clone"]} -> {fun_t, _, NamedArgsT, var_args, RetT} = FunType0, @@ -1639,15 +1649,15 @@ infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) -> end, NamedArgsTNoRef = lists:filter(fun({named_arg_t, _, {id, _, "ref"}, _, _}) -> false; (_) -> true end, NamedArgsT), - check_contract_construction(Env, ContractT, Fun, NamedArgsTNoRef, ArgTypes, RetT), + check_contract_construction(Env, false, ContractT, Fun, NamedArgsTNoRef, ArgTypes, RetT), {fun_t, Ann, NamedArgsT, ArgTypes, {if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}}; _ -> FunType0 end, {typed, Ann, Fun, FunType}. --spec check_contract_construction(env(), utype(), utype(), named_args_t(), [utype()], utype()) -> ok. -check_contract_construction(Env, ContractT, Fun, NamedArgsT, ArgTypes, RetT) -> +-spec check_contract_construction(env(), boolean(), utype(), utype(), named_args_t(), [utype()], utype()) -> ok. +check_contract_construction(Env, ForceDef, ContractT, Fun, NamedArgsT, ArgTypes, RetT) -> Ann = aeso_syntax:get_ann(Fun), InitT = fresh_uvar(Ann), unify(Env, InitT, {fun_t, Ann, NamedArgsT, ArgTypes, fresh_uvar(Ann)}, {checking_init_args, Ann, ContractT, ArgTypes}), @@ -1660,7 +1670,9 @@ check_contract_construction(Env, ContractT, Fun, NamedArgsT, ArgTypes, RetT) -> kind = project, context = {var_args, Ann, Fun} } , #is_contract_constraint{ contract_t = ContractT, - context = {var_args, Ann, Fun} } + context = {var_args, Ann, Fun}, + force_def = ForceDef + } ]), ok. @@ -1836,7 +1848,7 @@ next_count() -> ets_tables() -> [options, type_vars, type_defs, record_fields, named_argument_constraints, - field_constraints, freshen_tvars, type_errors]. + field_constraints, freshen_tvars, type_errors, defined_contracts]. clean_up_ets() -> [ catch ets_delete(Tab) || Tab <- ets_tables() ], @@ -2110,12 +2122,20 @@ check_record_create_constraints(Env, [C | Cs]) -> end, check_record_create_constraints(Env, Cs). +is_contract_defined(C) -> + ets_lookup(defined_contracts, qname(C)) =/= []. + check_is_contract_constraints(_Env, []) -> ok; check_is_contract_constraints(Env, [C | Cs]) -> - #is_contract_constraint{ contract_t = Type, context = Cxt } = C, + #is_contract_constraint{ contract_t = Type, context = Cxt, force_def = ForceDef } = C, Type1 = unfold_types_in_type(Env, instantiate(Type)), - case lookup_type(Env, record_type_name(Type1)) of - {_, {_Ann, {[], {contract_t, _}}}} -> ok; + TypeName = record_type_name(Type1), + case lookup_type(Env, TypeName) of + {_, {_Ann, {[], {contract_t, _}}}} -> + case not ForceDef orelse is_contract_defined(TypeName) of + true -> ok; + false -> type_error({contract_lacks_definition, Type1, Cxt}) + end; _ -> type_error({not_a_contract_type, Type1, Cxt}) end, check_is_contract_constraints(Env, Cs). @@ -2943,15 +2963,15 @@ mk_error({named_argument_must_be_literal_bool, Name, Arg}) -> mk_error({conflicting_updates_for_field, Upd, Key}) -> Msg = io_lib:format("Conflicting updates for field '~s'\n", [Key]), mk_t_err(pos(Upd), Msg); -mk_error({ambiguous_main_contract}) -> +mk_error({ambiguous_main_contract, Ann}) -> Msg = "Could not deduce the main contract. You can point it out manually with the `main` keyword.", - mk_t_err(pos(0, 0), Msg); -mk_error({main_contract_undefined}) -> + mk_t_err(pos(Ann), Msg); +mk_error({main_contract_undefined, Ann}) -> Msg = "No contract defined.\n", - mk_t_err(pos(0, 0), Msg); -mk_error({multiple_main_contracts}) -> + mk_t_err(pos(Ann), Msg); +mk_error({multiple_main_contracts, Ann}) -> Msg = "Only one main contract can be defined.\n", - mk_t_err(pos(0, 0), Msg); + mk_t_err(pos(Ann), Msg); mk_error({unify_varargs, When}) -> Msg = "Cannot unify variable argument list.\n", {Pos, Ctxt} = pp_when(When), @@ -2959,6 +2979,13 @@ mk_error({unify_varargs, When}) -> mk_error({clone_no_contract, Ann}) -> Msg = "Chain.clone requires `ref` named argument of contract type.\n", mk_t_err(pos(Ann), Msg); +mk_error({contract_lacks_definition, Type, When}) -> + Msg = io_lib:format( + "~s is not implemented.\n", + [pp_type(Type)] + ), + {Pos, Ctxt} = pp_when(When), + mk_t_err(Pos, Msg, Ctxt); mk_error(Err) -> Msg = io_lib:format("Unknown error: ~p\n", [Err]), mk_t_err(pos(0, 0), Msg). diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 83282d1..00df6ac 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -34,7 +34,7 @@ simple_compile_test_() -> #{fate_code := Code} when Backend == fate -> Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)), ?assertMatch({X, X}, {Code1, Code}); - Error -> print_and_throw(Error) + Error -> io:format("\n\n~p\n\n", [Error]), print_and_throw(Error) end end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate], not lists:member(ContractName, not_compilable_on(Backend))] ++ @@ -126,8 +126,7 @@ compile(Backend, Name, Options) -> %% The currently compilable contracts. compilable_contracts() -> - ["test", - "complex_types", + ["complex_types", "counter", "dutch_auction", "environment", @@ -180,7 +179,10 @@ compilable_contracts() -> "protected_call", "hermetization_turnoff", "multiple_contracts", - "clone", "clone_simple", "create" + "clone", + "clone_simple", + "create", + "test" % Custom general-purpose test file. Keep it last on the list. ]. not_compilable_on(fate) -> []; @@ -728,6 +730,39 @@ failing_contracts() -> , ?TYPE_ERROR(bad_state, [<>]) + , ?TYPE_ERROR(factories_type_errors, + [<>, + < if(protected, option(void), void)\n and (gas : int, value : int, protected : bool, int, bool) => 'b\n" + "when checking contract construction of type\n (gas : int, value : int, protected : bool) =>\n if(protected, option(void), void) (at line 11, column 18)\nagainst the expected type\n (gas : int, value : int, protected : bool, int, bool) => 'b">>, + <>, + <>, + < if(protected, option(void), void)\n and (gas : int, value : int, protected : bool) => 'a\n" + "when checking contract construction of type\n (gas : int, value : int, protected : bool, int, bool) =>\n if(protected, option(void), void) (at line 18, column 18)\nagainst the expected type\n (gas : int, value : int, protected : bool) => 'a">>, + <>, + <> + ]) + , ?TYPE_ERROR(ambiguous_main, + [<> + ]) + , ?TYPE_ERROR(no_main_contract, + [<> + ]) + , ?TYPE_ERROR(multiple_main_contracts, + [<> + ]) ]. -define(Path(File), "code_errors/" ??File). @@ -839,6 +874,8 @@ failing_code_gen_contracts() -> "Invalid state type\n" " {f : (int) => int}\n" "The state cannot contain functions in the AEVM. Use FATE if you need this.") + , ?FATE(child_with_decls, 2, 14, + "Missing definition of function 'f'.") ]. validation_test_() -> diff --git a/test/contracts/ambiguous_main.aes b/test/contracts/ambiguous_main.aes new file mode 100644 index 0000000..d375fa9 --- /dev/null +++ b/test/contracts/ambiguous_main.aes @@ -0,0 +1,5 @@ +contract C = + entrypoint f() = 123 + +contract D = + entrypoint f() = 123 \ No newline at end of file diff --git a/test/contracts/code_errors/child_with_decls.aes b/test/contracts/code_errors/child_with_decls.aes new file mode 100644 index 0000000..4b2d469 --- /dev/null +++ b/test/contracts/code_errors/child_with_decls.aes @@ -0,0 +1,5 @@ +contract C = + entrypoint f : () => unit + +main contract M = + entrypoint f() = 123 \ No newline at end of file diff --git a/test/contracts/factories_type_errors.aes b/test/contracts/factories_type_errors.aes new file mode 100644 index 0000000..2a5ea00 --- /dev/null +++ b/test/contracts/factories_type_errors.aes @@ -0,0 +1,24 @@ +contract interface Kaboom = + entrypoint init : () => void + +contract Bakoom = + type state = int + entrypoint init(x, b) = if(b) x else 0 + +main contract Test = + stateful entrypoint test(k : Kaboom) = + let k_bad1 = Chain.clone() : Kaboom + let k_bad2 = Chain.clone(ref=k, 123, true) + let k_bad3 = Chain.clone(ref=k, gas=true) + let k_bad4 = Chain.create() : Kaboom + let k_gud1 = Chain.clone(ref=k) + let Some(k_gud2) = Chain.clone(ref=k, protected=true) + let Some(k_gud3) = Chain.clone(ref=k, value=10, protected=true, gas=123) + + let b_bad1 = Chain.create() : Bakoom + let b_bad2 = Chain.create(123, true, protected=true) : Bakoom + let b_bad3 = Chain.create(123, true, value=true) : Bakoom + let b_gud1 = Chain.create(123, true) : Bakoom + let b_gud2 = Chain.create(123, true, value=100) : Bakoom + + b_gud1 \ No newline at end of file diff --git a/test/contracts/multiple_main_contracts.aes b/test/contracts/multiple_main_contracts.aes new file mode 100644 index 0000000..6c85bb1 --- /dev/null +++ b/test/contracts/multiple_main_contracts.aes @@ -0,0 +1,5 @@ +main contract C = + entrypoint f() = 123 + +main contract D = + entrypoint f() = 123 \ No newline at end of file diff --git a/test/contracts/no_main_contract.aes b/test/contracts/no_main_contract.aes new file mode 100644 index 0000000..4b0e9b4 --- /dev/null +++ b/test/contracts/no_main_contract.aes @@ -0,0 +1,2 @@ +contract interface C = + entrypoint f : () => unit \ No newline at end of file -- 2.30.2 From ce4d1cf978fca6e3f6e3ed321ef4cec3ebeb569f Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 17 May 2021 19:00:58 +0200 Subject: [PATCH 24/34] Docs --- docs/sophia.md | 94 ++++++++++++++++++++++----------- docs/sophia_stdlib.md | 120 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 31 deletions(-) diff --git a/docs/sophia.md b/docs/sophia.md index 98f0a43..f3d8363 100644 --- a/docs/sophia.md +++ b/docs/sophia.md @@ -94,16 +94,16 @@ To call a function in another contract you need the address to an instance of the contract. The type of the address must be a contract type, which consists of a number of type definitions and entrypoint declarations. For instance, -```javascript +```sophia // A contract type -contract VotingType = +contract interface VotingType = entrypoint vote : string => unit ``` Now given contract address of type `VotingType` you can call the `vote` entrypoint of that contract: -```javascript +```sophia contract VoteTwice = entrypoint voteTwice(v : VotingType, alt : string) = v.vote(alt) @@ -114,7 +114,7 @@ Contract calls take two optional named arguments `gas : int` and `value : int` that lets you set a gas limit and provide tokens to a contract call. If omitted the defaults are no gas limit and no tokens. Suppose there is a fee for voting: -```javascript +```sophia entrypoint voteTwice(v : VotingType, fee : int, alt : string) = v.vote(value = fee, alt) v.vote(value = fee, alt) @@ -136,7 +136,7 @@ To recover the underlying `address` of a contract instance there is a field `address : address`. For instance, to send tokens to the voting contract (given that it is payable) without calling it you can write -```javascript +```sophia entrypoint pay(v : VotingType, amount : int) = Chain.spend(v.address, amount) ``` @@ -154,7 +154,7 @@ If the call fails the result is `None`, otherwise it's `Some(r)` where `r` is the return value of the call. ```sophia -contract VotingType = +contract interface VotingType = entrypoint : vote : string => unit contract Voter = @@ -171,10 +171,42 @@ However, note that errors that would normally consume all the gas in the transaction still only uses up the gas spent running the contract. +### Contract factories and child contracts + +Since the version 5.0.0 Sophia supports deploying contracts by other +contracts. This can be done in two ways: + +- Contract cloning via [`Chain.clone`](sophia_stdlib.md#clone) +- Direct deploy via [`Chain.create`](sophia_stdlib.md#create) + +These functions take variable number of arguments that must match the created +contract's `init` function. Beside that they take some additional named +arguments – please refer to their documentation for details. + +While `Chain.clone` requires only a `contract interface` and a living instance +of a given contract on chain, `Chain.create` needs a full definition of a +to-create contract defined by the standard `contract` syntax, for example + +``` +contract IntHolder = + type state = int + entrypoint init(x) = x + entrypoint get() = state + +main contract IntHolderFactory = + entrypoint new(x : int) : IntHolder = + let ih = Chain.create(x) : IntHolder + ih +``` + +In case of a presence of child contracts (`IntHolder` in this case), the main +contract must be pointed out with the `main` keyword as shown in the example. + + ### Mutable state -Sophia does not have arbitrary mutable state, but only a limited form of -state associated with each contract instance. +Sophia does not have arbitrary mutable state, but only a limited form of state +associated with each contract instance. - Each contract defines a type `state` encapsulating its mutable state. The type `state` defaults to the `unit`. @@ -200,7 +232,7 @@ Top-level functions and entrypoints must be annotated with the `stateful` keyword to be allowed to affect the state of the running contract. For instance, -```javascript +```sophia stateful entrypoint set_state(s : state) = put(s) ``` @@ -237,7 +269,7 @@ A concrete contract is by default *not* payable. Any attempt at spending to such a contract (either a `Chain.spend` or a normal spend transaction) will fail. If a contract shall be able to receive funds in this way it has to be declared `payable`: -```javascript +```sophia // A payable contract payable contract ExampleContract = stateful entrypoint do_stuff() = ... @@ -253,7 +285,7 @@ A contract entrypoint is by default *not* payable. Any call to such a function that has a non-zero `value` will fail. Contract entrypoints that should be called with a non-zero value should be declared `payable`. -```javascript +```sophia payable stateful entrypoint buy(to : address) = if(Call.value > 42) transfer_item(to) @@ -414,7 +446,7 @@ corresponding integer, so setting very high bits can be expensive). Type aliases can be introduced with the `type` keyword and can be parameterized. For instance -``` +```sophia type number = int type string_map('a) = map(string, 'a) ``` @@ -434,7 +466,7 @@ datatype one_or_both('a, 'b) = Left('a) | Right('b) | Both('a, 'b) Elements of data types can be pattern matched against, using the `switch` construct: -``` +```sophia function get_left(x : one_or_both('a, 'b)) : option('a) = switch(x) Left(x) => Some(x) @@ -443,7 +475,7 @@ function get_left(x : one_or_both('a, 'b)) : option('a) = ``` or directly in the left-hand side: -``` +```sophia function get_left : one_or_both('a, 'b) => option('a) get_left(Left(x)) = Some(x) @@ -461,7 +493,7 @@ elements of a list can be any of datatype but they must have the same type. The type of lists with elements of type `'e` is written `list('e)`. For example we can have the following lists: -``` +```sophia [1, 33, 2, 666] : list(int) [(1, "aaa"), (10, "jjj"), (666, "the beast")] : list(int * string) [{[1] = "aaa", [10] = "jjj"}, {[5] = "eee", [666] = "the beast"}] : list(map(int, string)) @@ -475,13 +507,13 @@ and returns the resulting list. So concatenating two lists Sophia supports list comprehensions known from languages like Python, Haskell or Erlang. Example syntax: -``` +```sophia [x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]] // yields [12,13,14,20,21,22,30,31,32] ``` Lists can be constructed using the range syntax using special `..` operator: -``` +```sophia [1..4] == [1,2,3,4] ``` The ranges are always ascending and have step equal to 1. @@ -493,7 +525,7 @@ Please refer to the [standard library](sophia_stdlib.md#List) for the predefined A Sophia record type is given by a fixed set of fields with associated, possibly different, types. For instance -``` +```sophia record account = { name : string, balance : int, history : list(transaction) } @@ -510,12 +542,12 @@ Please refer to the [standard library](sophia_stdlib.md#Map) for the predefined A value of record type is constructed by giving a value for each of the fields. For the example above, -``` +```sophia function new_account(name) = {name = name, balance = 0, history = []} ``` Maps are constructed similarly, with keys enclosed in square brackets -``` +```sophia function example_map() : map(string, int) = {["key1"] = 1, ["key2"] = 2} ``` @@ -524,7 +556,7 @@ The empty map is written `{}`. #### Accessing values Record fields access is written `r.f` and map lookup `m[k]`. For instance, -``` +```sophia function get_balance(a : address, accounts : map(address, account)) = accounts[a].balance ``` @@ -549,14 +581,14 @@ in the map or execution fails, but a default value can be provided: `k` is not in the map. Updates can be nested: -``` +```sophia function clear_history(a : address, accounts : map(address, account)) : map(address, account) = accounts{ [a].history = [] } ``` This is equivalent to `accounts{ [a] @ acc = acc{ history = [] } }` and thus requires `a` to be present in the accounts map. To have `clear_history` create an account if `a` is not in the map you can write (given a function `empty_account`): -``` +```sophia accounts{ [a = empty_account()].history = [] } ``` @@ -627,7 +659,7 @@ For a functionality documentation refer to the [standard library](sophia_stdlib. #### Example Example for an oracle answering questions of type `string` with answers of type `int`: -``` +```sophia contract Oracles = stateful entrypoint registerOracle(acct : address, @@ -698,7 +730,7 @@ an account with address `addr`. In order to allow a contract `ct` to handle Armed with this information we can for example write a function that extends the name if it expires within 1000 blocks: -``` +```sophia stateful entrypoint extend_if_necessary(addr : address, name : string, sig : signature) = switch(AENS.lookup(name)) None => () @@ -709,7 +741,7 @@ the name if it expires within 1000 blocks: And we can write functions that adds and removes keys from the pointers of the name: -``` +```sophia stateful entrypoint add_key(addr : address, name : string, key : string, pt : AENS.pointee, sig : signature) = switch(AENS.lookup(name)) @@ -768,7 +800,7 @@ The fields can appear in any order. Events are emitted by using the `Chain.event` function. The following function will emit one Event of each kind in the example. -``` +```sophia entrypoint emit_events() : () = Chain.event(TheFirstEvent(42)) Chain.event(AnotherEvent(Contract.address, "This is not indexed")) @@ -778,7 +810,7 @@ will emit one Event of each kind in the example. It is only possible to have one (1) `string` parameter in the event, but it can be placed in any position (and its value will end up in the `data` field), i.e. -``` +```sophia AnotherEvent(string, indexed address) ... @@ -837,7 +869,7 @@ and `*/` and can be nested. ``` contract elif else entrypoint false function if import include let mod namespace -private payable stateful switch true type record datatype +private payable stateful switch true type record datatype main interface ``` #### Tokens @@ -941,7 +973,7 @@ Args ::= '(' Sep(Pattern, ',') ')' Contract declarations must appear at the top-level. For example, -``` +```sophia contract Test = type t = int entrypoint add (x : t, y : t) = x + y @@ -1089,7 +1121,7 @@ In order of highest to lowest precedence. ## Examples -``` +```sophia /* * A simple crowd-funding example */ diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index a39c630..78c3cd0 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -765,6 +765,126 @@ Chain.gas_limit : int The gas limit of the current block. +#### bytecode_hash +``` +Chain.bytecode_hash : 'c => option(hash) +``` + +Returns hash of the contract's bytecode (or `None` if it is nonexistent or +deployed before FATE2). The type `'c` must be instantiated with a contract. +The charged gas is affine to the size of the serialized bytecode. + + +#### create +``` +Chain.create(value : int, ...) => 'c +``` + +Creates and deploys a new instance of a contract `'c`. All of the unnamed +arguments will be passed to the `init` function. The charged gas is affine to +the size of the compiled child contract's bytecode. The `source_hash` onchain +entry of the newly created contract will be a SHA256 hash over concatenation of + +- whole contract source code +- single null byte +- name of the child contract + + +The `value` argument (default `0`) is equivalent to the value in the contract +creation transaction – it sets the initial value of the newly created contract +charging the calling contract. Note that this won't be visible in `Call.value` +in the `init` call of the new contract. It will be included in +`Contract.balance`, however. + + +The type `'c` must be instantiated with a contract. + + +Example usage: +``` +payable contract Auction = + record state = {supply: int, name: string} + entrypoint init(supply, name) = {supply: supply, name: name} + stateful payable entrypoint buy(amount) = + require(Call.value == amount, "amount_value_mismatch") + ... + stateful entrypoint sell(amount) = + require(amount >= 0, "negative_amount") + ... + +main contract Market = + type state = list(Auction) + entrypoint init() = [] + stateful entrypoint new(name : string) = + let auction = Chain.create(0, name) : Auction + put(new_auction::state) +``` + +The typechecker must be certain about the created contract's type, so it is +worth writing it explicitly as shown in the example. + +#### clone +``` +Chain.clone : ( ref : 'c, gas : int, value : int, protected : bool, ... + ) => if(protected) option('c) else 'c +``` + +Clones the contract under the mandatory named argument `ref`. That means a new +contract of the same bytecode and the same `payable` parameter shall be created. +The resulting contract's public key can be predicted and in case it happens to +have some funds before its creation, its balance will remain or be increased by +the `value` parameter. **NOTE:** the `state` won't be copied and the contract +will be initialized with a regular call to the `init` function with the +remaining unnamed arguments. This operation is significantly cheaper than +`Chain.create` as it costs a fixed amount of gas. + + +The `gas` argument (default `Call.gas_left`) limits the gas supply for the +`init` call of the cloned contract. + + +The `value` argument (default `0`) is equivalent to the value in the contract +creation transaction – it sets the initial value of the newly created contract +charging the calling contract. Note that this won't be visible in `Call.value` +in the `init` call of the new contract. It will be included in +`Contract.balance`, however. + + +The `protected` argument (default `false`) works identically as in remote calls. +If set to `true` it will change the return type to `option('c)` and will catch +all errors such as `abort`, out of gas and wrong arguments. Note that it can +only take a boolean *literal*, so other expressions such as variables will be +rejected by the compiler. + + +The type `'c` must be instantiated with a contract. + + +Example usage: + +``` +payable contract interface Auction = + entrypoint init : (int, string) => void + stateful payable entrypoint buy : (int) => () + stateful entrypoint sell : (int) => () + +main contract Market = + type state = list(Auction) + entrypoint init() = [] + stateful entrypoint new_of(template : Auction, name : string) = + switch(Chain.clone(ref=template, protected=true, 0, name)) + None => abort("Bad auction!") + Some(new_auction) => + put(new_auction::state) +``` + +When cloning by an interface, `init` entrypoint declaration is required. It is a +good practice to set its return type to `void` in order to indicate that this +function is not supposed to be called and is state agnostic. Trivia: internal +implementation of the `init` function does not actually return `state`, but +calls `put` instead. Moreover, FATE prevents even handcrafted calls to `init`. + + #### event ``` Chain.event(e : event) : unit -- 2.30.2 From 8100912dfc36a412e4e6bfc9db883936e90c0dcc Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 17 May 2021 19:07:40 +0200 Subject: [PATCH 25/34] TOC --- docs/sophia.md | 122 ++++++++++++++++++++++++------------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/docs/sophia.md b/docs/sophia.md index f3d8363..ff0703f 100644 --- a/docs/sophia.md +++ b/docs/sophia.md @@ -1,65 +1,6 @@ -**Table of Contents** - -- [-](#-) -- [Language Features](#language-features) - - [Contracts](#contracts) - - [Calling other contracts](#calling-other-contracts) - - [Protected contract calls](#protected-contract-calls) - - [Mutable state](#mutable-state) - - [Stateful functions](#stateful-functions) - - [Payable](#payable) - - [Payable contracts](#payable-contracts) - - [Payable entrypoints](#payable-entrypoints) - - [Namespaces](#namespaces) - - [Splitting code over multiple files](#splitting-code-over-multiple-files) - - [Standard library](#standard-library) - - [Types](#types) - - [Literals](#literals) - - [Arithmetic](#arithmetic) - - [Bit fields](#bit-fields) - - [Type aliases](#type-aliases) - - [Algebraic data types](#algebraic-data-types) - - [Lists](#lists) - - [Maps and records](#maps-and-records) - - [Constructing maps and records](#constructing-maps-and-records) - - [Accessing values](#accessing-values) - - [Updating a value](#updating-a-value) - - [Map implementation](#map-implementation) - - [Strings](#strings) - - [Chars](#chars) - - [Byte arrays](#byte-arrays) - - [Cryptographic builins](#cryptographic-builins) - - [AEVM note](#aevm-note) - - [Authorization interface](#authorization-interface) - - [Oracle interface](#oracle-interface) - - [Example](#example) - - [Sanity checks](#sanity-checks) - - [AENS interface](#aens-interface) - - [Example](#example-1) - - [Events](#events) - - [Argument order](#argument-order) - - [Compiler pragmas](#compiler-pragmas) - - [Exceptions](#exceptions) - - [Syntax](#syntax) - - [Lexical syntax](#lexical-syntax) - - [Comments](#comments) - - [Keywords](#keywords) - - [Tokens](#tokens) - - [Layout blocks](#layout-blocks) - - [Notation](#notation) - - [Declarations](#declarations) - - [Types](#types-1) - - [Statements](#statements) - - [Expressions](#expressions) - - [Operators types](#operators-types) - - [Operator precendences](#operator-precendences) - - [Examples](#examples) - - [Delegation signature](#delegation-signature) - - -## The Sophia Language +# The Sophia Language An Æternity BlockChain Language The Sophia is a language in the ML family. It is strongly typed and has @@ -69,6 +10,65 @@ Sophia is customized for smart contracts, which can be published to a blockchain (the Æternity BlockChain). Thus some features of conventional languages, such as floating point arithmetic, are not present in Sophia, and some blockchain specific primitives, constructions and types have been added. + +**Table of Contents** + + - [Language Features](#language-features) + - [Contracts](#contracts) + - [Calling other contracts](#calling-other-contracts) + - [Protected contract calls](#protected-contract-calls) + - [Contract factories and child contracts](#contract-factories-and-child-contracts) + - [Mutable state](#mutable-state) + - [Stateful functions](#stateful-functions) + - [Payable](#payable) + - [Payable contracts](#payable-contracts) + - [Payable entrypoints](#payable-entrypoints) + - [Namespaces](#namespaces) + - [Splitting code over multiple files](#splitting-code-over-multiple-files) + - [Standard library](#standard-library) + - [Types](#types) + - [Literals](#literals) + - [Arithmetic](#arithmetic) + - [Bit fields](#bit-fields) + - [Type aliases](#type-aliases) + - [Algebraic data types](#algebraic-data-types) + - [Lists](#lists) + - [Maps and records](#maps-and-records) + - [Constructing maps and records](#constructing-maps-and-records) + - [Accessing values](#accessing-values) + - [Updating a value](#updating-a-value) + - [Map implementation](#map-implementation) + - [Strings](#strings) + - [Chars](#chars) + - [Byte arrays](#byte-arrays) + - [Cryptographic builins](#cryptographic-builins) + - [AEVM note](#aevm-note) + - [Authorization interface](#authorization-interface) + - [Oracle interface](#oracle-interface) + - [Example](#example) + - [Sanity checks](#sanity-checks) + - [AENS interface](#aens-interface) + - [Example](#example) + - [Events](#events) + - [Argument order](#argument-order) + - [Compiler pragmas](#compiler-pragmas) + - [Exceptions](#exceptions) + - [Syntax](#syntax) + - [Lexical syntax](#lexical-syntax) + - [Comments](#comments) + - [Keywords](#keywords) + - [Tokens](#tokens) + - [Layout blocks](#layout-blocks) + - [Notation](#notation) + - [Declarations](#declarations) + - [Types](#types) + - [Statements](#statements) + - [Expressions](#expressions) + - [Operators types](#operators-types) + - [Operator precendences](#operator-precendences) + - [Examples](#examples) + - [Delegation signature](#delegation-signature) + ## Language Features ### Contracts @@ -171,7 +171,7 @@ However, note that errors that would normally consume all the gas in the transaction still only uses up the gas spent running the contract. -### Contract factories and child contracts +#### Contract factories and child contracts Since the version 5.0.0 Sophia supports deploying contracts by other contracts. This can be done in two ways: -- 2.30.2 From 4aebdeceec0178c95e7500f3ecec372346686326 Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 17 May 2021 19:09:04 +0200 Subject: [PATCH 26/34] missing 'the' --- docs/sophia.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sophia.md b/docs/sophia.md index ff0703f..6835a35 100644 --- a/docs/sophia.md +++ b/docs/sophia.md @@ -181,7 +181,7 @@ contracts. This can be done in two ways: These functions take variable number of arguments that must match the created contract's `init` function. Beside that they take some additional named -arguments – please refer to their documentation for details. +arguments – please refer to their documentation for the details. While `Chain.clone` requires only a `contract interface` and a living instance of a given contract on chain, `Chain.create` needs a full definition of a -- 2.30.2 From 860457b466077dfef45505eeb8069dbe8af5d62e Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 17 May 2021 19:09:30 +0200 Subject: [PATCH 27/34] missing 'the' --- docs/sophia.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sophia.md b/docs/sophia.md index 6835a35..8f8fd42 100644 --- a/docs/sophia.md +++ b/docs/sophia.md @@ -184,7 +184,7 @@ contract's `init` function. Beside that they take some additional named arguments – please refer to their documentation for the details. While `Chain.clone` requires only a `contract interface` and a living instance -of a given contract on chain, `Chain.create` needs a full definition of a +of a given contract on the chain, `Chain.create` needs a full definition of a to-create contract defined by the standard `contract` syntax, for example ``` -- 2.30.2 From 53d5d95ab1a870e7e1085a2abe267a537c951684 Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 17 May 2021 19:10:22 +0200 Subject: [PATCH 28/34] missing 'the' --- docs/sophia_stdlib.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 78c3cd0..abc2e7e 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -770,9 +770,9 @@ The gas limit of the current block. Chain.bytecode_hash : 'c => option(hash) ``` -Returns hash of the contract's bytecode (or `None` if it is nonexistent or -deployed before FATE2). The type `'c` must be instantiated with a contract. -The charged gas is affine to the size of the serialized bytecode. +Returns the hash of the contract's bytecode (or `None` if it is nonexistent or +deployed before FATE2). The type `'c` must be instantiated with a contract. The +charged gas is affine to the size of the serialized bytecode. #### create -- 2.30.2 From dd1d835e4f1744d40bc20e4b0632e2c2efacfc16 Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 17 May 2021 19:11:36 +0200 Subject: [PATCH 29/34] missing 'the' --- docs/sophia_stdlib.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index abc2e7e..a6c4b10 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -782,8 +782,9 @@ Chain.create(value : int, ...) => 'c Creates and deploys a new instance of a contract `'c`. All of the unnamed arguments will be passed to the `init` function. The charged gas is affine to -the size of the compiled child contract's bytecode. The `source_hash` onchain -entry of the newly created contract will be a SHA256 hash over concatenation of +the size of the compiled child contract's bytecode. The `source_hash` on-chain +entry of the newly created contract will be the SHA256 hash over concatenation +of - whole contract source code - single null byte -- 2.30.2 From cfb51e0825a873ddb7628b2bc493117d6ad2ade8 Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 17 May 2021 19:13:06 +0200 Subject: [PATCH 30/34] mention pre-fund --- docs/sophia_stdlib.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index a6c4b10..89cc667 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -795,7 +795,9 @@ The `value` argument (default `0`) is equivalent to the value in the contract creation transaction – it sets the initial value of the newly created contract charging the calling contract. Note that this won't be visible in `Call.value` in the `init` call of the new contract. It will be included in -`Contract.balance`, however. +`Contract.balance`, however. The resulting contract's public key can be +predicted and in case it happens to have some funds before its creation, its +balance will remain or be increased by the `value` parameter. The type `'c` must be instantiated with a contract. -- 2.30.2 From 87d20b1c94906deb7afd104d18b02826c1bd887e Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 17 May 2021 19:14:28 +0200 Subject: [PATCH 31/34] format --- docs/sophia_stdlib.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 89cc667..c44549d 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -790,14 +790,15 @@ of - single null byte - name of the child contract +The resulting contract's public key can be predicted and in case it happens to +have some funds before its creation, its balance will remain or be increased by +the `value` parameter. The `value` argument (default `0`) is equivalent to the value in the contract creation transaction – it sets the initial value of the newly created contract charging the calling contract. Note that this won't be visible in `Call.value` in the `init` call of the new contract. It will be included in -`Contract.balance`, however. The resulting contract's public key can be -predicted and in case it happens to have some funds before its creation, its -balance will remain or be increased by the `value` parameter. +`Contract.balance`, however. The type `'c` must be instantiated with a contract. @@ -845,24 +846,20 @@ remaining unnamed arguments. This operation is significantly cheaper than The `gas` argument (default `Call.gas_left`) limits the gas supply for the `init` call of the cloned contract. - The `value` argument (default `0`) is equivalent to the value in the contract creation transaction – it sets the initial value of the newly created contract charging the calling contract. Note that this won't be visible in `Call.value` in the `init` call of the new contract. It will be included in `Contract.balance`, however. - The `protected` argument (default `false`) works identically as in remote calls. If set to `true` it will change the return type to `option('c)` and will catch all errors such as `abort`, out of gas and wrong arguments. Note that it can only take a boolean *literal*, so other expressions such as variables will be rejected by the compiler. - The type `'c` must be instantiated with a contract. - Example usage: ``` -- 2.30.2 From 8d1ede63e5616b73ea78d383920e5afbde24dfa7 Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 17 May 2021 19:15:10 +0200 Subject: [PATCH 32/34] pre-fund clarification --- docs/sophia_stdlib.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index c44549d..f0e01dd 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -791,7 +791,7 @@ of - name of the child contract The resulting contract's public key can be predicted and in case it happens to -have some funds before its creation, its balance will remain or be increased by +have some funds before its creation, its balance will be increased by the `value` parameter. The `value` argument (default `0`) is equivalent to the value in the contract @@ -836,7 +836,7 @@ Chain.clone : ( ref : 'c, gas : int, value : int, protected : bool, ... Clones the contract under the mandatory named argument `ref`. That means a new contract of the same bytecode and the same `payable` parameter shall be created. The resulting contract's public key can be predicted and in case it happens to -have some funds before its creation, its balance will remain or be increased by +have some funds before its creation, its balance will be increased by the `value` parameter. **NOTE:** the `state` won't be copied and the contract will be initialized with a regular call to the `init` function with the remaining unnamed arguments. This operation is significantly cheaper than -- 2.30.2 From 90fad0c49bc3ec67e35fe8a72f3b90fbc40edd87 Mon Sep 17 00:00:00 2001 From: radrow Date: Mon, 17 May 2021 19:18:05 +0200 Subject: [PATCH 33/34] format --- docs/sophia_stdlib.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index f0e01dd..efbddd8 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -772,7 +772,8 @@ Chain.bytecode_hash : 'c => option(hash) Returns the hash of the contract's bytecode (or `None` if it is nonexistent or deployed before FATE2). The type `'c` must be instantiated with a contract. The -charged gas is affine to the size of the serialized bytecode. +charged gas is affine to the size of the serialized bytecode of the deployed +contract. #### create @@ -835,12 +836,12 @@ Chain.clone : ( ref : 'c, gas : int, value : int, protected : bool, ... Clones the contract under the mandatory named argument `ref`. That means a new contract of the same bytecode and the same `payable` parameter shall be created. -The resulting contract's public key can be predicted and in case it happens to -have some funds before its creation, its balance will be increased by -the `value` parameter. **NOTE:** the `state` won't be copied and the contract -will be initialized with a regular call to the `init` function with the -remaining unnamed arguments. This operation is significantly cheaper than -`Chain.create` as it costs a fixed amount of gas. +**NOTE:** the `state` won't be copied and the contract will be initialized with +a regular call to the `init` function with the remaining unnamed arguments. The +resulting contract's public key can be predicted and in case it happens to have +some funds before its creation, its balance will be increased by the `value` +parameter. This operation is significantly cheaper than `Chain.create` as it +costs a fixed amount of gas. The `gas` argument (default `Call.gas_left`) limits the gas supply for the -- 2.30.2 From 166b56a7a83bb4bc621099346459c7e2ebf30ecf Mon Sep 17 00:00:00 2001 From: radrow Date: Tue, 18 May 2021 09:18:57 +0200 Subject: [PATCH 34/34] Grammar in docs --- docs/sophia_stdlib.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index efbddd8..6710dcc 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -770,10 +770,10 @@ The gas limit of the current block. Chain.bytecode_hash : 'c => option(hash) ``` -Returns the hash of the contract's bytecode (or `None` if it is nonexistent or -deployed before FATE2). The type `'c` must be instantiated with a contract. The -charged gas is affine to the size of the serialized bytecode of the deployed -contract. +Returns the hash of the contract's bytecode (or `None` if it is +nonexistent or deployed before FATE2). The type `'c` must be +instantiated with a contract. The charged gas increases linearly to +the size of the serialized bytecode of the deployed contract. #### create @@ -781,11 +781,11 @@ contract. Chain.create(value : int, ...) => 'c ``` -Creates and deploys a new instance of a contract `'c`. All of the unnamed -arguments will be passed to the `init` function. The charged gas is affine to -the size of the compiled child contract's bytecode. The `source_hash` on-chain -entry of the newly created contract will be the SHA256 hash over concatenation -of +Creates and deploys a new instance of a contract `'c`. All of the +unnamed arguments will be passed to the `init` function. The charged +gas increases linearly with the size of the compiled child contract's +bytecode. The `source_hash` on-chain entry of the newly created +contract will be the SHA256 hash over concatenation of - whole contract source code - single null byte -- 2.30.2