diff --git a/rebar.config b/rebar.config index 07f9c84..e0d5529 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,"fdd660a"}}} +{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"befa1e3"}}} , {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 39c6dc1..bebe38c 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,7 +1,7 @@ {"1.1.0", [{<<"aebytecode">>, {git,"https://github.com/aeternity/aebytecode.git", - {ref,"fdd660a2191f19e1bd77cafe0d39c826539c1b6c"}}, + {ref,"befa1e3ff9c53c1054917c82e3b30c1c3c743f4f"}}, 0}, {<<"aeserialization">>, {git,"https://github.com/aeternity/aeserialization.git", diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index 2edba2c..433c17d 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -113,7 +113,7 @@ encode_contract(Contract = {contract, _, {con, _, Name}, _}) -> || F <- sort_decls(contract_funcs(Contract)), is_entrypoint(F) ], - #{contract => C3#{functions => Fdefs}}; + #{contract => C3#{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), @@ -125,12 +125,14 @@ encode_function(FDef = {letfun, _, {id, _, Name}, Args, Type, _}) -> #{name => encode_name(Name), arguments => encode_args(Args), returns => encode_type(Type), - stateful => is_stateful(FDef)}; + stateful => is_stateful(FDef), + payable => is_payable(FDef)}; encode_function(FDecl = {fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Type}}) -> #{name => encode_name(Name), arguments => encode_anon_args(Args), returns => encode_type(Type), - stateful => is_stateful(FDecl)}. + stateful => is_stateful(FDecl), + payable => is_payable(FDecl)}. encode_anon_args(Types) -> Anons = [ list_to_binary("_" ++ integer_to_list(X)) || X <- lists:seq(1, length(Types))], @@ -234,12 +236,13 @@ do_render_aci_json(Json) -> {ok, list_to_binary(string:join(DecodedContracts, "\n"))}. decode_contract(#{contract := #{name := Name, + 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, - ["contract ", io_lib:format("~s", [Name])," =\n", + [payable(Payable), "contract ", 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", @@ -249,8 +252,8 @@ decode_contract(_) -> []. decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ]. %% decode_func(#{name := init}) -> []; -decode_func(#{name := Name, arguments := As, returns := T}) -> - [" entrypoint", " ", io_lib:format("~s", [Name]), " : ", +decode_func(#{name := Name, payable := Payable, arguments := As, returns := T}) -> + [" ", payable(Payable), "entrypoint ", io_lib:format("~s", [Name]), " : ", decode_args(As), " => ", decode_type(T), $\n]. decode_args(As) -> @@ -321,13 +324,16 @@ decode_deftype(#{record := _Efs}) -> "record"; decode_deftype(#{variant := _}) -> "datatype"; decode_deftype(_T) -> "type". -decode_tvars([]) -> []; %No tvars, no parentheses +decode_tvars([]) -> []; %No tvars, no parentheses decode_tvars(Vs) -> Dvs = [ decode_tvar(V) || V <- Vs ], [$(,lists:join(", ", Dvs),$)]. decode_tvar(#{name := N}) -> io_lib:format("~s", [N]). +payable(true) -> "payable "; +payable(false) -> "". + %% #contract{Ann, Con, [Declarations]}. contract_funcs({C, _, _, Decls}) when C == contract; C == namespace -> @@ -352,6 +358,7 @@ sort_decls(Ds) -> is_entrypoint(Node) -> aeso_syntax:get_ann(entrypoint, Node, false). is_stateful(Node) -> aeso_syntax:get_ann(stateful, Node, false). +is_payable(Node) -> aeso_syntax:get_ann(payable, Node, false). typedef_name({type_def, _, {id, _, Name}, _, _}) -> Name. diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 2bd79fa..e640c49 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -504,7 +504,8 @@ global_env() -> IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) }, AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}, {"is_oracle", Fun1(Address, Bool)}, - {"is_contract", Fun1(Address, Bool)}]) }, + {"is_contract", Fun1(Address, Bool)}, + {"is_payable", Fun1(Address, Bool)}]) }, #env{ scopes = #{ [] => TopScope diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index c8e8cda..91c4dcb 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -16,7 +16,7 @@ -type option() :: term(). --type attribute() :: stateful | pure | private. +-type attribute() :: stateful | payable | pure | private. -type fun_name() :: {entrypoint, binary()} | {local_fun, [string()]} @@ -109,7 +109,8 @@ -type fcode() :: #{ contract_name := string(), state_type := ftype(), event_type := ftype() | none, - functions := #{ fun_name() => fun_def() } }. + functions := #{ fun_name() => fun_def() }, + payable := boolean() }. -type type_def() :: fun(([ftype()]) -> ftype()). @@ -196,7 +197,7 @@ builtins() -> {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, {["Bytes"], [{"to_int", 1}, {"to_str", 1}]}, {["Int"], [{"to_str", 1}]}, - {["Address"], [{"to_str", 1}, {"is_oracle", 1}, {"is_contract", 1}]} + {["Address"], [{"to_str", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]} ], maps:from_list([ {NS ++ [Fun], {MkName(NS, Fun), Arity}} || {NS, Funs} <- Scopes, @@ -227,7 +228,7 @@ init_type_env() -> %% -- Compilation ------------------------------------------------------------ -spec to_fcode(env(), aeso_syntax:ast()) -> fcode(). -to_fcode(Env, [{contract, _, {con, _, Main}, Decls}]) -> +to_fcode(Env, [{contract, Attrs, {con, _, Main}, Decls}]) -> #{ builtins := Builtins } = Env, MainEnv = Env#{ context => {main_contract, Main}, builtins => Builtins#{[Main, "state"] => {get_state, none}, @@ -237,9 +238,11 @@ to_fcode(Env, [{contract, _, {con, _, Main}, Decls}]) -> decls_to_fcode(MainEnv, Decls), StateType = lookup_type(Env1, [Main, "state"], [], {tuple, []}), EventType = lookup_type(Env1, [Main, "event"], [], none), + Payable = proplists:get_value(payable, Attrs, false), #{ contract_name => Main, state_type => StateType, event_type => EventType, + payable => Payable, functions => add_init_function(Env1, add_event_function(Env1, EventType, Funs)) }; to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) -> @@ -1304,7 +1307,9 @@ field_value({field_t, _, {id, _, X}, _}, Fields) -> %% -- Attributes -- get_attributes(Ann) -> - [stateful || proplists:get_value(stateful, Ann, false)]. + [stateful || proplists:get_value(stateful, Ann, false)] ++ + [payable || proplists:get_value(payable, Ann, false)] ++ + [private || not proplists:get_value(entrypoint, Ann, false)]. %% -- Basic utilities -- diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 82fee1f..184275b 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -17,11 +17,15 @@ -spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode(). convert_typed(TypedTree, Options) -> - Name = case lists:last(TypedTree) of - {contract, _, {con, _, Con}, _} -> Con; - _ -> gen_error(last_declaration_must_be_contract) + {Payable, Name} = + case lists:last(TypedTree) of + {contract, Attrs, {con, _, Con}, _} -> + {proplists:get_value(payable, Attrs, false), Con}; + _ -> + gen_error(last_declaration_must_be_contract) end, - NewIcode = aeso_icode:set_name(Name, aeso_icode:new(Options)), + NewIcode = aeso_icode:set_payable(Payable, + aeso_icode:set_name(Name, aeso_icode:new(Options))), Icode = code(TypedTree, NewIcode, Options), deadcode_elimination(Icode). @@ -87,6 +91,7 @@ contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest], contract_to_icode(Rest, Icode2); contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) -> FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++ + [ payable || proplists:get_value(payable, Attrib, false) ] ++ [ private || is_private(Attrib, Icode) ], %% TODO: Handle types FunName = ast_id(Name), @@ -428,6 +433,9 @@ ast_body(?qid_app(["Address", "is_oracle"], [Addr], _, _), Icode) -> ast_body(?qid_app(["Address", "is_contract"], [Addr], _, _), Icode) -> prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0}, [ast_body(Addr, Icode)], [word], word); +ast_body(?qid_app(["Address", "is_payable"], [Addr], _, _), Icode) -> + prim_call(?PRIM_CALL_ADDR_IS_PAYABLE, #integer{value = 0}, + [ast_body(Addr, Icode)], [word], word); ast_body(?qid_app(["Bytes", "to_int"], [Bytes], _, _), Icode) -> {typed, _, _, {bytes_t, _, N}} = Bytes, diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 9f54d87..fff9870 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -123,7 +123,8 @@ from_string1(aevm, ContractString, Options) -> compiler_version => Version, contract_source => ContractString, type_info => TypeInfo, - abi_version => aeb_aevm_abi:abi_version() + abi_version => aeb_aevm_abi:abi_version(), + payable => maps:get(payable, Icode) }}; from_string1(fate, ContractString, Options) -> #{fcode := FCode} = string_to_code(ContractString, Options), @@ -135,7 +136,8 @@ from_string1(fate, ContractString, Options) -> contract_source => ContractString, type_info => [], fate_code => FateCode, - abi_version => aeb_fate_abi:abi_version() + abi_version => aeb_fate_abi:abi_version(), + payable => maps:get(payable, FCode) }}. -spec string_to_code(string(), options()) -> map(). @@ -549,8 +551,9 @@ to_bytecode([], _) -> []. extract_type_info(#{functions := Functions} =_Icode) -> ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end, + Payable = fun(Attrs) -> proplists:get_value(payable, Attrs, false) end, TypeInfo = [aeb_aevm_abi:function_type_info(list_to_binary(lists:last(Name)), - ArgTypesOnly(Args), TypeRep) + Payable(Attrs), ArgTypesOnly(Args), TypeRep) || {Name, Attrs, Args,_Body, TypeRep} <- Functions, not is_tuple(Name), not lists:member(private, Attrs) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index d7a88e3..e735678 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -109,6 +109,7 @@ Op =:= 'ORACLE_CHECK_QUERY' orelse Op =:= 'IS_ORACLE' orelse Op =:= 'IS_CONTRACT' orelse + Op =:= 'IS_PAYABLE' orelse Op =:= 'CREATOR' orelse false)). @@ -148,15 +149,17 @@ make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, functions_to_scode(ContractName, Functions, Options) -> FunNames = maps:keys(Functions), maps:from_list( - [ {make_function_name(Name), function_to_scode(ContractName, FunNames, Name, Args, Body, Type, Options)} + [ {make_function_name(Name), function_to_scode(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, Args, Body, ResType, _Options) -> +function_to_scode(ContractName, Functions, _Name, Attrs0, Args, Body, ResType, _Options) -> {ArgTypes, ResType1} = typesig_to_scode(Args, ResType), - SCode = to_scode(init_env(ContractName, Functions, Args), Body), - {{ArgTypes, ResType1}, SCode}. + Attrs = Attrs0 -- [stateful], %% Only track private and payable from here. + SCode = to_scode(init_env(ContractName, Functions, Args), Body), + {Attrs, {ArgTypes, ResType1}, SCode}. -define(tvars, '$tvars'). @@ -204,7 +207,7 @@ add_default_init_function(SFuns, {tuple, []}) -> error -> Sig = {[], {tuple, []}}, Body = [tuple(0)], - SFuns#{ InitName => {Sig, Body} } + SFuns#{ InitName => {[], Sig, Body} } end. %% -- Phase I ---------------------------------------------------------------- @@ -541,6 +544,8 @@ builtin_to_scode(Env, address_is_oracle, [_] = Args) -> call_to_scode(Env, aeb_fate_ops:is_oracle(?a, ?a), Args); builtin_to_scode(Env, address_is_contract, [_] = Args) -> call_to_scode(Env, aeb_fate_ops:is_contract(?a, ?a), Args); +builtin_to_scode(Env, address_is_payable, [_] = Args) -> + call_to_scode(Env, aeb_fate_ops:is_payable(?a, ?a), Args); builtin_to_scode(Env, aens_resolve, [_Name, _Key, _Type] = Args) -> call_to_scode(Env, aeb_fate_ops:aens_resolve(?a, ?a, ?a, ?a), Args); builtin_to_scode(Env, aens_preclaim, [_Sign, _Account, _Hash] = Args) -> @@ -628,12 +633,12 @@ flatten_s(I) -> I. -define(MAX_SIMPL_ITERATIONS, 10). -optimize_fun(_Funs, Name, {{Args, Res}, Code}, Options) -> +optimize_fun(_Funs, Name, {Attrs, Sig, Code}, Options) -> Code0 = flatten(Code), debug(opt, Options, "Optimizing ~s\n", [Name]), Code1 = simpl_loop(0, Code0, Options), Code2 = desugar(Code1), - {{Args, Res}, Code2}. + {Attrs, Sig, Code2}. simpl_loop(N, Code, Options) when N >= ?MAX_SIMPL_ITERATIONS -> debug(opt, Options, " No simpl_loop fixed_point after ~p iterations.\n\n", [N]), @@ -834,6 +839,7 @@ attributes(I) -> {'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Impure(A, [B, C, D, E]); {'IS_ORACLE', A, B} -> Impure(A, [B]); {'IS_CONTRACT', A, B} -> Impure(A, [B]); + {'IS_PAYABLE', A, B} -> Impure(A, [B]); {'CREATOR', A} -> Pure(A, []); {'ADDRESS', A} -> Pure(A, []); {'BALANCE', A} -> Impure(A, []); @@ -1282,9 +1288,9 @@ desugar(I) -> [I]. to_basic_blocks(Funs) -> to_basic_blocks(maps:to_list(Funs), aeb_fate_code:new()). -to_basic_blocks([{Name, {Sig, Code}}|Left], Acc) -> +to_basic_blocks([{Name, {Attrs, Sig, Code}}|Left], Acc) -> BB = bb(Name, Code ++ [aeb_fate_ops:return()]), - to_basic_blocks(Left, aeb_fate_code:insert_fun(Name, Sig, BB, Acc)); + to_basic_blocks(Left, aeb_fate_code:insert_fun(Name, Attrs, Sig, BB, Acc)); to_basic_blocks([], Acc) -> Acc. diff --git a/src/aeso_icode.erl b/src/aeso_icode.erl index c589152..22d2d44 100644 --- a/src/aeso_icode.erl +++ b/src/aeso_icode.erl @@ -13,6 +13,7 @@ pp/1, set_name/2, set_namespace/2, + set_payable/2, enter_namespace/2, get_namespace/1, qualify/2, @@ -48,6 +49,7 @@ , type_vars => #{ string() => aeb_aevm_data:type() } , constructors => #{ [string()] => integer() } %% name to tag , options => [any()] + , payable => boolean() }. pp(Icode) -> @@ -65,7 +67,8 @@ new(Options) -> , types => builtin_types() , type_vars => #{} , constructors => builtin_constructors() - , options => Options}. + , options => Options + , payable => false }. builtin_types() -> Word = fun([]) -> word end, @@ -103,6 +106,10 @@ new_env() -> set_name(Name, Icode) -> maps:put(contract_name, Name, Icode). +-spec set_payable(boolean(), icode()) -> icode(). +set_payable(Payable, Icode) -> + maps:put(payable, Payable, Icode). + -spec set_namespace(aeso_syntax:con() | aeso_syntax:qcon(), icode()) -> icode(). set_namespace(NS, Icode) -> Icode#{ namespace => NS }. diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 1dd0ca0..c53712e 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -57,6 +57,7 @@ decl() -> 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(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4}) , ?RULE(keyword(include), str(), {include, get_ann(_1), _2}) @@ -81,7 +82,7 @@ fun_or_entry() -> ?RULE(keyword(entrypoint), {entrypoint, _1})]). modifiers() -> - many(choice([token(stateful), token(private), token(public)])). + many(choice([token(stateful), token(payable), token(private), token(public)])). add_modifiers(Mods, Entry = {entrypoint, _}, Node) -> add_modifiers(Mods ++ [Entry], Node); diff --git a/src/aeso_scan.erl b/src/aeso_scan.erl index 400a5cf..6553dd5 100644 --- a/src/aeso_scan.erl +++ b/src/aeso_scan.erl @@ -37,7 +37,7 @@ lexer() -> , {"[^/*]+|[/*]", skip()} ], Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function", - "stateful", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace"], + "stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace"], KW = string:join(Keywords, "|"), Rules = diff --git a/test/aeso_aci_tests.erl b/test/aeso_aci_tests.erl index 6f348fd..18cb499 100644 --- a/test/aeso_aci_tests.erl +++ b/test/aeso_aci_tests.erl @@ -14,20 +14,22 @@ test_contract(N) -> ?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)). test_cases(1) -> - Contract = <<"contract C =\n" - " entrypoint a(i : int) = i+1\n">>, + Contract = <<"payable contract C =\n" + " payable stateful entrypoint a(i : int) = i+1\n">>, MapACI = #{contract => #{name => <<"C">>, type_defs => [], + payable => true, functions => [#{name => <<"a">>, arguments => [#{name => <<"i">>, type => <<"int">>}], returns => <<"int">>, - stateful => false}]}}, - DecACI = <<"contract C =\n" - " entrypoint a : (int) => int\n">>, + stateful => true, + payable => true}]}}, + DecACI = <<"payable contract C =\n" + " payable entrypoint a : (int) => int\n">>, {Contract,MapACI,DecACI}; test_cases(2) -> @@ -35,7 +37,7 @@ test_cases(2) -> " type allan = int\n" " entrypoint a(i : allan) = i+1\n">>, MapACI = #{contract => - #{name => <<"C">>, + #{name => <<"C">>, payable => false, type_defs => [#{name => <<"allan">>, typedef => <<"int">>, @@ -46,7 +48,8 @@ test_cases(2) -> type => <<"C.allan">>}], name => <<"a">>, returns => <<"int">>, - stateful => false}]}}, + stateful => false, + payable => false}]}}, DecACI = <<"contract C =\n" " type allan = int\n" " entrypoint a : (C.allan) => int\n">>, @@ -64,8 +67,8 @@ test_cases(3) -> type => #{<<"C.bert">> => [<<"string">>]}}], name => <<"a">>,returns => <<"int">>, - stateful => false}], - name => <<"C">>, + stateful => false, payable => false}], + name => <<"C">>, payable => false, event => #{variant => [#{<<"SingleEventDefined">> => []}]}, state => <<"unit">>, type_defs => diff --git a/test/aeso_calldata_tests.erl b/test/aeso_calldata_tests.erl index 7fab206..579d6ea 100644 --- a/test/aeso_calldata_tests.erl +++ b/test/aeso_calldata_tests.erl @@ -123,7 +123,8 @@ compilable_contracts() -> {"bitcoin_auth", "authorize", ["1", "#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f202122232425262728293031323334353637"]}, {"bitcoin_auth", "to_sign", ["#0102030405060708090a0b0c0d0e0f1017181920212223242526272829303132", "2"]}, {"stub", "foo", ["42"]}, - {"stub", "foo", ["-42"]} + {"stub", "foo", ["-42"]}, + {"payable", "foo", ["42"]} ]. not_yet_compilable(fate) -> diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 47f0efb..f3e3b67 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -125,7 +125,8 @@ compilable_contracts() -> "stdlib_include", "double_include", "manual_stdlib_include", - "list_comp" + "list_comp", + "payable" ]. not_yet_compilable(fate) -> []; diff --git a/test/contracts/address_chain.aes b/test/contracts/address_chain.aes index e8edccf..4c55a45 100644 --- a/test/contracts/address_chain.aes +++ b/test/contracts/address_chain.aes @@ -31,3 +31,6 @@ contract AddrChain = entrypoint c_creator() : address = Contract.creator + + entrypoint is_payable(a : address) : bool = + Address.is_payable(a) diff --git a/test/contracts/payable.aes b/test/contracts/payable.aes new file mode 100644 index 0000000..d2f3efa --- /dev/null +++ b/test/contracts/payable.aes @@ -0,0 +1,3 @@ +payable contract Test = + payable entrypoint foo(x : int) = () + function bar() = 42