Add payable modifier for contracts and entrypoints
This commit is contained in:
parent
e566186800
commit
f27d37d624
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
-type option() :: term().
|
-type option() :: term().
|
||||||
|
|
||||||
-type attribute() :: stateful | pure | private.
|
-type attribute() :: stateful | payable | pure | private.
|
||||||
|
|
||||||
-type fun_name() :: {entrypoint, binary()}
|
-type fun_name() :: {entrypoint, binary()}
|
||||||
| {local_fun, [string()]}
|
| {local_fun, [string()]}
|
||||||
@ -109,7 +109,8 @@
|
|||||||
-type fcode() :: #{ contract_name := string(),
|
-type fcode() :: #{ contract_name := string(),
|
||||||
state_type := ftype(),
|
state_type := ftype(),
|
||||||
event_type := ftype() | none,
|
event_type := ftype() | none,
|
||||||
functions := #{ fun_name() => fun_def() } }.
|
functions := #{ fun_name() => fun_def() },
|
||||||
|
payable := boolean() }.
|
||||||
|
|
||||||
-type type_def() :: fun(([ftype()]) -> ftype()).
|
-type type_def() :: fun(([ftype()]) -> ftype()).
|
||||||
|
|
||||||
@ -227,7 +228,7 @@ init_type_env() ->
|
|||||||
%% -- Compilation ------------------------------------------------------------
|
%% -- Compilation ------------------------------------------------------------
|
||||||
|
|
||||||
-spec to_fcode(env(), aeso_syntax:ast()) -> fcode().
|
-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,
|
#{ builtins := Builtins } = Env,
|
||||||
MainEnv = Env#{ context => {main_contract, Main},
|
MainEnv = Env#{ context => {main_contract, Main},
|
||||||
builtins => Builtins#{[Main, "state"] => {get_state, none},
|
builtins => Builtins#{[Main, "state"] => {get_state, none},
|
||||||
@ -237,9 +238,11 @@ to_fcode(Env, [{contract, _, {con, _, Main}, Decls}]) ->
|
|||||||
decls_to_fcode(MainEnv, Decls),
|
decls_to_fcode(MainEnv, Decls),
|
||||||
StateType = lookup_type(Env1, [Main, "state"], [], {tuple, []}),
|
StateType = lookup_type(Env1, [Main, "state"], [], {tuple, []}),
|
||||||
EventType = lookup_type(Env1, [Main, "event"], [], none),
|
EventType = lookup_type(Env1, [Main, "event"], [], none),
|
||||||
|
Payable = proplists:get_value(payable, Attrs, false),
|
||||||
#{ contract_name => Main,
|
#{ contract_name => Main,
|
||||||
state_type => StateType,
|
state_type => StateType,
|
||||||
event_type => EventType,
|
event_type => EventType,
|
||||||
|
payable => Payable,
|
||||||
functions => add_init_function(Env1,
|
functions => add_init_function(Env1,
|
||||||
add_event_function(Env1, EventType, Funs)) };
|
add_event_function(Env1, EventType, Funs)) };
|
||||||
to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) ->
|
to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) ->
|
||||||
@ -1304,7 +1307,9 @@ field_value({field_t, _, {id, _, X}, _}, Fields) ->
|
|||||||
%% -- Attributes --
|
%% -- Attributes --
|
||||||
|
|
||||||
get_attributes(Ann) ->
|
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 --
|
%% -- Basic utilities --
|
||||||
|
|
||||||
|
@ -17,11 +17,15 @@
|
|||||||
|
|
||||||
-spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode().
|
-spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode().
|
||||||
convert_typed(TypedTree, Options) ->
|
convert_typed(TypedTree, Options) ->
|
||||||
Name = case lists:last(TypedTree) of
|
{Payable, Name} =
|
||||||
{contract, _, {con, _, Con}, _} -> Con;
|
case lists:last(TypedTree) of
|
||||||
_ -> gen_error(last_declaration_must_be_contract)
|
{contract, Attrs, {con, _, Con}, _} ->
|
||||||
|
{proplists:get_value(payable, Attrs, false), Con};
|
||||||
|
_ ->
|
||||||
|
gen_error(last_declaration_must_be_contract)
|
||||||
end,
|
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),
|
Icode = code(TypedTree, NewIcode, Options),
|
||||||
deadcode_elimination(Icode).
|
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(Rest, Icode2);
|
||||||
contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) ->
|
contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) ->
|
||||||
FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++
|
FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++
|
||||||
|
[ payable || proplists:get_value(payable, Attrib, false) ] ++
|
||||||
[ private || is_private(Attrib, Icode) ],
|
[ private || is_private(Attrib, Icode) ],
|
||||||
%% TODO: Handle types
|
%% TODO: Handle types
|
||||||
FunName = ast_id(Name),
|
FunName = ast_id(Name),
|
||||||
|
@ -123,7 +123,8 @@ from_string1(aevm, ContractString, Options) ->
|
|||||||
compiler_version => Version,
|
compiler_version => Version,
|
||||||
contract_source => ContractString,
|
contract_source => ContractString,
|
||||||
type_info => TypeInfo,
|
type_info => TypeInfo,
|
||||||
abi_version => aeb_aevm_abi:abi_version()
|
abi_version => aeb_aevm_abi:abi_version(),
|
||||||
|
payable => maps:get(payable, Icode)
|
||||||
}};
|
}};
|
||||||
from_string1(fate, ContractString, Options) ->
|
from_string1(fate, ContractString, Options) ->
|
||||||
#{fcode := FCode} = string_to_code(ContractString, Options),
|
#{fcode := FCode} = string_to_code(ContractString, Options),
|
||||||
@ -135,7 +136,8 @@ from_string1(fate, ContractString, Options) ->
|
|||||||
contract_source => ContractString,
|
contract_source => ContractString,
|
||||||
type_info => [],
|
type_info => [],
|
||||||
fate_code => FateCode,
|
fate_code => FateCode,
|
||||||
abi_version => aeb_fate_abi:abi_version()
|
abi_version => aeb_fate_abi:abi_version(),
|
||||||
|
payable => maps:get(payable, FCode)
|
||||||
}}.
|
}}.
|
||||||
|
|
||||||
-spec string_to_code(string(), options()) -> map().
|
-spec string_to_code(string(), options()) -> map().
|
||||||
@ -549,8 +551,9 @@ to_bytecode([], _) -> [].
|
|||||||
|
|
||||||
extract_type_info(#{functions := Functions} =_Icode) ->
|
extract_type_info(#{functions := Functions} =_Icode) ->
|
||||||
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
|
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)),
|
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,
|
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
|
||||||
not is_tuple(Name),
|
not is_tuple(Name),
|
||||||
not lists:member(private, Attrs)
|
not lists:member(private, Attrs)
|
||||||
|
@ -148,15 +148,17 @@ make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs,
|
|||||||
functions_to_scode(ContractName, Functions, Options) ->
|
functions_to_scode(ContractName, Functions, Options) ->
|
||||||
FunNames = maps:keys(Functions),
|
FunNames = maps:keys(Functions),
|
||||||
maps:from_list(
|
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,
|
|| {Name, #{args := Args,
|
||||||
body := Body,
|
body := Body,
|
||||||
|
attrs := Attrs,
|
||||||
return := Type}} <- maps:to_list(Functions)]).
|
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),
|
{ArgTypes, ResType1} = typesig_to_scode(Args, ResType),
|
||||||
SCode = to_scode(init_env(ContractName, Functions, Args), Body),
|
Attrs = Attrs0 -- [stateful], %% Only track private and payable from here.
|
||||||
{{ArgTypes, ResType1}, SCode}.
|
SCode = to_scode(init_env(ContractName, Functions, Args), Body),
|
||||||
|
{Attrs, {ArgTypes, ResType1}, SCode}.
|
||||||
|
|
||||||
-define(tvars, '$tvars').
|
-define(tvars, '$tvars').
|
||||||
|
|
||||||
@ -204,7 +206,7 @@ add_default_init_function(SFuns, {tuple, []}) ->
|
|||||||
error ->
|
error ->
|
||||||
Sig = {[], {tuple, []}},
|
Sig = {[], {tuple, []}},
|
||||||
Body = [tuple(0)],
|
Body = [tuple(0)],
|
||||||
SFuns#{ InitName => {Sig, Body} }
|
SFuns#{ InitName => {[], Sig, Body} }
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% -- Phase I ----------------------------------------------------------------
|
%% -- Phase I ----------------------------------------------------------------
|
||||||
@ -628,12 +630,12 @@ flatten_s(I) -> I.
|
|||||||
|
|
||||||
-define(MAX_SIMPL_ITERATIONS, 10).
|
-define(MAX_SIMPL_ITERATIONS, 10).
|
||||||
|
|
||||||
optimize_fun(_Funs, Name, {{Args, Res}, Code}, Options) ->
|
optimize_fun(_Funs, Name, {Attrs, Sig, Code}, Options) ->
|
||||||
Code0 = flatten(Code),
|
Code0 = flatten(Code),
|
||||||
debug(opt, Options, "Optimizing ~s\n", [Name]),
|
debug(opt, Options, "Optimizing ~s\n", [Name]),
|
||||||
Code1 = simpl_loop(0, Code0, Options),
|
Code1 = simpl_loop(0, Code0, Options),
|
||||||
Code2 = desugar(Code1),
|
Code2 = desugar(Code1),
|
||||||
{{Args, Res}, Code2}.
|
{Attrs, Sig, Code2}.
|
||||||
|
|
||||||
simpl_loop(N, Code, Options) when N >= ?MAX_SIMPL_ITERATIONS ->
|
simpl_loop(N, Code, Options) when N >= ?MAX_SIMPL_ITERATIONS ->
|
||||||
debug(opt, Options, " No simpl_loop fixed_point after ~p iterations.\n\n", [N]),
|
debug(opt, Options, " No simpl_loop fixed_point after ~p iterations.\n\n", [N]),
|
||||||
@ -1282,9 +1284,9 @@ desugar(I) -> [I].
|
|||||||
to_basic_blocks(Funs) ->
|
to_basic_blocks(Funs) ->
|
||||||
to_basic_blocks(maps:to_list(Funs), aeb_fate_code:new()).
|
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()]),
|
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) ->
|
to_basic_blocks([], Acc) ->
|
||||||
Acc.
|
Acc.
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
pp/1,
|
pp/1,
|
||||||
set_name/2,
|
set_name/2,
|
||||||
set_namespace/2,
|
set_namespace/2,
|
||||||
|
set_payable/2,
|
||||||
enter_namespace/2,
|
enter_namespace/2,
|
||||||
get_namespace/1,
|
get_namespace/1,
|
||||||
qualify/2,
|
qualify/2,
|
||||||
@ -65,7 +66,8 @@ new(Options) ->
|
|||||||
, types => builtin_types()
|
, types => builtin_types()
|
||||||
, type_vars => #{}
|
, type_vars => #{}
|
||||||
, constructors => builtin_constructors()
|
, constructors => builtin_constructors()
|
||||||
, options => Options}.
|
, options => Options
|
||||||
|
, payable => false }.
|
||||||
|
|
||||||
builtin_types() ->
|
builtin_types() ->
|
||||||
Word = fun([]) -> word end,
|
Word = fun([]) -> word end,
|
||||||
@ -103,6 +105,10 @@ new_env() ->
|
|||||||
set_name(Name, Icode) ->
|
set_name(Name, Icode) ->
|
||||||
maps:put(contract_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().
|
-spec set_namespace(aeso_syntax:con() | aeso_syntax:qcon(), icode()) -> icode().
|
||||||
set_namespace(NS, Icode) -> Icode#{ namespace => NS }.
|
set_namespace(NS, Icode) -> Icode#{ namespace => NS }.
|
||||||
|
|
||||||
|
@ -57,6 +57,7 @@ decl() ->
|
|||||||
choice(
|
choice(
|
||||||
%% Contract declaration
|
%% Contract declaration
|
||||||
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
|
[ ?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(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
|
||||||
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
|
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
|
||||||
|
|
||||||
@ -81,7 +82,7 @@ fun_or_entry() ->
|
|||||||
?RULE(keyword(entrypoint), {entrypoint, _1})]).
|
?RULE(keyword(entrypoint), {entrypoint, _1})]).
|
||||||
|
|
||||||
modifiers() ->
|
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 = {entrypoint, _}, Node) ->
|
||||||
add_modifiers(Mods ++ [Entry], Node);
|
add_modifiers(Mods ++ [Entry], Node);
|
||||||
|
@ -37,7 +37,7 @@ lexer() ->
|
|||||||
, {"[^/*]+|[/*]", skip()} ],
|
, {"[^/*]+|[/*]", skip()} ],
|
||||||
|
|
||||||
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
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, "|"),
|
KW = string:join(Keywords, "|"),
|
||||||
|
|
||||||
Rules =
|
Rules =
|
||||||
|
@ -123,7 +123,8 @@ compilable_contracts() ->
|
|||||||
{"bitcoin_auth", "authorize", ["1", "#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f202122232425262728293031323334353637"]},
|
{"bitcoin_auth", "authorize", ["1", "#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f202122232425262728293031323334353637"]},
|
||||||
{"bitcoin_auth", "to_sign", ["#0102030405060708090a0b0c0d0e0f1017181920212223242526272829303132", "2"]},
|
{"bitcoin_auth", "to_sign", ["#0102030405060708090a0b0c0d0e0f1017181920212223242526272829303132", "2"]},
|
||||||
{"stub", "foo", ["42"]},
|
{"stub", "foo", ["42"]},
|
||||||
{"stub", "foo", ["-42"]}
|
{"stub", "foo", ["-42"]},
|
||||||
|
{"payable", "foo", ["42"]}
|
||||||
].
|
].
|
||||||
|
|
||||||
not_yet_compilable(fate) ->
|
not_yet_compilable(fate) ->
|
||||||
|
@ -125,7 +125,8 @@ compilable_contracts() ->
|
|||||||
"stdlib_include",
|
"stdlib_include",
|
||||||
"double_include",
|
"double_include",
|
||||||
"manual_stdlib_include",
|
"manual_stdlib_include",
|
||||||
"list_comp"
|
"list_comp",
|
||||||
|
"payable"
|
||||||
].
|
].
|
||||||
|
|
||||||
not_yet_compilable(fate) -> [];
|
not_yet_compilable(fate) -> [];
|
||||||
|
3
test/contracts/payable.aes
Normal file
3
test/contracts/payable.aes
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
payable contract Test =
|
||||||
|
payable entrypoint foo(x : int) = ()
|
||||||
|
function bar() = 42
|
Loading…
x
Reference in New Issue
Block a user