Address review, fix some dialyzer errors

This commit is contained in:
radrow 2021-05-12 20:09:52 +02:00
parent 7898d2a17d
commit 6789b739a5
10 changed files with 123 additions and 88 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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, [])

View File

@ -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()}.

View File

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

View File

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

View File

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

View File

@ -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))
main contract Test =
stateful entrypoint f(c) = Chain.clone(ref=c, 123)