Bind state and event primitives only in contracts (and with the right types)

This commit is contained in:
Ulf Norell 2019-02-04 18:28:46 +01:00
parent d9188d58a7
commit e6f01481bf
3 changed files with 42 additions and 25 deletions

View File

@ -150,17 +150,20 @@ check_tvar(#env{ typevars = TVars}, T = {tvar, _, X}) ->
-spec bind_fun(name(), type() | typesig(), env()) -> env(). -spec bind_fun(name(), type() | typesig(), env()) -> env().
bind_fun(X, Type, Env) -> bind_fun(X, Type, Env) ->
Ann = aeso_syntax:get_ann(Type),
case lookup_name(Env, [X]) of case lookup_name(Env, [X]) of
false -> false -> force_bind_fun(X, Type, Env);
on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) ->
Scope#scope{ funs = [{X, {Ann, Type}} | Funs] }
end);
{_QId, {Ann1, _}} -> {_QId, {Ann1, _}} ->
type_error({duplicate_definition, X, [Ann1, Ann]}), type_error({duplicate_definition, X, [Ann1, aeso_syntax:get_ann(Type)]}),
Env Env
end. end.
-spec force_bind_fun(name(), type() | typesig(), env()) -> env().
force_bind_fun(X, Type, Env) ->
Ann = aeso_syntax:get_ann(Type),
on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) ->
Scope#scope{ funs = [{X, {Ann, Type}} | Funs] }
end).
-spec bind_funs([{name(), type() | typesig()}], env()) -> env(). -spec bind_funs([{name(), type() | typesig()}], env()) -> env().
bind_funs([], Env) -> Env; bind_funs([], Env) -> Env;
bind_funs([{Id, Type} | Rest], Env) -> bind_funs([{Id, Type} | Rest], Env) ->
@ -182,8 +185,18 @@ bind_state(Env) ->
{S, _} -> {qid, Ann, S}; {S, _} -> {qid, Ann, S};
false -> Unit false -> Unit
end, end,
bind_funs([{"state", State}, Event =
{"put", {fun_t, Ann, [], [State], Unit}}], Env). case lookup_type(Env, {id, Ann, "event"}) of
{E, _} -> {qid, Ann, E};
false -> {id, Ann, "event"} %% will cause type error if used(?)
end,
Env1 = bind_funs([{"state", State},
{"put", {fun_t, Ann, [], [State], Unit}}], Env),
%% A bit of a hack: we bind Chain.event with the local event type.
Env2 = force_bind_fun("event", {fun_t, Ann, [], [Event], Unit},
Env1#env{ namespace = ["Chain"] }),
Env2#env{ namespace = Env1#env.namespace }.
-spec bind_field(name(), field_info(), env()) -> env(). -spec bind_field(name(), field_info(), env()) -> env().
bind_field(X, Info, Env = #env{ fields = Fields }) -> bind_field(X, Info, Env = #env{ fields = Fields }) ->
@ -325,7 +338,6 @@ global_env() ->
Bool = {id, Ann, "bool"}, Bool = {id, Ann, "bool"},
String = {id, Ann, "string"}, String = {id, Ann, "string"},
Address = {id, Ann, "address"}, Address = {id, Ann, "address"},
Event = {id, Ann, "event"},
Hash = {id, Ann, "hash"}, Hash = {id, Ann, "hash"},
Bits = {id, Ann, "bits"}, Bits = {id, Ann, "bits"},
Oracle = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle"}, [Q, R]} end, Oracle = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle"}, [Q, R]} end,
@ -376,8 +388,7 @@ global_env() ->
{"timestamp", Int}, {"timestamp", Int},
{"block_height", Int}, {"block_height", Int},
{"difficulty", Int}, {"difficulty", Int},
{"gas_limit", Int}, {"gas_limit", Int}])
{"event", Fun1(Event, Unit)}])
, types = MkDefs([{"ttl", 0}]) }, , types = MkDefs([{"ttl", 0}]) },
ContractScope = #scope ContractScope = #scope
@ -558,13 +569,13 @@ infer_contract(Env, What, Defs) ->
end, end,
Get = fun(K) -> [ Def || Def <- Defs, Kind(Def) == K ] end, Get = fun(K) -> [ Def || Def <- Defs, Kind(Def) == K ] end,
{Env1, TypeDefs} = check_typedefs(Env, Get(type)), {Env1, TypeDefs} = check_typedefs(Env, Get(type)),
create_type_errors(),
Env2 = Env2 =
case What of case What of
namespace -> Env1; namespace -> Env1;
contract -> bind_state(Env1) %% bind state and put contract -> bind_state(Env1) %% bind state and put
end, end,
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype) ]), {ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype) ]),
create_type_errors(),
Env3 = bind_funs(ProtoSigs, Env2), Env3 = bind_funs(ProtoSigs, Env2),
Functions = Get(function), Functions = Get(function),
%% Check for duplicates in Functions (we turn it into a map below) %% Check for duplicates in Functions (we turn it into a map below)
@ -780,8 +791,9 @@ check_reserved_entrypoints(Funs) ->
check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type = {fun_t, _, _, _, _}}) -> check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type = {fun_t, _, _, _, _}}) ->
Type1 = {fun_t, _, Named, Args, Ret} = check_type(Env, Type), Type1 = {fun_t, _, Named, Args, Ret} = check_type(Env, Type),
{{Name, {type_sig, Ann, Named, Args, Ret}}, {fun_decl, Ann, Id, Type1}}; {{Name, {type_sig, Ann, Named, Args, Ret}}, {fun_decl, Ann, Id, Type1}};
check_fundecl(_, {fun_decl, _Attrib, {id, _, Name}, Type}) -> check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type}) ->
error({fundecl_must_have_funtype, Name, Type}). type_error({fundecl_must_have_funtype, Ann, Id, Type}),
{{Name, {type_sig, Ann, [], [], Type}}, check_type(Env, Type)}.
infer_nonrec(Env, LetFun) -> infer_nonrec(Env, LetFun) ->
create_constraints(), create_constraints(),

View File

@ -76,7 +76,9 @@ compilable_contracts() ->
"builtin_map_get_bug", "builtin_map_get_bug",
"nodeadcode", "nodeadcode",
"deadcode", "deadcode",
"variant_types" "variant_types",
"state_handling",
"events"
]. ].
%% Contracts that should produce type errors %% Contracts that should produce type errors

View File

@ -1,19 +1,22 @@
contract Remote = contract Remote =
function look_at : (state) => () record rstate = { i : int, s : string, m : map(int, int) }
function look_at : (rstate) => ()
function return_s : (bool) => string function return_s : (bool) => string
function return_m : (bool) => map(int, int) function return_m : (bool) => map(int, int)
function get : (state) => state function get : (rstate) => rstate
function get_i : (state) => int function get_i : (rstate) => int
function get_s : (state) => string function get_s : (rstate) => string
function get_m : (state) => map(int, int) function get_m : (rstate) => map(int, int)
function fun_update_i : (state, int) => state function fun_update_i : (rstate, int) => rstate
function fun_update_s : (state, string) => state function fun_update_s : (rstate, string) => rstate
function fun_update_m : (state, map(int, int)) => state function fun_update_m : (rstate, map(int, int)) => rstate
function fun_update_mk : (state, int, int) => state function fun_update_mk : (rstate, int, int) => rstate
contract StateHandling = contract StateHandling =
record state = { i : int, s : string, m : map(int, int) }
type state = Remote.rstate
function init(r : Remote, i : int) = function init(r : Remote, i : int) =
let state0 = { i = 0, s = "undefined", m = {} } let state0 = { i = 0, s = "undefined", m = {} }