Write negative tests
This commit is contained in:
parent
0105140878
commit
3ea2de8dbe
@ -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
|
- `main` keyword to indicate the main contract in case there are child contracts around
|
||||||
- `List.sum` and `List.product` no longer use `List.foldl`
|
- `List.sum` and `List.product` no longer use `List.foldl`
|
||||||
### Removed
|
### Removed
|
||||||
- Singleton tuples are no longer unwrapped (this was causing problems with arity 1 init functions)
|
|
||||||
|
|
||||||
## [5.0.0] 2021-04-30
|
## [5.0.0] 2021-04-30
|
||||||
### Added
|
### Added
|
||||||
|
@ -78,7 +78,8 @@
|
|||||||
context :: {contract_literal, aeso_syntax:expr()} |
|
context :: {contract_literal, aeso_syntax:expr()} |
|
||||||
{address_to_contract, aeso_syntax:ann()} |
|
{address_to_contract, aeso_syntax:ann()} |
|
||||||
{bytecode_hash, 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{}.
|
-type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}.
|
||||||
@ -754,10 +755,11 @@ infer(Contracts, Options) ->
|
|||||||
try
|
try
|
||||||
Env = init_env(Options),
|
Env = init_env(Options),
|
||||||
create_options(Options),
|
create_options(Options),
|
||||||
|
ets_new(defined_contracts, [bag]),
|
||||||
ets_new(type_vars, [set]),
|
ets_new(type_vars, [set]),
|
||||||
check_modifiers(Env, Contracts),
|
check_modifiers(Env, Contracts),
|
||||||
create_type_errors(),
|
create_type_errors(),
|
||||||
Contracts1 = identify_main_contract(Contracts),
|
Contracts1 = identify_main_contract(Contracts, Options),
|
||||||
destroy_and_report_type_errors(Env),
|
destroy_and_report_type_errors(Env),
|
||||||
{Env1, Decls} = infer1(Env, Contracts1, [], Options),
|
{Env1, Decls} = infer1(Env, Contracts1, [], Options),
|
||||||
{Env2, DeclsFolded, DeclsUnfolded} =
|
{Env2, DeclsFolded, DeclsUnfolded} =
|
||||||
@ -786,6 +788,10 @@ infer1(Env, [{Contract, Ann, ConName, Code} | Rest], Acc, Options)
|
|||||||
contract_child -> contract;
|
contract_child -> contract;
|
||||||
contract_interface -> contract_interface
|
contract_interface -> contract_interface
|
||||||
end,
|
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),
|
{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),
|
Env2 = pop_scope(Env1),
|
||||||
@ -801,18 +807,22 @@ infer1(Env, [{pragma, _, _} | Rest], Acc, Options) ->
|
|||||||
infer1(Env, Rest, Acc, Options).
|
infer1(Env, Rest, Acc, Options).
|
||||||
|
|
||||||
%% Asserts that the main contract is somehow defined.
|
%% Asserts that the main contract is somehow defined.
|
||||||
identify_main_contract(Contracts) ->
|
identify_main_contract(Contracts, Options) ->
|
||||||
Children = [C || C = {contract_child, _, _, _} <- Contracts],
|
Children = [C || C = {contract_child, _, _, _} <- Contracts],
|
||||||
Mains = [C || C = {contract_main, _, _, _} <- Contracts],
|
Mains = [C || C = {contract_main, _, _, _} <- Contracts],
|
||||||
case Mains of
|
case Mains of
|
||||||
[] -> case Children 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}] ->
|
[{contract_child, Ann, Con, Body}] ->
|
||||||
(Contracts -- Children) ++ [{contract_main, 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;
|
end;
|
||||||
[_] -> (Contracts -- Mains) ++ Mains; %% Move to the 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.
|
end.
|
||||||
|
|
||||||
check_scope_name_clash(Env, Kind, Name) ->
|
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
|
% generally type error, but will be caught
|
||||||
_ -> [GasCapMock, ProtectedMock|NamedArgsT]
|
_ -> [GasCapMock, ProtectedMock|NamedArgsT]
|
||||||
end,
|
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};
|
{fun_t, Ann, NamedArgsT, ArgTypes, RetT};
|
||||||
{qid, _, ["Chain", "clone"]} ->
|
{qid, _, ["Chain", "clone"]} ->
|
||||||
{fun_t, _, NamedArgsT, var_args, RetT} = FunType0,
|
{fun_t, _, NamedArgsT, var_args, RetT} = FunType0,
|
||||||
@ -1639,15 +1649,15 @@ infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) ->
|
|||||||
end,
|
end,
|
||||||
NamedArgsTNoRef =
|
NamedArgsTNoRef =
|
||||||
lists:filter(fun({named_arg_t, _, {id, _, "ref"}, _, _}) -> false; (_) -> true end, NamedArgsT),
|
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,
|
{fun_t, Ann, NamedArgsT, ArgTypes,
|
||||||
{if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}};
|
{if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}};
|
||||||
_ -> FunType0
|
_ -> FunType0
|
||||||
end,
|
end,
|
||||||
{typed, Ann, Fun, FunType}.
|
{typed, Ann, Fun, FunType}.
|
||||||
|
|
||||||
-spec check_contract_construction(env(), utype(), utype(), named_args_t(), [utype()], utype()) -> ok.
|
-spec check_contract_construction(env(), boolean(), utype(), utype(), named_args_t(), [utype()], utype()) -> ok.
|
||||||
check_contract_construction(Env, ContractT, Fun, NamedArgsT, ArgTypes, RetT) ->
|
check_contract_construction(Env, ForceDef, ContractT, Fun, NamedArgsT, ArgTypes, RetT) ->
|
||||||
Ann = aeso_syntax:get_ann(Fun),
|
Ann = aeso_syntax:get_ann(Fun),
|
||||||
InitT = fresh_uvar(Ann),
|
InitT = fresh_uvar(Ann),
|
||||||
unify(Env, InitT, {fun_t, Ann, NamedArgsT, ArgTypes, fresh_uvar(Ann)}, {checking_init_args, Ann, ContractT, ArgTypes}),
|
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,
|
kind = project,
|
||||||
context = {var_args, Ann, Fun} }
|
context = {var_args, Ann, Fun} }
|
||||||
, #is_contract_constraint{ contract_t = ContractT,
|
, #is_contract_constraint{ contract_t = ContractT,
|
||||||
context = {var_args, Ann, Fun} }
|
context = {var_args, Ann, Fun},
|
||||||
|
force_def = ForceDef
|
||||||
|
}
|
||||||
]),
|
]),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
@ -1836,7 +1848,7 @@ next_count() ->
|
|||||||
|
|
||||||
ets_tables() ->
|
ets_tables() ->
|
||||||
[options, type_vars, type_defs, record_fields, named_argument_constraints,
|
[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() ->
|
clean_up_ets() ->
|
||||||
[ catch ets_delete(Tab) || Tab <- ets_tables() ],
|
[ catch ets_delete(Tab) || Tab <- ets_tables() ],
|
||||||
@ -2110,12 +2122,20 @@ check_record_create_constraints(Env, [C | Cs]) ->
|
|||||||
end,
|
end,
|
||||||
check_record_create_constraints(Env, Cs).
|
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, []) -> ok;
|
||||||
check_is_contract_constraints(Env, [C | Cs]) ->
|
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)),
|
Type1 = unfold_types_in_type(Env, instantiate(Type)),
|
||||||
case lookup_type(Env, record_type_name(Type1)) of
|
TypeName = record_type_name(Type1),
|
||||||
{_, {_Ann, {[], {contract_t, _}}}} -> ok;
|
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})
|
_ -> type_error({not_a_contract_type, Type1, Cxt})
|
||||||
end,
|
end,
|
||||||
check_is_contract_constraints(Env, Cs).
|
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}) ->
|
mk_error({conflicting_updates_for_field, Upd, Key}) ->
|
||||||
Msg = io_lib:format("Conflicting updates for field '~s'\n", [Key]),
|
Msg = io_lib:format("Conflicting updates for field '~s'\n", [Key]),
|
||||||
mk_t_err(pos(Upd), Msg);
|
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.",
|
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_t_err(pos(Ann), Msg);
|
||||||
mk_error({main_contract_undefined}) ->
|
mk_error({main_contract_undefined, Ann}) ->
|
||||||
Msg = "No contract defined.\n",
|
Msg = "No contract defined.\n",
|
||||||
mk_t_err(pos(0, 0), Msg);
|
mk_t_err(pos(Ann), Msg);
|
||||||
mk_error({multiple_main_contracts}) ->
|
mk_error({multiple_main_contracts, Ann}) ->
|
||||||
Msg = "Only one main contract can be defined.\n",
|
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}) ->
|
mk_error({unify_varargs, When}) ->
|
||||||
Msg = "Cannot unify variable argument list.\n",
|
Msg = "Cannot unify variable argument list.\n",
|
||||||
{Pos, Ctxt} = pp_when(When),
|
{Pos, Ctxt} = pp_when(When),
|
||||||
@ -2959,6 +2979,13 @@ mk_error({unify_varargs, When}) ->
|
|||||||
mk_error({clone_no_contract, Ann}) ->
|
mk_error({clone_no_contract, Ann}) ->
|
||||||
Msg = "Chain.clone requires `ref` named argument of contract type.\n",
|
Msg = "Chain.clone requires `ref` named argument of contract type.\n",
|
||||||
mk_t_err(pos(Ann), Msg);
|
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) ->
|
mk_error(Err) ->
|
||||||
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
|
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
|
||||||
mk_t_err(pos(0, 0), Msg).
|
mk_t_err(pos(0, 0), Msg).
|
||||||
|
@ -34,7 +34,7 @@ simple_compile_test_() ->
|
|||||||
#{fate_code := Code} when Backend == fate ->
|
#{fate_code := Code} when Backend == fate ->
|
||||||
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
|
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
|
||||||
?assertMatch({X, X}, {Code1, 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
|
||||||
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
|
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
|
||||||
not lists:member(ContractName, not_compilable_on(Backend))] ++
|
not lists:member(ContractName, not_compilable_on(Backend))] ++
|
||||||
@ -126,8 +126,7 @@ compile(Backend, Name, Options) ->
|
|||||||
%% The currently compilable contracts.
|
%% The currently compilable contracts.
|
||||||
|
|
||||||
compilable_contracts() ->
|
compilable_contracts() ->
|
||||||
["test",
|
["complex_types",
|
||||||
"complex_types",
|
|
||||||
"counter",
|
"counter",
|
||||||
"dutch_auction",
|
"dutch_auction",
|
||||||
"environment",
|
"environment",
|
||||||
@ -180,7 +179,10 @@ compilable_contracts() ->
|
|||||||
"protected_call",
|
"protected_call",
|
||||||
"hermetization_turnoff",
|
"hermetization_turnoff",
|
||||||
"multiple_contracts",
|
"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) -> [];
|
not_compilable_on(fate) -> [];
|
||||||
@ -728,6 +730,39 @@ failing_contracts() ->
|
|||||||
, ?TYPE_ERROR(bad_state,
|
, ?TYPE_ERROR(bad_state,
|
||||||
[<<?Pos(4, 16)
|
[<<?Pos(4, 16)
|
||||||
"Conflicting updates for field 'foo'">>])
|
"Conflicting updates for field 'foo'">>])
|
||||||
|
, ?TYPE_ERROR(factories_type_errors,
|
||||||
|
[<<?Pos(10,18)
|
||||||
|
"Chain.clone requires `ref` named argument of contract type.">>,
|
||||||
|
<<?Pos(11,18)
|
||||||
|
"Cannot unify (gas : int, value : int, protected : bool) => 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">>,
|
||||||
|
<<?Pos(12,37)
|
||||||
|
"Cannot unify int\n and bool\n"
|
||||||
|
"when checking named argument\n gas : int\nagainst inferred type\n bool">>,
|
||||||
|
<<?Pos(13,18),
|
||||||
|
"Kaboom is not implemented.\n"
|
||||||
|
"when resolving arguments of variadic function\n Chain.create">>,
|
||||||
|
<<?Pos(18,18)
|
||||||
|
"Cannot unify (gas : int, value : int, protected : bool, int, bool) => 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">>,
|
||||||
|
<<?Pos(19,42),
|
||||||
|
"Named argument protected (at line 19, column 42) is not one of the expected named arguments\n - value : int">>,
|
||||||
|
<<?Pos(20,42),
|
||||||
|
"Cannot unify int\n and bool\n"
|
||||||
|
"when checking named argument\n value : int\nagainst inferred type\n bool">>
|
||||||
|
])
|
||||||
|
, ?TYPE_ERROR(ambiguous_main,
|
||||||
|
[<<?Pos(1,1)
|
||||||
|
"Could not deduce the main contract. You can point it out manually with the `main` keyword.">>
|
||||||
|
])
|
||||||
|
, ?TYPE_ERROR(no_main_contract,
|
||||||
|
[<<?Pos(0,0)
|
||||||
|
"No contract defined.">>
|
||||||
|
])
|
||||||
|
, ?TYPE_ERROR(multiple_main_contracts,
|
||||||
|
[<<?Pos(1,6)
|
||||||
|
"Only one main contract can be defined.">>
|
||||||
|
])
|
||||||
].
|
].
|
||||||
|
|
||||||
-define(Path(File), "code_errors/" ??File).
|
-define(Path(File), "code_errors/" ??File).
|
||||||
@ -839,6 +874,8 @@ failing_code_gen_contracts() ->
|
|||||||
"Invalid state type\n"
|
"Invalid state type\n"
|
||||||
" {f : (int) => int}\n"
|
" {f : (int) => int}\n"
|
||||||
"The state cannot contain functions in the AEVM. Use FATE if you need this.")
|
"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_() ->
|
validation_test_() ->
|
||||||
|
5
test/contracts/ambiguous_main.aes
Normal file
5
test/contracts/ambiguous_main.aes
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
contract C =
|
||||||
|
entrypoint f() = 123
|
||||||
|
|
||||||
|
contract D =
|
||||||
|
entrypoint f() = 123
|
5
test/contracts/code_errors/child_with_decls.aes
Normal file
5
test/contracts/code_errors/child_with_decls.aes
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
contract C =
|
||||||
|
entrypoint f : () => unit
|
||||||
|
|
||||||
|
main contract M =
|
||||||
|
entrypoint f() = 123
|
24
test/contracts/factories_type_errors.aes
Normal file
24
test/contracts/factories_type_errors.aes
Normal file
@ -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
|
5
test/contracts/multiple_main_contracts.aes
Normal file
5
test/contracts/multiple_main_contracts.aes
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
main contract C =
|
||||||
|
entrypoint f() = 123
|
||||||
|
|
||||||
|
main contract D =
|
||||||
|
entrypoint f() = 123
|
2
test/contracts/no_main_contract.aes
Normal file
2
test/contracts/no_main_contract.aes
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
contract interface C =
|
||||||
|
entrypoint f : () => unit
|
Loading…
x
Reference in New Issue
Block a user