Merge pull request #95 from aeternity/PT-166233670-fate-events

PT-166233670 FATE events
This commit is contained in:
Hans Svensson 2019-06-26 08:48:44 +02:00 committed by GitHub
commit cfb1605a76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 160 additions and 65 deletions

View File

@ -2,7 +2,7 @@
{erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"bf05e14"}}}
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"677712b"}}}
, {getopt, "1.0.1"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
{tag, "2.8.0"}}}

View File

@ -1,7 +1,7 @@
{"1.1.0",
[{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git",
{ref,"bf05e14661ae25905bd020bfc2dcbc074f8ad66b"}},
{ref,"677712b0b857da6a747b674b4efb1e030937f5f3"}},
0},
{<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git",

View File

@ -772,11 +772,13 @@ is_word_type({id, _, Name}) ->
lists:member(Name, ["int", "address", "hash", "bits", "bool"]);
is_word_type({app_t, _, {id, _, Name}, [_, _]}) ->
lists:member(Name, ["oracle", "oracle_query"]);
is_word_type({bytes_t, _, N}) -> N =< 32;
is_word_type({con, _, _}) -> true;
is_word_type({qcon, _, _}) -> true;
is_word_type(_) -> false.
is_string_type({id, _, "string"}) -> true;
is_string_type({bytes_t, _, N}) -> N > 32;
is_string_type(_) -> false.
-spec check_constructor_overlap(env(), aeso_syntax:con(), type()) -> ok | no_return().

View File

@ -16,11 +16,11 @@
-type option() :: term().
-type attribute() :: stateful | pure.
-type attribute() :: stateful | pure | private.
-type fun_name() :: {entrypoint, binary()}
| {local_fun, [string()]}
| init.
| init | event.
-type var_name() :: string().
-type sophia_name() :: [string()].
@ -127,14 +127,15 @@
| {namespace, string()}
| {abstract_contract, string()}.
-type env() :: #{ type_env := type_env(),
fun_env := fun_env(),
con_env := con_env(),
builtins := builtins(),
options := [option()],
context => context(),
vars => [var_name()],
functions := #{ fun_name() => fun_def() } }.
-type env() :: #{ type_env := type_env(),
fun_env := fun_env(),
con_env := con_env(),
event_type => aeso_syntax:typedef(),
builtins := builtins(),
options := [option()],
context => context(),
vars => [var_name()],
functions := #{ fun_name() => fun_def() } }.
%% -- Entrypoint -------------------------------------------------------------
@ -175,11 +176,12 @@ builtins() ->
{["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none},
{"timestamp", none}, {"block_height", none}, {"difficulty", none},
{"gas_limit", none}]},
{["Contract"], [{"address", none}, {"balance", none}]},
{["Contract"], [{"address", none}, {"balance", none}, {"creator", none}]},
{["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none},
{"gas_left", 0}]},
{["Oracle"], [{"register", 4}, {"query_fee", 1}, {"query", 5}, {"get_question", 2},
{"respond", 4}, {"extend", 3}, {"get_answer", 2}]},
{"respond", 4}, {"extend", 3}, {"get_answer", 2},
{"check", 1}, {"check_query", 2}]},
{["AENS"], [{"resolve", 2}, {"preclaim", 3}, {"claim", 4}, {"transfer", 4},
{"revoke", 3}]},
{["Map"], [{"from_list", 1}, {"to_list", 1}, {"lookup", 2},
@ -192,7 +194,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}]}
{["Address"], [{"to_str", 1}, {"is_oracle", 1}, {"is_contract", 1}]}
],
maps:from_list([ {NS ++ [Fun], {MkName(NS, Fun), Arity}}
|| {NS, Funs} <- Scopes,
@ -228,7 +230,7 @@ to_fcode(Env, [{contract, _, {con, _, Main}, Decls}]) ->
MainEnv = Env#{ context => {main_contract, Main},
builtins => Builtins#{[Main, "state"] => {get_state, none},
[Main, "put"] => {set_state, 1},
[Main, "Chain", "event"] => {event, 1}} },
[Main, "Chain", "event"] => {chain_event, 1}} },
#{ functions := Funs } = Env1 =
decls_to_fcode(MainEnv, Decls),
StateType = lookup_type(Env1, [Main, "state"], [], {tuple, []}),
@ -236,7 +238,7 @@ to_fcode(Env, [{contract, _, {con, _, Main}, Decls}]) ->
#{ contract_name => Main,
state_type => StateType,
event_type => EventType,
functions => Funs };
functions => add_event_function(Env1, EventType, Funs) };
to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) ->
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls),
to_fcode(Env1, Code);
@ -302,7 +304,11 @@ typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
_ -> #{}
end,
Env1 = bind_constructors(Env, Constructors),
bind_type(Env1, Q, FDef).
Env2 = case Name of
"event" -> Env1#{ event_type => Def };
_ -> Env1
end,
bind_type(Env2, Q, FDef).
-spec type_to_fcode(env(), aeso_syntax:type()) -> ftype().
type_to_fcode(Env, Type) ->
@ -475,7 +481,9 @@ expr_to_fcode(Env, Type, {app, _Ann, Fun = {typed, _, _, {fun_t, _, NamedArgsT,
B =:= oracle_get_question;
B =:= oracle_get_answer;
B =:= oracle_respond;
B =:= oracle_register ->
B =:= oracle_register;
B =:= oracle_check;
B =:= oracle_check_query ->
%% Get the type of the oracle from the args or the expression itself
OType = get_oracle_type(B, Type, Args1),
{oracle, QType, RType} = type_to_fcode(Env, OType),
@ -547,10 +555,12 @@ make_if(Cond, Then, Else) ->
get_oracle_type(oracle_register, OType, _Args) -> OType;
get_oracle_type(oracle_query, _Type, [{typed, _,_Expr, OType}|_]) -> OType;
get_oracle_type(oracle_get_question, _Type, [{typed, _,_Expr, OType}|_]) -> OType;
get_oracle_type(oracle_get_answer, _Type, [{typed, _,_Expr, OType}|_]) -> OType;
get_oracle_type(oracle_respond, _Type, [_,{typed, _,_Expr, OType}|_]) -> OType.
get_oracle_type(oracle_query, _Type, [{typed, _, _Expr, OType} | _]) -> OType;
get_oracle_type(oracle_get_question, _Type, [{typed, _, _Expr, OType} | _]) -> OType;
get_oracle_type(oracle_get_answer, _Type, [{typed, _, _Expr, OType} | _]) -> OType;
get_oracle_type(oracle_check, _Type, [{typed, _, _Expr, OType}]) -> OType;
get_oracle_type(oracle_check_query, _Type, [{typed, _, _Expr, OType} | _]) -> OType;
get_oracle_type(oracle_respond, _Type, [_, {typed, _,_Expr, OType} | _]) -> OType.
%% -- Pattern matching --
@ -783,6 +793,8 @@ op_builtins() ->
builtin_to_fcode(require, [Cond, Msg]) ->
make_if(Cond, {tuple, []}, {builtin, abort, [Msg]});
builtin_to_fcode(chain_event, [Event]) ->
{def, event, [Event]};
builtin_to_fcode(map_delete, [Key, Map]) ->
{op, map_delete, [Map, Key]};
builtin_to_fcode(map_member, [Key, Map]) ->
@ -801,6 +813,35 @@ builtin_to_fcode(Builtin, Args) ->
false -> {builtin, Builtin, Args}
end.
%% -- Event function --
add_event_function(_Env, none, Funs) -> Funs;
add_event_function(Env, EventFType, Funs) ->
Funs#{ event => event_function(Env, EventFType) }.
event_function(_Env = #{event_type := {variant_t, EventCons}}, EventType = {variant, FCons}) ->
Cons = [ {Name, I - 1, proplists:get_value(indices, Ann)}
|| {I, {constr_t, Ann, {con, _, Name}, _}} <- indexed(EventCons) ],
Arities = [length(Ts) || Ts <- FCons],
Case = fun({Name, Tag, Ixs}) ->
%% TODO: precompute (needs dependency)
Hash = {op, crypto_sha3, [{lit, {string, list_to_binary(Name)}}]},
Vars = [ "arg" ++ integer_to_list(I) || I <- lists:seq(1, length(Ixs)) ],
IVars = lists:zip(Ixs, Vars),
Payload =
case [ V || {notindexed, V} <- IVars ] of
[] -> {lit, {string, <<>>}};
[V] -> {var, V}
end,
Indices = [ {var, V} || {indexed, V} <- IVars ],
Body = {builtin, chain_event, [Payload, Hash | Indices]},
{'case', {con, Arities, Tag, Vars}, {nosplit, Body}}
end,
#{ attrs => [private],
args => [{"e", EventType}],
return => {tuple, []},
body => {switch, {split, EventType, "e", lists:map(Case, Cons)}} }.
%% -- Lambda lifting ---------------------------------------------------------
%% The expr_to_fcode compiler lambda expressions to {lam, Xs, Body}, but in
%% FATE we can only call top-level functions, so we need to lift the lambda to
@ -1232,7 +1273,8 @@ pp_fun(Name, #{ args := Args, return := Return, body := Body }) ->
pp_text(" : "), pp_ftype(Return), pp_text(" =")]),
prettypr:nest(2, pp_fexpr(Body))).
pp_fun_name(init) -> pp_text("init");
pp_fun_name(init) -> pp_text(init);
pp_fun_name(event) -> pp_text(event);
pp_fun_name({entrypoint, E}) -> pp_text(binary_to_list(E));
pp_fun_name({local_fun, Q}) -> pp_text(string:join(Q, ".")).
@ -1261,6 +1303,8 @@ pp_punctuate(Sep, [X | Xs]) -> [pp_beside(X, Sep) | pp_punctuate(Sep, Xs)].
pp_par([]) -> prettypr:empty();
pp_par(Xs) -> prettypr:par(Xs).
pp_fexpr({lit, {typerep, T}}) ->
pp_ftype(T);
pp_fexpr({lit, {Tag, Lit}}) ->
aeso_pretty:expr({Tag, [], Lit});
pp_fexpr(nil) ->
@ -1321,18 +1365,23 @@ pp_fexpr({switch, Split}) -> pp_split(Split).
pp_call(Fun, Args) ->
pp_beside(Fun, pp_fexpr({tuple, Args})).
pp_call_t(Fun, Args) ->
pp_beside(pp_text(Fun), pp_ftype({tuple, Args})).
-spec pp_ftype(ftype()) -> any().
pp_ftype(T) when is_atom(T) -> pp_text(T);
pp_ftype(any) -> pp_text("_");
pp_ftype({tvar, X}) -> pp_text(X);
pp_ftype({bytes, N}) -> pp_text("bytes(" ++ integer_to_list(N) ++ ")");
pp_ftype({bytes, N}) -> pp_call(pp_text("bytes"), [{lit, {int, N}}]);
pp_ftype({oracle, Q, R}) -> pp_call_t("oracle", [Q, R]);
pp_ftype({tuple, Ts}) ->
pp_parens(pp_par(pp_punctuate(pp_text(","), [pp_ftype(T) || T <- Ts])));
pp_ftype({list, T}) ->
pp_beside([pp_text("list("), pp_ftype(T), pp_text(")")]);
pp_call_t("list", [T]);
pp_ftype({function, Args, Res}) ->
pp_par([pp_ftype({tuple, Args}), pp_text("=>"), pp_ftype(Res)]);
pp_ftype({map, Key, Val}) ->
pp_beside([pp_text("map"), pp_ftype({tuple, [Key, Val]})]);
pp_call_t("map", [Key, Val]);
pp_ftype({variant, Cons}) ->
pp_par(
pp_punctuate(pp_text(" |"),

View File

@ -119,11 +119,12 @@ check_event_type(EvtName, Ix, Type, Icode) ->
catch _:_ ->
error({EvtName, could_not_resolve_type, Type})
end,
case {Ix, VMType} of
{indexed, word} -> ok;
{notindexed, string} -> ok;
{indexed, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
{notindexed, _} -> error({EvtName, payload_should_be_string, is, VMType})
case {Ix, VMType, Type} of
{indexed, word, _} -> ok;
{notindexed, string, _} -> ok;
{notindexed, _, {bytes_t, _, N}} when N > 32 -> ok;
{indexed, _, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
{notindexed, _, _} -> error({EvtName, payload_should_be_string, is, VMType})
end.
bfun(B, {IArgs, IExpr, IRet}) ->
@ -177,16 +178,22 @@ builtin_event(EventT) ->
VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end,
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
Payload = %% Should put data ptr, length on stack.
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
([V]) -> {seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]} %% ptr+32, length
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
([{{id, _, "string"}, V}]) ->
{seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]}; %% ptr+32, length
([{{bytes_t, _, N}, V}]) -> {seq, [V, {integer, N}, {inline_asm, A(?SWAP1)}]}
end,
Ix =
fun({bytes_t, _, N}, V) when N < 32 -> ?BSR(V, 32 - N);
(_, V) -> V end,
Clause =
fun(_Tag, {con, _, Con}, IxTypes) ->
Types = [ T || {_Ix, T} <- IxTypes ],
Indexed = [ Var || {Var, {indexed, _Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
Indexed = [ Ix(Type, Var) || {Var, {indexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
Data = [ {Type, Var} || {Var, {notindexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
EvtIndex = {unop, 'sha3', str_to_icode(Con)},
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(ArgPats(Types) -- Indexed)}
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(Data)}
end,
Pat = fun(Tag, Types) -> {tuple, [{integer, Tag} | ArgPats(Types)]} end,

View File

@ -104,6 +104,11 @@
Op =:= 'AUTH_TX_HASH' orelse
Op =:= 'BYTES_TO_INT' orelse
Op =:= 'BYTES_TO_STR' orelse
Op =:= 'ORACLE_CHECK' orelse
Op =:= 'ORACLE_CHECK_QUERY' orelse
Op =:= 'IS_ORACLE' orelse
Op =:= 'IS_CONTRACT' orelse
Op =:= 'CREATOR' orelse
false)).
-record(env, { contract, vars = [], locals = [], tailpos = true }).
@ -135,6 +140,7 @@ make_function_id(X) ->
aeb_fate_code:symbol_identifier(make_function_name(X)).
make_function_name(init) -> <<"init">>;
make_function_name(event) -> <<"Chain.event">>;
make_function_name({entrypoint, Name}) -> Name;
make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, ".")).
@ -464,8 +470,9 @@ builtin_to_scode(_Env, get_state, []) ->
builtin_to_scode(Env, set_state, [_] = Args) ->
call_to_scode(Env, [aeb_fate_ops:store(?s, ?a),
tuple(0)], Args);
builtin_to_scode(_Env, event, [_] = _Args) ->
?TODO(fate_event_instruction);
builtin_to_scode(Env, chain_event, Args) ->
call_to_scode(Env, [erlang:apply(aeb_fate_ops, log, lists:duplicate(length(Args), ?a)),
tuple(0)], Args);
builtin_to_scode(_Env, map_empty, []) ->
[aeb_fate_ops:map_empty(?a)];
builtin_to_scode(_Env, bits_none, []) ->
@ -499,6 +506,8 @@ builtin_to_scode(_Env, contract_balance, []) ->
[aeb_fate_ops:balance(?a)];
builtin_to_scode(_Env, contract_address, []) ->
[aeb_fate_ops:address(?a)];
builtin_to_scode(_Env, contract_creator, []) ->
[aeb_fate_ops:contract_creator(?a)];
builtin_to_scode(_Env, call_origin, []) ->
[aeb_fate_ops:origin(?a)];
builtin_to_scode(_Env, call_caller, []) ->
@ -525,6 +534,14 @@ builtin_to_scode(Env, oracle_extend, [_Sign, _Oracle, _TTL] = Args) ->
tuple(0)], Args);
builtin_to_scode(Env, oracle_get_answer, [_Oracle, _QueryId, _QType, _RType] = Args) ->
call_to_scode(Env, aeb_fate_ops:oracle_get_answer(?a, ?a, ?a, ?a, ?a), Args);
builtin_to_scode(Env, oracle_check, [_Oracle, _QType, _RType] = Args) ->
call_to_scode(Env, aeb_fate_ops:oracle_check(?a, ?a, ?a, ?a), Args);
builtin_to_scode(Env, oracle_check_query, [_Oracle, _Query, _QType, _RType] = Args) ->
call_to_scode(Env, aeb_fate_ops:oracle_check_query(?a, ?a, ?a, ?a, ?a), Args);
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, aens_resolve, [_, _] = _Args) ->
?TODO(fate_aens_resolve_instruction);
builtin_to_scode(_Env, aens_preclaim, [_, _, _] = _Args) ->
@ -810,6 +827,11 @@ attributes(I) ->
{'AUTH_TX_HASH', A} -> Pure(A, []);
{'BYTES_TO_INT', A, B} -> Pure(A, [B]);
{'BYTES_TO_STR', A, B} -> Pure(A, [B]);
{'ORACLE_CHECK', A, B, C, D} -> Impure(A, [B, C, D]);
{'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]);
{'CREATOR', A} -> Pure(A, []);
{'ADDRESS', A} -> Pure(A, []);
{'BALANCE', A} -> Impure(A, []);
{'BALANCE_OTHER', A, B} -> Impure(A, [B]);
@ -824,11 +846,11 @@ attributes(I) ->
{'DIFFICULTY', A} -> Pure(A, []);
{'GASLIMIT', A} -> Pure(A, []);
{'GAS', A} -> Impure(?a, A);
{'LOG0', A, B} -> Impure(none, [A, B]);
{'LOG1', A, B, C} -> Impure(none, [A, B, C]);
{'LOG2', A, B, C, D} -> Impure(none, [A, B, C, D]);
{'LOG3', A, B, C, D, E} -> Impure(none, [A, B, C, D, E]);
{'LOG4', A, B, C, D, E, F} -> Impure(none, [A, B, C, D, E, F]);
{'LOG0', A} -> Impure(none, [A]);
{'LOG1', A, B} -> Impure(none, [A, B]);
{'LOG2', A, B, C} -> Impure(none, [A, B, C]);
{'LOG3', A, B, C, D} -> Impure(none, [A, B, C, D]);
{'LOG4', A, B, C, D, E} -> Impure(none, [A, B, C, D, E]);
'DEACTIVATE' -> Impure(none, []);
{'SPEND', A, B} -> Impure(none, [A, B]);

View File

@ -330,6 +330,7 @@ expr_p(_, {Type, _, Bin})
Type == oracle_pubkey;
Type == oracle_query_id ->
text(binary_to_list(aeser_api_encoder:encode(Type, Bin)));
expr_p(_, {string, _, <<>>}) -> text("\"\"");
expr_p(_, {string, _, S}) -> term(binary_to_list(S));
expr_p(_, {char, _, C}) ->
case C of

View File

@ -119,11 +119,7 @@ compilable_contracts() ->
"bytes_to_x"
].
not_yet_compilable(fate) ->
["events", %% events
"address_literals", %% oracle_query_id literals
"address_chain" %% Oracle.check_query
];
not_yet_compilable(fate) -> [];
not_yet_compilable(aevm) -> [].
%% Contracts that should produce type errors

View File

@ -1,22 +1,40 @@
contract Remote =
function dummy : () => ()
contract Events =
type alias_int = int
type alias_address = address
type alias_string = string
datatype event =
Event1(indexed alias_int, indexed int, string)
| Event2(alias_string, indexed alias_address)
// | BadEvent1(indexed string, string)
// | BadEvent2(indexed int, int)
// Valid index types
type ix1 = int
type ix2 = bool
type ix3 = bits
type ix4 = bytes(12)
type ix5 = hash // bytes(32)
type ix6 = address
type ix7 = Remote
type ix8 = oracle(int, int)
type ix9 = oracle_query(int, int)
function f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y))
// Valid payload types
type data1 = string
type data2 = signature // bytes(64)
type data3 = bytes(65)
function f2(s : string) =
Chain.event(Event2(s, Call.caller))
datatype event
= Nodata0
| Nodata1(ix1)
| Nodata2(ix2, ix3)
| Nodata3(ix4, ix5, ix6)
| Data0(data1)
| Data1(data2, ix7)
| Data2(ix8, data3, ix9)
| Data3(ix1, ix2, ix5, data1)
function f3(x : int) =
Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
function nodata0() = Chain.event(Nodata0)
function nodata1(ix1) = Chain.event(Nodata1(ix1))
function nodata2(ix2, ix3) = Chain.event(Nodata2(ix2, ix3))
function nodata3(ix4, ix5, ix6) = Chain.event(Nodata3(ix4, ix5, ix6))
function data0(data1) = Chain.event(Data0(data1))
function data1(data2, ix7) = Chain.event(Data1(data2, ix7))
function data2(ix8, data3, ix9) = Chain.event(Data2(ix8, data3, ix9))
function data3(ix1, ix2, ix5, data1) = Chain.event(Data3(ix1, ix2, ix5, data1))
function i2s(i : int) = Int.to_str(i)
function a2s(a : address) = Address.to_str(a)