Compare commits

..

14 Commits

Author SHA1 Message Date
Robert Virding d16fb82e25 Break out state and event from typedefs and update docs 2019-05-08 16:06:58 +02:00
Robert Virding d2cd97def7 Add handling of creating/updating maps and records, definitely WIP 2019-04-29 00:56:31 +02:00
Robert Virding 5455d0fcd7 Fixed a type error and test, definitely WIP 2019-04-25 12:19:49 +02:00
Robert Virding 2d3e6ab6e0 Refactor internal code and more add statements, definitely WIP 2019-04-25 11:56:21 +02:00
Robert Virding 70a0f77793 Replace hash with bytes, definitely WIP 2019-04-23 11:56:54 +02:00
Robert Virding 04b3227317 Update documentation, definitely WIP 2019-04-23 11:56:08 +02:00
Robert Virding d9be8b2fca Saving even more stuff, definitely WIP 2019-04-23 11:56:08 +02:00
Robert Virding a38afe7693 Saving more stuff, definitely WIP 2019-04-23 11:56:08 +02:00
Robert Virding 5719730d8c Saving stuff, definitely WIP 2019-04-23 11:56:08 +02:00
Hans Svensson 51b63f9559 Merge pull request #59 from aeternity/PT-164629541-generic_hash_and_signature
Add bytes(int), add address_literalsm add ecverify_secp256k1
2019-04-23 11:23:32 +02:00
Hans Svensson 5e6af18c7b Address review comment 2019-04-23 11:10:56 +02:00
Hans Svensson 4324bfd49e Add bytes(int), add address_literalsm add ecverify_secp25k1
hash -> bytes(32)
signature -> bytes(64)
address literals
2019-04-23 10:40:02 +02:00
Erik Stenman faa0ef9772 Merge pull request #57 from aeternity/PT-165312102-setelement
Point to latest aebytecode with setelement instruction.
2019-04-15 10:57:10 +02:00
Erik Stenman f07954f62c Point to latest aebytecode with setelement instruction. 2019-04-15 08:54:02 +02:00
19 changed files with 782 additions and 267 deletions
+103 -17
View File
@@ -31,23 +31,48 @@ generates the following JSON structure representing the contract interface:
{
"contract": {
"name": "Answers",
"state": {
"record": [
{
"name": "a",
"type": {
"map": {
"key": "string",
"value": "int"
}
}
}
]
},
"type_defs": [
{
"name": "state",
"vars": [],
"typedef": "{a : map(string,int)}"
},
{
"name": "answers",
"vars": [],
"typedef": "map(string,int)"
"typedef": {
"map": {
"key": "string",
"value": "int"
}
}
}
],
"functions": [
{
"name": "init",
"arguments": [],
"type": "{a : map(string,int)}",
"returns": {
"record": [
{
"name": "a",
"type": {
"map": {
"key": "string",
"value": "int"
}
}
}
]
},
"stateful": true
},
{
@@ -62,7 +87,12 @@ generates the following JSON structure representing the contract interface:
"type": "int"
}
],
"type": "map(string,int)",
"returns": {
"map": {
"key": "string",
"value": "int"
}
},
"stateful": false
}
]
@@ -74,7 +104,7 @@ When that encoding is decoded the following include definition is generated:
```
contract Answers =
function new_answer : (string, int) => map(string,int)
function new_answer : (string, int) => map(string, int)
```
### Types
@@ -85,7 +115,7 @@ json_string() = binary()
### Exports
#### encode(ContractString) -> {ok,JSONstring} | {error,ErrorString}
#### encode_contract(ContractString) -> {ok,JSONstring} | {error,ErrorString}
Types
@@ -94,9 +124,21 @@ ConstractString = contract_string()
JSONstring = json_string()
```
This is equivalent to `aeso_aci:encode_contract(ConstractString, [])`.
#### encode_contract(ContractString, Options) -> {ok,JSONstring} | {error,ErrorString}
Types
``` erlang
ConstractString = contract_string()
Options = [option()]
JSONstring = json_string()
```
Generate the JSON encoding of the interface to a contract. The type definitions and non-private functions are included in the JSON string.
#### decode(JSONstring) -> ConstractString.
#### decode_contract(JSONstring) -> ConstractString.
Types
@@ -107,23 +149,67 @@ JSONstring = json_string()
Take a JSON encoding of a contract interface and generate and generate a contract definition which can be included in another contract.
#### encode_type(TypeAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of a type from the AST of the type.
#### encode_arg(ArgAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of a function argument from the AST of the argument.
#### encode_stmt(StmtAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of a statement from the AST of the statement.
#### encode_expr(ExprAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of an expression from the AST of the expression.
### Notes
The deprecated functions `aseo_aci:encode/2` and `aeso_aci:decode/1` are still available but should not be used.
### Example run
This is an example of using the ACI generator from an Erlang shell. The file called `aci_test.aes` contains the contract in the description from which we want to generate files `aci_test.json` which is the JSON encoding of the contract interface and `aci_test.include` which is the contract definition to be included inside another contract.
``` erlang
1> {ok,Contract} = file:read_file("aci_test.aes").
{ok,<<"contract Answers =\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful function"...>>}
2> {ok,Encoding} = aeso_aci:encode(Contract).
<<"{\"contract\":{\"name\":\"Answers\",\"type_defs\":[{\"name\":\"state\",\"vars\":[],\"typedef\":\"{a : map(string,int)}\"},{\"name\":\"ans"...>>
{ok,<<"contract Answers =\n\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful functio"...>>}
2> {ok,Encoding} = aeso_aci:encode_contract(Contract).
{ok,<<"{\"contract\":{\"name\":\"Answers\",\"state\":{\"record\":[{\"name\":\"a\",\"type\":{\"map\":{\"key\":\"string\",\"value\":\"int\"}}}]"...>>}
3> file:write_file("aci_test.aci", Encoding).
ok
4> Decoded = aeso_aci:decode(Encoding).
<<"contract Answers =\n function new_answer : (string, int) => map(string,int)\n">>
4> Decoded = aeso_aci:decode_contract(Encoding).
<<"contract Answers =\n function new_answer : (string, int) => map(string, int)\n">>
5> file:write_file("aci_test.include", Decoded).
ok
6> jsx:prettify(Encoding).
<<"{\n \"contract\": {\n \"name\": \"Answers\",\n \"type_defs\": [\n {\n \"name\": \"state\",\n \"vars\": [],\n "...>>
<<"{\n \"contract\": {\n \"name\": \"Answers\",\n \"state\": {\n \"record\": [\n {\n \"name\": \"a\",\n "...>>
```
The final call to `jsx:prettify(Encoding)` returns the encoding in a
+1 -1
View File
@@ -3,7 +3,7 @@
{erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
{ref, "28f6c42"}}}
{ref, "e8253b0"}}}
, {getopt, "1.0.1"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
{tag, "2.8.0"}}}
+1 -1
View File
@@ -1,7 +1,7 @@
{"1.1.0",
[{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git",
{ref,"9041423906247a7267a5a94530307b19c4490e8c"}},
{ref,"e8253b09709f1595d8bd6a1756a0ce93185c6518"}},
0},
{<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git",
+340 -161
View File
@@ -9,11 +9,16 @@
-module(aeso_aci).
%% Old deprecated interface.
-export([encode/1,encode/2,decode/1]).
-export([encode_type/1,encode_stmt/1,encode_expr/1]).
-export([encode_contract/1,encode_contract/2,decode_contract/1]).
-export([encode_func/1,encode_type/1,encode_arg/1,
encode_stmt/1,encode_expr/1]).
%% Define records for the various typed syntactic forms. These make
%% the code easier but don't seem to exist elsewhere.
%% the code easier but don't seem to exist elsewhere. Unfortunately
%% sometimes the same typename is used with different fields.
%% Top-level
-record(contract, {ann,con,decls}).
@@ -24,6 +29,7 @@
%% Types
-record(app_t, {ann,id,fields}).
-record(tuple_t, {ann,args}).
-record(bytes_t, {ann,len}).
-record(record_t, {fields}).
-record(field_t, {ann,id,type}).
-record(alias_t, {type}).
@@ -39,43 +45,62 @@
-record(qcon, {ann,names}).
-record(tvar, {ann,name}).
%% Statements
-record(block, {ann,body}).
-record('if', {ann,test,then,else}). %Both statement and expression
-record(letval, {ann,pat,type,exp}).
-record(switch, {ann,arg,cases}).
-record('case', {ann,pat,body}).
%% Expressions
-record(bool, {ann,bool}).
-record(int, {ann,value}).
-record(string, {ann,bin}).
-record(hash, {ann,hash}).
-record(bytes, {ann,bin}).
-record(tuple, {ann,args}).
-record(list, {ann,args}).
-record(record, {ann,fields}). %Create a record
-record(field, {ann,name,value}). %A record field
-record(proj, {ann,value}). %?
-record(map, {ann,fields}). %Create a map
-record(map_get, {ann,field}).
-record(lam, {ann,args,body}).
-record(app, {ann,func,args}).
-record(typed, {ann,expr,type}).
%% encode(ContractString) -> {ok,JSON} | {error,String}.
%% encode(ContractString, Options) -> {ok,JSON} | {error,String}.
%% The old deprecated interface.
encode(C) -> encode_contract(C).
encode(C, Os) -> encode_contract(C, Os).
decode(J) -> decode_contract(J).
%% encode_contract(ContractString) -> {ok,JSON} | {error,String}.
%% encode_contract(ContractString, Options) -> {ok,JSON} | {error,String}.
%% Build a JSON structure with lists and tuples, not maps, as this
%% allows us to order the fields in the contructed JSON string.
encode(ContractString) -> encode(ContractString, []).
encode(ContractString, Options) when is_binary(ContractString) ->
encode(binary_to_list(ContractString), Options);
encode(ContractString, Options) ->
encode_contract(ContractString) ->
encode_contract(ContractString, []).
encode_contract(ContractString, Options) when is_binary(ContractString) ->
encode_contract(binary_to_list(ContractString), Options);
encode_contract(ContractString, Options) ->
try
Ast = parse(ContractString, Options),
%% io:format("~p\n", [Ast]),
%% io:format("Ast\n~p\n", [Ast]),
%% aeso_ast:pp(Ast),
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
%% io:format("~p\n", [TypedAst]),
%% io:format("Typed ast\n~p\n", [TypedAst]),
%% aeso_ast:pp_typed(TypedAst),
%% We find and look at the last contract.
Contract = lists:last(TypedAst),
Cname = contract_name(Contract),
Tdefs = [ encode_typedef(T) ||
T <- sort_decls(contract_types(Contract)) ],
Fdefs = [ encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
not is_private_func(F) ],
Jmap = [{<<"contract">>, [{<<"name">>, encode_name(Cname)},
{<<"type_defs">>, Tdefs},
{<<"functions">>, Fdefs}]}],
Tdefs = do_encode_contract_typedefs(sort_decls(contract_types(Contract))),
Fdefs = [ do_encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
not is_private_func(F) ],
Jmap = [{<<"contract">>,
[{<<"name">>, do_encode_name(Cname)}] ++
Tdefs ++
[{<<"functions">>, Fdefs}]}],
%% io:format("~p\n", [Jmap]),
{ok,jsx:encode(Jmap)}
catch
@@ -94,221 +119,375 @@ join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
%% encode_func(Function) -> JSON
%% do_encode_contract_typedefs(TypeDefs) -> [JSON].
%% Return a list of typedefs and state and event if they occur.
do_encode_contract_typedefs(Tdefs) ->
Fun = fun(T, {Ts,Ss,Es}) ->
%% Only one state and event.
case typedef_name(T) of
"state" -> {Ts,[do_encode_state_typedef(T)],Es};
"event" -> {Ts,Ss,[do_encode_event_typedef(T)]};
_Name -> {Ts ++ [do_encode_typedef(T)],Ss,Es}
end
end,
{Ts,Ss,Es} = lists:foldl(Fun, {[],[],[]}, Tdefs),
Ss ++ [{<<"type_defs">>, Ts}] ++ Es.
%% do_encode_state_typedef(StateTdef) -> JSON.
%% do_encode_event_typedef(EventTdef) -> JSON.
do_encode_state_typedef(State) ->
Def = typedef_def(State),
{<<"state">>,do_encode_alias(Def)}.
do_encode_event_typedef(State) ->
Def = typedef_def(State),
{<<"event">>,do_encode_alias(Def)}.
%% encode_func(TypedAST) -> JSON.
%% Encode a function AST into a JSON structure.
encode_func(AST) ->
jsx:encode(do_encode_func(AST)).
%% do_encode_func(Function) -> JSONmap
%% Encode a function definition. Currently we are only interested in
%% the interface and type.
encode_func(Fdef) ->
do_encode_func(Fdef) ->
Name = function_name(Fdef),
Args = function_args(Fdef),
Type = function_type(Fdef),
[{<<"name">>, encode_name(Name)},
{<<"arguments">>, encode_args(Args)},
{<<"returns">>, encode_type(Type)},
[{<<"name">>, do_encode_name(Name)},
{<<"arguments">>, do_encode_args(Args)},
{<<"returns">>, do_encode_type(Type)},
{<<"stateful">>, is_stateful_func(Fdef)}].
%% encode_args(Args) -> [JSON].
%% encode_arg(Args) -> JSON.
%% encode_arg(TypedAST) -> JSON.
%% Encode an argument AST into a JSON structure.
encode_args(Args) ->
[ encode_arg(A) || A <- Args ].
encode_arg(AST) ->
jsx:encode(do_encode_arg(AST)).
encode_arg(#arg{id=Id,type=T}) ->
[{<<"name">>,encode_type(Id)},
{<<"type">>,[encode_type(T)]}].
%% do_encode_args(ArgASTs) -> [JSONmap].
%% do_encode_arg(ArgAST) -> JSONmap.
%% encode_types(Types) -> [JSON].
%% encode_type(Type) -> JSON.
do_encode_args(Args) ->
[ do_encode_arg(A) || A <- Args ].
encode_types(Types) ->
[ encode_type(T) || T <- Types ].
do_encode_arg(#arg{id=Id,type=T}) ->
[{<<"name">>,do_encode_type(Id)},
{<<"type">>,do_encode_type(T)}].
encode_type(#tvar{name=N}) -> encode_name(N);
encode_type(#id{name=N}) -> encode_name(N);
encode_type(#con{name=N}) -> encode_name(N);
encode_type(#qid{names=Ns}) ->
encode_name(lists:join(".", Ns));
encode_type(#qcon{names=Ns}) ->
encode_name(lists:join(".", Ns)); %?
encode_type(#tuple_t{args=As}) ->
Eas = encode_types(As),
%% encode_type(TypedAST) -> JSON.
%% Encode a type AST into a JSON structure.
encode_type(AST) ->
jsx:encode(do_encode_type(AST)).
%% do_encode_types([TypeAST]) -> [JSONmap].
%% do_encode_type(TypeAST) -> JsonMap.
do_encode_types(Types) ->
[ do_encode_type(T) || T <- Types ].
do_encode_type(#tvar{name=N}) -> do_encode_name(N);
do_encode_type(#id{name=N}) -> do_encode_name(N);
do_encode_type(#con{name=N}) -> do_encode_name(N);
do_encode_type(#qid{names=Ns}) ->
do_encode_name(lists:join(".", Ns));
do_encode_type(#qcon{names=Ns}) ->
do_encode_name(lists:join(".", Ns)); %?
do_encode_type(#tuple_t{args=As}) ->
Eas = do_encode_types(As),
[{<<"tuple">>,Eas}];
encode_type(#record_t{fields=Fs}) ->
Efs = encode_fields(Fs),
do_encode_type(#bytes_t{len=Len}) ->
{<<"bytes">>,Len};
do_encode_type(#record_t{fields=Fs}) ->
Efs = do_encode_type_rec_fields(Fs),
[{<<"record">>,Efs}];
encode_type(#app_t{id=Id,fields=Fs}) ->
Name = encode_type(Id),
Efs = encode_types(Fs),
%% Special case lists and maps as they are built-in types.
do_encode_type(#app_t{id=#id{name="list"},fields=[F]}) ->
Ef = do_encode_type(F),
[{<<"list">>,Ef}];
do_encode_type(#app_t{id=#id{name="map"},fields=Fs}) ->
Ef = do_encode_type_mapo_field(Fs),
[{<<"map">>,Ef}];
%% Other applications.
do_encode_type(#app_t{id=Id,fields=Fs}) ->
Name = do_encode_type(Id),
Efs = do_encode_types(Fs),
[{Name,Efs}];
encode_type(#variant_t{cons=Cs}) ->
Ecs = encode_types(Cs),
do_encode_type(#variant_t{cons=Cs}) ->
Ecs = do_encode_types(Cs),
[{<<"variant">>,Ecs}];
encode_type(#constr_t{con=C,args=As}) ->
Ec = encode_type(C),
Eas = encode_types(As),
do_encode_type(#constr_t{con=C,args=As}) ->
Ec = do_encode_type(C),
Eas = do_encode_types(As),
[{Ec,Eas}];
encode_type(#fun_t{args=As,type=T}) ->
Eas = encode_types(As),
Et = encode_type(T),
do_encode_type(#fun_t{args=As,type=T}) ->
Eas = do_encode_types(As),
Et = do_encode_type(T),
[{<<"function">>,[{<<"arguments">>,Eas},{<<"returns">>,Et}]}].
encode_name(Name) ->
do_encode_name(Name) ->
list_to_binary(Name).
%% encode_fields(Fields) -> [JSON].
%% encode_field(Field) -> JSON.
%% Encode a record field.
%% do_encode_type_rec_fields(Fields) -> [JSONmap].
%% do_encode_type_rec_field(Field) -> JSONmap.
%% Encode a record field type.
encode_fields(Fs) ->
[ encode_field(F) || F <- Fs ].
do_encode_type_rec_fields(Fs) ->
[ do_encode_type_rec_field(F) || F <- Fs ].
encode_field(#field_t{id=Id,type=T}) ->
[{<<"name">>,encode_type(Id)},
{<<"type">>,[encode_type(T)]}].
do_encode_type_rec_field(#field_t{id=Id,type=T}) ->
[{<<"name">>,do_encode_type(Id)},
{<<"type">>,do_encode_type(T)}].
%% encode_typedef(TypeDef) -> JSON.
%% do_encode_type_mapo_field(Field) -> JSONmap.
%% Two fields for one map type.
encode_typedef(Type) ->
do_encode_type_mapo_field([K,V]) ->
[{<<"key">>,do_encode_type(K)},
{<<"value">>,do_encode_type(V)}].
%% do_encode_typedef(TypeDefAST) -> JSON.
do_encode_typedef(Type) ->
Name = typedef_name(Type),
Vars = typedef_vars(Type),
Def = typedef_def(Type),
[{<<"name">>, encode_name(Name)},
{<<"vars">>, encode_tvars(Vars)},
{<<"typedef">>, encode_alias(Def)}].
[{<<"name">>, do_encode_name(Name)},
{<<"vars">>, do_encode_tvars(Vars)},
{<<"typedef">>, do_encode_alias(Def)}].
encode_tvars(Vars) ->
[ encode_tvar(V) || V <- Vars ].
do_encode_tvars(Vars) ->
[ do_encode_tvar(V) || V <- Vars ].
encode_tvar(#tvar{name=N}) ->
[{<<"name">>, encode_name(N)}].
do_encode_tvar(#tvar{name=N}) ->
[{<<"name">>, do_encode_name(N)}].
encode_alias(#alias_t{type=T}) ->
encode_type(T);
encode_alias(A) -> encode_type(A).
do_encode_alias(#alias_t{type=T}) ->
do_encode_type(T);
do_encode_alias(A) -> do_encode_type(A).
%% encode_stmt(Stmt) -> JSON.
%% encode_stmt(StmtAST) -> JSON.
%% Encode a statement AST into a JSON structure.
encode_stmt(E) ->
encode_expr(E).
encode_stmt(AST) ->
jsx:encode(do_encode_stmt(AST)).
%% encode_exprs(Exprs) -> [JSON].
%% encode_expr(Expr) -> JSON.
%% do_encode_stmt(StmtAST) -> JSONmap.
encode_exprs(Es) ->
[ encode_expr(E) || E <- Es ].
do_encode_stmt(#typed{expr=E}) -> %Ignore the type
do_encode_stmt(E);
do_encode_stmt(#block{body=Body}) ->
Eblock = [ do_encode_stmt(B) || B <- Body ],
[{<<"block">>,Eblock}];
do_encode_stmt(#'if'{test=Test,then=Then,else=Else}) ->
%% This is both a statement and en expression.
Etest = do_encode_expr(Test),
Ethen = do_encode_stmt(Then),
Eelse = do_encode_stmt(Else),
[{<<"if">>,[{<<"test">>,Etest},{<<"then">>,Ethen},{<<"else">>,Eelse}]}];
do_encode_stmt(#letval{pat=Pat,exp=Exp}) ->
Epat = do_encode_expr(Pat),
Eexp = do_encode_expr(Exp),
[{<<"let">>,[{<<"pattern">>,Epat},{<<"expression">>,Eexp}]}];
do_encode_stmt(#switch{arg=Arg,cases=Cases}) ->
Earg = do_encode_expr(Arg),
Ecases = [ do_encode_stmt_case(Case) || Case <- Cases ],
[{<<"switch">>,[{<<"arg">>,Earg},{<<"cases">>,Ecases}]}];
do_encode_stmt(E) ->
do_encode_expr(E).
encode_expr(#id{name=N}) -> encode_name(N);
encode_expr(#con{name=N}) -> encode_name(N);
encode_expr(#qid{names=Ns}) ->
encode_name(lists:join(".", Ns));
encode_expr(#qcon{names=Ns}) ->
encode_name(lists:join(".", Ns)); %?
encode_expr(#typed{expr=E}) ->
encode_expr(E);
encode_expr(#bool{bool=B}) -> B;
encode_expr(#int{value=V}) -> V;
encode_expr(#string{bin=B}) -> B;
encode_expr(#hash{hash=H}) -> H;
encode_expr(#tuple{args=As}) ->
Eas = encode_exprs(As),
do_encode_stmt_case(#'case'{pat=Pat,body=Body}) ->
Epat = do_encode_expr(Pat), %Patterns are expessions
Ebody = do_encode_stmt(Body),
[{<<"pattern">>,Epat},{<<"body">>,Ebody}].
%% encode_expr(ExprAST) -> JSON.
%% Encode an expression AST into a JSON structure.
encode_expr(AST) ->
jsx:encode(do_encode_expr(AST)).
%% do_encode_exprs(ExprASTs) -> [JSONmap].
%% do_encode_expr(ExprAST) -> JSONmap.
do_encode_exprs(Es) ->
[ do_encode_expr(E) || E <- Es ].
do_encode_expr(#typed{expr=E}) -> %Ignore the type
do_encode_expr(E);
do_encode_expr(#id{name=N}) -> do_encode_name(N);
do_encode_expr(#con{name=N}) -> do_encode_name(N);
do_encode_expr(#qid{names=Ns}) ->
do_encode_name(lists:join(".", Ns));
do_encode_expr(#qcon{names=Ns}) ->
do_encode_name(lists:join(".", Ns)); %?
do_encode_expr(#bool{bool=B}) -> B;
do_encode_expr(#int{value=V}) -> V;
do_encode_expr(#string{bin=B}) ->
[{<<"string">>,B}];
do_encode_expr(#bytes{bin=B}) -> B;
do_encode_expr(#tuple{args=As}) ->
Eas = do_encode_exprs(As),
[{<<"tuple">>,Eas}];
encode_expr(#list{args=As}) ->
Eas = encode_exprs(As),
do_encode_expr(#list{args=As}) ->
Eas = do_encode_exprs(As),
[{<<"list">>,Eas}];
encode_expr(#app{func=F,args=As}) ->
Ef = encode_expr(F),
Eas = encode_exprs(As),
do_encode_expr(#record{fields=Fs}) -> %Create a record
Efs = do_encode_expr_rec_fields(Fs),
[{<<"create_record">>,Efs}];
do_encode_expr({record,_Ann,Rec,Fs}) -> %Update a record
Erec = do_encode_expr(Rec),
Efs = do_encode_expr_rec_fields(Fs),
[{<<"update_record">>,[Erec,Efs]}];
do_encode_expr(#lam{args=As,body=B}) ->
Eas = do_encode_args(As),
Eb = do_encode_stmt(B),
[{<<"function">>,[{<<"arguments">>,Eas},{<<"body">>,Eb}]}];
do_encode_expr(#map{fields=Fs}) -> %Create a map
Efs = do_encode_expr_map_fields(Fs),
[{<<"create_map">>,Efs}];
do_encode_expr({map,_Ann,Map,Fs}) -> %Update a map
Emap = do_encode_expr(Map),
Efs = do_encode_expr_map_fields(Fs),
[{<<"update_map">>,[Emap,Efs]}];
do_encode_expr(#map_get{field=F}) ->
do_encode_expr(F);
do_encode_expr(#proj{value=V}) ->
do_encode_expr(V);
do_encode_expr(#app{func=F,args=As}) ->
Ef = do_encode_expr(F),
Eas = do_encode_exprs(As),
[{<<"apply">>,[{<<"function">>,Ef},
{<<"arguments">>,Eas}]}];
encode_expr({Op,_Ann}) ->
{<<"arguments">>,Eas}]}];
do_encode_expr(#'if'{test=Test,then=Then,else=Else}) ->
%% This is both a statement and en expression.
Etest = do_encode_expr(Test),
Ethen = do_encode_expr(Then),
Eelse = do_encode_expr(Else),
[{<<"if">>,[{<<"test">>,Etest},{<<"then">>,Ethen},{<<"else">>,Eelse}]}];
do_encode_expr({Op,_Ann}) ->
list_to_binary(atom_to_list(Op)).
%% decode(JSON) -> ContractString.
%% do_encode_expr_rec_fields(Fields) -> [JSON].
%% do_encode_expr_rec_field(Field) -> JSON.
%% Encode a record field expression.
do_encode_expr_rec_fields(Fs) ->
[ do_encode_expr_rec_field(F) || F <- Fs ].
do_encode_expr_rec_field(#field{name=[N],value=V}) ->
[{<<"name">>,do_encode_expr(N)},
{<<"value">>,do_encode_expr(V)}].
%% do_encode_expr_map_fields(Fields) -> [JSON].
%% do_encode_expr_map_field(Field) -> JSON.
%% Encode a map field expression.
do_encode_expr_map_fields(Fs) ->
[ do_encode_expr_map_field(F) || F <- Fs ].
do_encode_expr_map_field({K,V}) ->
[{<<"key">>,do_encode_expr(K)},
{<<"value">>,do_encode_expr(V)}];
do_encode_expr_map_field(#field{name=[K],value=V}) ->
[{<<"key">>,do_encode_expr(K)},
{<<"value">>,do_encode_expr(V)}].
%% decode_contract(JSON) -> ContractString.
%% Decode a JSON string and generate a suitable contract string which
%% can be included in a contract definition. We decode into a map
%% here as this is easier to work with and order is not important.
decode(Json) ->
decode_contract(Json) ->
Map = jsx:decode(Json, [return_maps]),
%% io:format("~p\n", [Map]),
#{<<"contract">> := C} = Map,
list_to_binary(decode_contract(C)).
#{<<"name">> := Name, <<"type_defs">> := Ts, <<"functions">> := Fs} = C,
CS = ["contract"," ",io_lib:format("~s", [Name])," =\n",
do_decode_tdefs(Ts),
do_decode_funcs(Fs)],
list_to_binary(CS).
decode_contract(#{<<"name">> := Name,
<<"type_defs">> := Ts,
<<"functions">> := Fs}) ->
["contract"," ",io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts),
decode_funcs(Fs)].
do_decode_funcs(Fs) -> [ do_decode_func(F) || F <- Fs ].
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
decode_func(#{<<"name">> := <<"init">>}) -> [];
decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
do_decode_func(#{<<"name">> := <<"init">>}) -> [];
do_decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
[" function"," ",io_lib:format("~s", [Name])," : ",
decode_args(As)," => ",decode_type(T),$\n].
do_decode_args(As)," => ",do_decode_type(T),$\n].
decode_args(As) ->
Das = [ decode_arg(A) || A <- As ],
do_decode_args(As) ->
Das = [ do_decode_arg(A) || A <- As ],
[$(,lists:join(", ", Das),$)].
decode_arg(#{<<"type">> := [T]}) -> decode_type(T).
do_decode_arg(#{<<"type">> := T}) -> do_decode_type(T).
decode_types(Ets) ->
[ decode_type(Et) || Et <- Ets ].
do_decode_types(Ets) ->
[ do_decode_type(Et) || Et <- Ets ].
decode_type(#{<<"tuple">> := Ets}) ->
Ts = decode_types(Ets),
[$(,lists:join(",", Ts),$)];
decode_type(#{<<"record">> := Efs}) ->
Fs = decode_fields(Efs),
[${,lists:join(",", Fs),$}];
decode_type(#{<<"list">> := [Et]}) ->
T = decode_type(Et),
do_decode_type(#{<<"tuple">> := Ets}) ->
Ts = do_decode_types(Ets),
[$(,lists:join(", ", Ts),$)];
do_decode_type(#{<<"record">> := Efs}) ->
Fs = do_decode_type_rec_fields(Efs),
[${,lists:join(", ", Fs),$}];
do_decode_type(#{<<"list">> := Et}) ->
T = do_decode_type(Et),
["list",$(,T,$)];
decode_type(#{<<"map">> := Ets}) ->
Ts = decode_types(Ets),
["map",$(,lists:join(",", Ts),$)];
decode_type(#{<<"variant">> := Ets}) ->
Ts = decode_types(Ets),
do_decode_type(#{<<"map">> := Et}) ->
T = do_decode_type_map(Et),
["map",$(,T,$)];
do_decode_type(#{<<"variant">> := Ets}) ->
Ts = do_decode_types(Ets),
lists:join(" | ", Ts);
decode_type(Econs) when is_map(Econs) -> %General constructor
do_decode_type(Econs) when is_map(Econs) -> %General constructor
%% io:format("~p\n", [Econs]),
[{Ec,Ets}] = maps:to_list(Econs),
C = decode_name(Ec),
Ts = decode_types(Ets),
[C,$(,lists:join(",", Ts),$)];
decode_type(T) -> %Just raw names.
decode_name(T).
C = do_decode_name(Ec),
Ts = do_decode_types(Ets),
[C,$(,lists:join(", ", Ts),$)];
do_decode_type(T) -> %Just raw names.
do_decode_name(T).
decode_name(En) ->
do_decode_name(En) ->
binary_to_list(En).
decode_fields(Efs) ->
[ decode_field(Ef) || Ef <- Efs ].
do_decode_type_rec_fields(Efs) ->
[ do_decode_type_rec_field(Ef) || Ef <- Efs ].
decode_field(#{<<"name">> := En,<<"type">> := [Et]}) ->
Name = decode_name(En),
Type = decode_type(Et),
do_decode_type_rec_field(#{<<"name">> := En,<<"type">> := Et}) ->
Name = do_decode_name(En),
Type = do_decode_type(Et),
[Name," : ",Type].
%% decode_tdefs(Json) -> [TypeString].
do_decode_type_map(#{<<"key">> := Ek,<<"value">> := Ev}) ->
Key = do_decode_type(Ek),
Value = do_decode_type(Ev),
[Key,", ",Value].
%% do_decode_tdefs(Json) -> [TypeString].
%% Here we are only interested in the type definitions and ignore the
%% aliases. We find them as they always have variants.
decode_tdefs(Ts) -> [ decode_tdef(T) ||
#{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts
].
do_decode_tdefs(Ts) -> [ do_decode_tdef(T) ||
#{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts
].
decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
[" datatype"," ",decode_name(Name),decode_tvars(Vs),
" = ",decode_type(T),$\n].
do_decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
[" datatype"," ",do_decode_name(Name),do_decode_tvars(Vs),
" = ",do_decode_type(T),$\n].
decode_tvars([]) -> []; %No tvars, no parentheses
decode_tvars(Vs) ->
Dvs = [ decode_tvar(V) || V <- Vs ],
do_decode_tvars([]) -> []; %No tvars, no parentheses
do_decode_tvars(Vs) ->
Dvs = [ do_decode_tvar(V) || V <- Vs ],
[$(,lists:join(", ", Dvs),$)].
decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
do_decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
%% #contract{Ann, Con, [Declarations]}.
+65 -34
View File
@@ -56,7 +56,12 @@
, fields :: [aeso_syntax:id()]
, context :: why_record() }).
-type field_constraint() :: #field_constraint{} | #record_create_constraint{}.
-record(is_contract_constraint,
{ contract_t :: utype(),
context :: aeso_syntax:expr() %% The address literal
}).
-type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}.
-record(field_info,
{ ann :: aeso_syntax:ann()
@@ -341,6 +346,7 @@ global_env() ->
Address = {id, Ann, "address"},
Hash = {id, Ann, "hash"},
Bits = {id, Ann, "bits"},
Bytes = fun(Len) -> {bytes_t, Ann, Len} end,
Oracle = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle"}, [Q, R]} end,
Query = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle_query"}, [Q, R]} end,
Unit = {tuple_t, Ann, []},
@@ -373,7 +379,9 @@ global_env() ->
{"abort", Fun1(String, A)}])
, types = MkDefs(
[{"int", 0}, {"bool", 0}, {"string", 0}, {"address", 0},
{"hash", 0}, {"signature", 0}, {"bits", 0},
{"hash", {[], {alias_t, Bytes(32)}}},
{"signature", {[], {alias_t, Bytes(64)}}},
{"bits", 0},
{"option", 1}, {"list", 1}, {"map", 2},
{"oracle", 2}, {"oracle_query", 2}
]) },
@@ -439,6 +447,7 @@ global_env() ->
CryptoScope = #scope
{ funs = MkDefs(
[{"ecverify", Fun([Hash, Address, SignId], Bool)},
{"ecverify_secp256k1", Fun([Hash, Bytes(64), Bytes(64)], Bool)},
{"sha3", Fun1(A, Hash)},
{"sha256", Fun1(A, Hash)},
{"blake2b", Fun1(A, Hash)}]) },
@@ -497,7 +506,7 @@ map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
infer(Contracts) ->
infer(Contracts, []).
-type option() :: permissive_address_literals | return_env.
-type option() :: return_env.
-spec init_env(list(option())) -> env().
init_env(_Options) -> global_env().
@@ -675,6 +684,9 @@ check_type(Env, X = {Tag, _, _}, Arity) when Tag == con; Tag == qcon; Tag == id;
check_type(Env, Type = {tuple_t, Ann, Types}, Arity) ->
ensure_base_type(Type, Arity),
{tuple_t, Ann, [ check_type(Env, T, 0) || T <- Types ]};
check_type(_Env, Type = {bytes_t, _Ann, _Len}, Arity) ->
ensure_base_type(Type, Arity),
Type;
check_type(Env, {app_t, Ann, Type, Types}, Arity) ->
Types1 = [ check_type(Env, T, 0) || T <- Types ],
Type1 = check_type(Env, Type, Arity + length(Types)),
@@ -898,20 +910,29 @@ infer_expr(_Env, Body={int, As, _}) ->
{typed, As, Body, {id, As, "int"}};
infer_expr(_Env, Body={string, As, _}) ->
{typed, As, Body, {id, As, "string"}};
infer_expr(_Env, Body={hash, As, Hash}) ->
case byte_size(Hash) of
32 -> {typed, As, Body, {id, As, "address"}};
64 -> {typed, As, Body, {id, As, "signature"}}
end;
infer_expr(_Env, Body={bytes, As, Bin}) ->
{typed, As, Body, {bytes_t, As, byte_size(Bin)}};
infer_expr(_Env, Body={account_pubkey, As, _}) ->
{typed, As, Body, {id, As, "address"}};
infer_expr(_Env, Body={oracle_pubkey, As, _}) ->
Q = fresh_uvar(As),
R = fresh_uvar(As),
{typed, As, Body, {app_t, As, {id, As, "oracle"}, [Q, R]}};
infer_expr(_Env, Body={oracle_query_id, As, _}) ->
Q = fresh_uvar(As),
R = fresh_uvar(As),
{typed, As, Body, {app_t, As, {id, As, "oracle_query"}, [Q, R]}};
infer_expr(_Env, Body={contract_pubkey, As, _}) ->
Con = fresh_uvar(As),
constrain([#is_contract_constraint{ contract_t = Con,
context = Body }]),
{typed, As, Body, Con};
infer_expr(_Env, Body={id, As, "_"}) ->
{typed, As, Body, fresh_uvar(As)};
infer_expr(Env, Id = {id, As, _}) ->
infer_expr(Env, Id = {Tag, As, _}) when Tag == id; Tag == qid ->
{QName, Type} = lookup_name(Env, As, Id),
{typed, As, QName, Type};
infer_expr(Env, Id = {qid, As, _}) ->
{QName, Type} = lookup_name(Env, As, Id),
{typed, As, QName, Type};
infer_expr(Env, Id = {con, As, _}) ->
infer_expr(Env, Id = {Tag, As, _}) when Tag == con; Tag == qcon ->
{QName, Type} = lookup_name(Env, As, Id, [freshen]),
{typed, As, QName, Type};
infer_expr(Env, {unit, As}) ->
@@ -1348,6 +1369,16 @@ check_record_create_constraints(Env, [C | Cs]) ->
end,
check_record_create_constraints(Env, Cs).
check_is_contract_constraints(_Env, []) -> ok;
check_is_contract_constraints(Env, [C | Cs]) ->
#is_contract_constraint{ contract_t = Type, context = Lit } = C,
Type1 = unfold_types_in_type(Env, instantiate(Type)),
case lookup_type(Env, record_type_name(Type1)) of
{_, {_Ann, {[], {contract_t, _}}}} -> ok;
_ -> type_error({not_a_contract_type, Type1, Lit})
end,
check_is_contract_constraints(Env, Cs).
-spec solve_field_constraints(env(), [field_constraint()]) -> ok.
solve_field_constraints(Env, Constraints) ->
%% First look for record fields that appear in only one type definition
@@ -1446,9 +1477,12 @@ solve_known_record_types(Env, Constraints) ->
DerefConstraints--SolvedConstraints.
destroy_and_report_unsolved_field_constraints(Env) ->
{FieldCs, CreateCs} =
{FieldCs, OtherCs} =
lists:partition(fun(#field_constraint{}) -> true; (_) -> false end,
get_field_constraints()),
{CreateCs, ContractCs} =
lists:partition(fun(#record_create_constraint{}) -> true; (_) -> false end,
OtherCs),
Unknown = solve_known_record_types(Env, FieldCs),
if Unknown == [] -> ok;
true ->
@@ -1458,6 +1492,7 @@ destroy_and_report_unsolved_field_constraints(Env) ->
end
end,
check_record_create_constraints(Env, CreateCs),
check_is_contract_constraints(Env, ContractCs),
destroy_field_constraints(),
ok.
@@ -1583,6 +1618,8 @@ unfold_types_in_type(Env, {field_t, Attr, Name, Type}, Options) ->
{field_t, Attr, Name, unfold_types_in_type(Env, Type, Options)};
unfold_types_in_type(Env, {constr_t, Ann, Con, Types}, Options) ->
{constr_t, Ann, Con, unfold_types_in_type(Env, Types, Options)};
unfold_types_in_type(Env, {named_arg_t, Ann, Con, Types, Default}, Options) ->
{named_arg_t, Ann, Con, unfold_types_in_type(Env, Types, Options), Default};
unfold_types_in_type(Env, T, Options) when is_tuple(T) ->
list_to_tuple(unfold_types_in_type(Env, tuple_to_list(T), Options));
unfold_types_in_type(Env, [H|T], Options) ->
@@ -1638,6 +1675,8 @@ unify1(_Env, {qid, _, Name}, {qid, _, Name}, _When) ->
true;
unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _When) ->
true;
unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) ->
true;
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) ->
unify(Env, Named1, Named2, When) andalso
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
@@ -1655,26 +1694,8 @@ unify1(Env, {app_t, _, T, []}, B, When) ->
unify1(Env, A, {app_t, _, T, []}, When) ->
unify(Env, A, T, When);
unify1(_Env, A, B, When) ->
Ok =
case get_option(permissive_address_literals, false) of
true ->
Kind = fun({qcon, _, _}) -> con;
({con, _, _}) -> con;
({id, _, "address"}) -> addr;
({id, _, "hash"}) -> hash;
({app_t, _, {id, _, "oracle"}, _}) -> oracle;
({app_t, _, {id, _, "oracle_query"}, _}) -> query;
(_) -> other end,
%% If permissive_address_literals we allow unifying adresses
%% with contract types or oracles/oracle queries
case lists:usort([Kind(A), Kind(B)]) of
[addr, K] -> K /= other;
_ -> false
end;
false -> false
end,
[ cannot_unify(A, B, When) || not Ok ],
Ok.
cannot_unify(A, B, When),
false.
dereference(T = {uvar, _, R}) ->
case ets_lookup(type_vars, R) of
@@ -1703,6 +1724,7 @@ occurs_check1(_, {con, _, _}) -> false;
occurs_check1(_, {qid, _, _}) -> false;
occurs_check1(_, {qcon, _, _}) -> false;
occurs_check1(_, {tvar, _, _}) -> false;
occurs_check1(_, {bytes_t, _, _}) -> false;
occurs_check1(R, {fun_t, _, Named, Args, Res}) ->
occurs_check(R, [Res, Named | Args]);
occurs_check1(R, {app_t, _, T, Ts}) ->
@@ -1819,6 +1841,11 @@ pp_error({undefined_field, Id}) ->
io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]);
pp_error({not_a_record_type, Type, Why}) ->
io_lib:format("~s\n~s\n", [pp_type("Not a record type: ", Type), pp_why_record(Why)]);
pp_error({not_a_contract_type, Type, Lit}) ->
io_lib:format("The type ~s is not a contract type\n"
"when checking that the contract literal at ~s\n~s\n"
"has the type\n~s\n",
[pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]);
pp_error({non_linear_pattern, Pattern, Nonlinear}) ->
Plural = [ $s || length(Nonlinear) > 1 ],
io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n",
@@ -2043,6 +2070,8 @@ pp({qid, _, Name}) ->
string:join(Name, ".");
pp({con, _, Name}) ->
Name;
pp({qcon, _, Name}) ->
string:join(Name, ".");
pp({uvar, _, Ref}) ->
%% Show some unique representation
["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ];
@@ -2050,6 +2079,8 @@ pp({tvar, _, Name}) ->
Name;
pp({tuple_t, _, Cpts}) ->
["(", pp(Cpts), ")"];
pp({bytes_t, _, Len}) ->
["bytes(", integer_to_list(Len), ")"];
pp({app_t, _, T, []}) ->
pp(T);
pp({app_t, _, Type, Args}) ->
+21 -9
View File
@@ -346,6 +346,11 @@ ast_body(?qid_app(["Crypto", "ecverify"], [Msg, PK, Sig], _, _), Icode) ->
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[word, word, sign_t()], word);
ast_body(?qid_app(["Crypto", "ecverify_secp256k1"], [Msg, PK, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[bytes_t(32), bytes_t(64), bytes_t(64)], word);
ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode);
ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) ->
@@ -416,14 +421,17 @@ ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints
#integer{value = Value};
ast_body({int, _, Value}, _Icode) ->
#integer{value = Value};
ast_body({hash, _, Hash}, _Icode) ->
case Hash of
<<Value:32/unit:8>> -> %% address
#integer{value = Value};
<<Hi:32/unit:8, Lo:32/unit:8>> -> %% signature
#tuple{cpts = [#integer{value = Hi},
#integer{value = Lo}]}
ast_body({bytes, _, Bin}, _Icode) ->
case aeb_memory:binary_to_words(Bin) of
[Word] -> #integer{value = Word};
Words -> #tuple{cpts = [#integer{value = W} || W <- Words]}
end;
ast_body({Key, _, Bin}, _Icode) when Key == account_pubkey;
Key == contract_pubkey;
Key == oracle_pubkey;
Key == oracle_query_id ->
<<Value:32/unit:8>> = Bin,
#integer{value = Value};
ast_body({string,_,Bin}, _Icode) ->
Cpts = [size(Bin) | aeb_memory:binary_to_words(Bin)],
#tuple{cpts = [#integer{value=X} || X <- Cpts]};
@@ -688,6 +696,8 @@ ast_typerep({qid, _, Name}, Icode) ->
lookup_type_id(Name, [], Icode);
ast_typerep({con, _, _}, _) ->
word; %% Contract type
ast_typerep({bytes_t, _, Len}, _) ->
{bytes, Len};
ast_typerep({app_t, _, {id, _, Name}, Args}, Icode) ->
ArgReps = [ ast_typerep(Arg, Icode) || Arg <- Args ],
lookup_type_id(Name, ArgReps, Icode);
@@ -715,8 +725,8 @@ ast_typerep({variant_t, Cons}, Icode) ->
ttl_t(Icode) ->
ast_typerep({qid, [], ["Chain", "ttl"]}, Icode).
sign_t() ->
{tuple, [word, word]}.
sign_t() -> bytes_t(64).
bytes_t(Len) -> {bytes, Len}.
get_signature_arg(Args0) ->
NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0],
@@ -750,6 +760,8 @@ type_value({list, A}) ->
type_value({tuple, As}) ->
#tuple{ cpts = [#integer{ value = ?TYPEREP_TUPLE_TAG },
#list{ elems = [ type_value(A) || A <- As ] }] };
type_value({bytes, Len}) ->
#tuple{ cpts = [#integer{ value = ?TYPEREP_BYTES_TAG }, #integer{ value = Len }] };
type_value({variant, Cs}) ->
#tuple{ cpts = [#integer{ value = ?TYPEREP_VARIANT_TAG },
#list{ elems = [ #list{ elems = [ type_value(A) || A <- As ] } || As <- Cs ] }] };
+21 -16
View File
@@ -104,13 +104,12 @@ from_string(ContractString, Options) ->
%% General programming errors in the compiler just signal error.
end.
-spec string_to_icode(string(), [option() | permissive_address_literals]) -> map().
string_to_icode(ContractString, Options0) ->
{InferOptions, Options} = lists:partition(fun(Opt) -> Opt == permissive_address_literals end, Options0),
-spec string_to_icode(string(), [option()]) -> map().
string_to_icode(ContractString, Options) ->
Ast = parse(ContractString, Options),
pp_sophia_code(Ast, Options),
pp_ast(Ast, Options),
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | InferOptions]),
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]),
pp_typed_ast(TypedAst, Options),
Icode = ast_to_icode(TypedAst, Options),
pp_icode(Icode, Options),
@@ -151,11 +150,11 @@ check_call(Source, FunName, Args, Options) ->
check_call(ContractString0, FunName, Args, Options, PatchFun) ->
try
%% First check the contract without the __call function and no permissive literals
%% First check the contract without the __call function
#{} = string_to_icode(ContractString0, Options),
ContractString = insert_call_function(ContractString0, FunName, Args, Options),
#{typed_ast := TypedAst,
icode := Icode} = string_to_icode(ContractString, [permissive_address_literals | Options]),
icode := Icode} = string_to_icode(ContractString, Options),
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
RetVMType = case RetType of
@@ -257,19 +256,21 @@ to_sophia_value(ContractString, FunName, ok, Data, Options) ->
fun (E) -> io_lib:format("~p", [E]) end)}
end.
address_literal(N) -> {hash, [], <<N:256>>}. % TODO
address_literal(Type, N) -> {Type, [], <<N:256>>}.
%% TODO: somewhere else
-spec translate_vm_value(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
translate_vm_value(word, {id, _, "address"}, N) -> address_literal(N);
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(N);
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(N);
translate_vm_value(word, {id, _, "hash"}, N) -> {hash, [], <<N:256>>};
translate_vm_value(word, {id, _, "int"}, N) -> {int, [], N};
translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, N});
translate_vm_value(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
translate_vm_value({tuple, [word, word]}, {id, _, "signature"}, {tuple, [Hi, Lo]}) ->
{hash, [], <<Hi:256, Lo:256>>};
translate_vm_value(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
translate_vm_value(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
translate_vm_value(word, {id, _, "int"}, N) -> {int, [], N};
translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, N});
translate_vm_value(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
translate_vm_value({bytes, Len}, {bytes_t, _, Len}, Val) when Len =< 32 ->
{bytes, [], <<Val:Len/unit:8>>};
translate_vm_value({bytes, Len}, {bytes_t, _, Len}, Val) ->
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
translate_vm_value(string, {id, _, "string"}, S) -> {string, [], S};
translate_vm_value({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
{list, [], [translate_vm_value(VmType, Type, X) || X <- List]};
@@ -402,6 +403,10 @@ icode_to_term(word, {integer, N}) -> N;
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
Str;
icode_to_term({bytes, Len}, {integer, Value}) when Len =< 32 ->
Value;
icode_to_term({bytes, Len}, {tuple, Words}) when Len > 32->
list_to_tuple([W || {integer, W} <- Words]);
icode_to_term({list, T}, {list, Vs}) ->
[ icode_to_term(T, V) || V <- Vs ];
icode_to_term({tuple, Ts}, {tuple, Vs}) ->
+29 -5
View File
@@ -136,11 +136,15 @@ type200() ->
type300() -> type400().
type400() ->
?RULE(typeAtom(), optional(type_args()),
choice(
[?RULE(typeAtom(), optional(type_args()),
case _2 of
none -> _1;
{ok, Args} -> {app_t, get_ann(_1), _1, Args}
end).
end),
?RULE(id("bytes"), parens(token(int)),
{bytes_t, get_ann(_1), element(3, _2)})
]).
typeAtom() ->
?LAZY_P(choice(
@@ -203,8 +207,8 @@ exprAtom() ->
?LAZY_P(begin
Expr = ?LAZY_P(expr()),
choice(
[ id(), con(), token(qid), token(qcon)
, token(hash), token(string), token(char)
[ id_or_addr(), con(), token(qid), token(qcon)
, token(bytes), token(string), token(char)
, token(int)
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
, {bool, keyword(true), true}
@@ -326,6 +330,26 @@ token(Tag) ->
{Tok, {Line, Col}, Val} -> {Tok, pos_ann(Line, Col), Val}
end).
id(Id) ->
?LET_P({id, A, X} = Y, id(),
if X == Id -> Y;
true -> fail({A, "expected 'bytes'"})
end).
id_or_addr() ->
?RULE(id(), parse_addr_literal(_1)).
parse_addr_literal(Id = {id, Ann, Name}) ->
case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_"]) of
false -> Id;
true ->
try aeser_api_encoder:decode(list_to_binary(Name)) of
{Type, Bin} -> {Type, Ann, Bin}
catch _:_ ->
Id
end
end.
%% -- Helpers ----------------------------------------------------------------
keyword(K) -> ann(tok(K)).
@@ -457,7 +481,7 @@ parse_pattern(E = {id, _, _}) -> E;
parse_pattern(E = {unit, _}) -> E;
parse_pattern(E = {int, _, _}) -> E;
parse_pattern(E = {bool, _, _}) -> E;
parse_pattern(E = {hash, _, _}) -> E;
parse_pattern(E = {bytes, _, _}) -> E;
parse_pattern(E = {string, _, _}) -> E;
parse_pattern(E = {char, _, _}) -> E;
parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
+12 -1
View File
@@ -236,6 +236,8 @@ type({app_t, _, Type, Args}) ->
beside(type(Type), tuple_type(Args));
type({tuple_t, _, Args}) ->
tuple_type(Args);
type({bytes_t, _, Len}) ->
text(lists:concat(["bytes(", Len, ")"]));
type({named_arg_t, _, Name, Type, _Default}) ->
%% Drop the default value
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
@@ -319,8 +321,17 @@ expr_p(_, E = {int, _, N}) ->
end,
text(S);
expr_p(_, {bool, _, B}) -> text(atom_to_list(B));
expr_p(_, {hash, _, <<N:256>>}) -> text("#" ++ integer_to_list(N, 16));
expr_p(_, {bytes, _, Bin}) ->
Digits = byte_size(Bin),
<<N:Digits/unit:8>> = Bin,
text(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N])));
expr_p(_, {hash, _, <<N:512>>}) -> text("#" ++ integer_to_list(N, 16));
expr_p(_, {Type, _, Bin})
when Type == account_pubkey;
Type == contract_pubkey;
Type == oracle_pubkey;
Type == oracle_query_id ->
text(binary_to_list(aeser_api_encoder:encode(Type, Bin)));
expr_p(_, {unit, _}) -> text("()");
expr_p(_, {string, _, S}) -> term(binary_to_list(S));
expr_p(_, {char, _, C}) ->
+6 -8
View File
@@ -20,7 +20,7 @@ lexer() ->
CON = [UPPER, "[a-zA-Z0-9_]*"],
INT = [DIGIT, "+"],
HEX = ["0x", HEXDIGIT, "+"],
HASH = ["#", HEXDIGIT, "+"],
BYTES = ["#", HEXDIGIT, "+"],
WS = "[\\000-\\ ]+",
ID = [LOWER, "[a-zA-Z0-9_']*"],
TVAR = ["'", ID],
@@ -54,7 +54,7 @@ lexer() ->
, {STRING, token(string, fun parse_string/1)}
, {HEX, token(hex, fun parse_hex/1)}
, {INT, token(int, fun list_to_integer/1)}
, {HASH, token(hash, fun parse_hash/1)}
, {BYTES, token(bytes, fun parse_bytes/1)}
%% Identifiers (qualified first!)
, {QID, token(qid, fun(S) -> string:tokens(S, ".") end)}
@@ -117,10 +117,8 @@ unescape([C | Chars], Acc) ->
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
parse_hash("#" ++ Chars) ->
N = list_to_integer(Chars, 16),
case length(Chars) > 64 of %% 64 hex digits = 32 bytes
true -> <<N:64/unit:8>>; %% signature
false -> <<N:32/unit:8>> %% address
end.
parse_bytes("#" ++ Chars) ->
N = list_to_integer(Chars, 16),
Digits = (length(Chars) + 1) div 2,
<<N:Digits/unit:8>>.
+5
View File
@@ -60,6 +60,7 @@
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
| {app_t, ann(), type(), [type()]}
| {tuple_t, ann(), [type()]}
| {bytes_t, ann(), integer()}
| id() | qid()
| con() | qcon() %% contracts
| tvar().
@@ -70,6 +71,10 @@
:: {int, ann(), integer()}
| {bool, ann(), true | false}
| {hash, ann(), binary()}
| {account_pubkey, binary()}
| {contract_pubkey, binary()}
| {oracle_pubkey, binary()}
| {oracle_query_id, binary()}
| {unit, ann()}
| {string, ann(), binary()}
| {char, ann(), integer()}.
+18 -3
View File
@@ -4,6 +4,9 @@
-compile(export_all).
-define(SANDBOX(Code), sandbox(fun() -> Code end)).
-define(DUMMY_HASH_WORD, 16#123).
-define(DUMMY_HASH, <<0:30/unit:8, 127, 119>>). %% 16#123
-define(DUMMY_HASH_LIT, "#0000000000000000000000000000000000000000000000000000000000000123").
sandbox(Code) ->
Parent = self(),
@@ -97,7 +100,15 @@ calldata_test() ->
Map = #{ <<"a">> => 4 },
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
[16#123, 16#456] = encode_decode_calldata("foo", ["hash", "address"], ["#123", "#456"]),
[?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"],
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
[?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] =
encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]),
[119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]),
[16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
ok.
calldata_init_test() ->
@@ -126,7 +137,9 @@ parameterized_contract(FunName, Types) ->
parameterized_contract(ExtraCode, FunName, Types) ->
lists:flatten(
["contract Dummy =\n",
["contract Remote =\n"
" function bla : () => ()\n\n"
"contract Dummy =\n",
ExtraCode, "\n",
" type an_alias('a) = (string, 'a)\n"
" record r = {x : an_alias(int), y : variant}\n"
@@ -139,7 +152,9 @@ oracle_test() ->
" function question(o, q : oracle_query(list(string), option(int))) =\n"
" Oracle.get_question(o, q)\n",
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
aeso_compiler:check_call(Contract, "question", ["#123", "#456"], []),
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
"oq_1111111111111111111111111111113AFEFpt5"], []),
ok.
permissive_literals_fail_test() ->
+3 -3
View File
@@ -24,7 +24,7 @@ test_cases(1) ->
[#{<<"name">> => <<"a">>,
<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> => [<<"int">>]}],
<<"type">> => <<"int">>}],
<<"returns">> => <<"int">>,
<<"stateful">> => false}]}},
DecACI = <<"contract C =\n"
@@ -44,7 +44,7 @@ test_cases(2) ->
<<"functions">> =>
[#{<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> => [<<"int">>]}],
<<"type">> => <<"int">>}],
<<"name">> => <<"a">>,
<<"returns">> => <<"int">>,
<<"stateful">> => false}]}},
@@ -60,7 +60,7 @@ test_cases(3) ->
[#{<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> =>
[#{<<"C.bert">> => [<<"string">>]}]}],
#{<<"C.bert">> => [<<"string">>]}}],
<<"name">> => <<"a">>,<<"returns">> => <<"int">>,
<<"stateful">> => false}],
<<"name">> => <<"C">>,
+89 -5
View File
@@ -8,6 +8,8 @@
-module(aeso_compiler_tests).
-compile([export_all, nowarn_export_all]).
-include_lib("eunit/include/eunit.hrl").
%% Very simply test compile the given contracts. Only basic checks
@@ -16,10 +18,14 @@
simple_compile_test_() ->
[ {"Testing the " ++ ContractName ++ " contract",
fun() ->
#{byte_code := ByteCode,
contract_source := _,
type_info := _} = compile(ContractName),
?assertMatch(Code when is_binary(Code), ByteCode)
case compile(ContractName) of
#{byte_code := ByteCode,
contract_source := _,
type_info := _} -> ?assertMatch(Code when is_binary(Code), ByteCode);
ErrBin ->
io:format("\n~s", [ErrBin]),
error(ErrBin)
end
end} || ContractName <- compilable_contracts() ] ++
[ {"Testing error messages of " ++ ContractName,
fun() ->
@@ -98,7 +104,9 @@ compilable_contracts() ->
"state_handling",
"events",
"include",
"basic_auth"
"basic_auth",
"bitcoin_auth",
"address_literals"
].
%% Contracts that should produce type errors
@@ -218,4 +226,80 @@ failing_contracts() ->
, {"bad_include_and_ns",
[<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
<<"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]}
, {"bad_address_literals",
[<<"The type bytes(32) is not a contract type\n"
"when checking that the contract literal at line 32, column 5\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n"
" bytes(32)">>,
<<"The type oracle(int, bool) is not a contract type\n"
"when checking that the contract literal at line 30, column 5\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n"
" oracle(int, bool)">>,
<<"The type address is not a contract type\n"
"when checking that the contract literal at line 28, column 5\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n"
" address">>,
<<"Cannot unify oracle_query('1, '2)\n"
" and Remote\n"
"when checking the type of the expression at line 25, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('1, '2)\n"
"against the expected type\n"
" Remote">>,
<<"Cannot unify oracle_query('3, '4)\n"
" and bytes(32)\n"
"when checking the type of the expression at line 23, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('3, '4)\n"
"against the expected type\n"
" bytes(32)">>,
<<"Cannot unify oracle_query('5, '6)\n"
" and oracle(int, bool)\n"
"when checking the type of the expression at line 21, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('5, '6)\n"
"against the expected type\n"
" oracle(int, bool)">>,
<<"Cannot unify oracle('7, '8)\n"
" and Remote\n"
"when checking the type of the expression at line 18, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('7, '8)\n"
"against the expected type\n"
" Remote">>,
<<"Cannot unify oracle('9, '10)\n"
" and bytes(32)\n"
"when checking the type of the expression at line 16, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('9, '10)\n"
"against the expected type\n"
" bytes(32)">>,
<<"Cannot unify oracle('11, '12)\n"
" and oracle_query(int, bool)\n"
"when checking the type of the expression at line 14, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('11, '12)\n"
"against the expected type\n"
" oracle_query(int, bool)">>,
<<"Cannot unify address\n"
" and oracle(int, bool)\n"
"when checking the type of the expression at line 11, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n"
" oracle(int, bool)">>,
<<"Cannot unify address\n"
" and Remote\n"
"when checking the type of the expression at line 9, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n"
" Remote">>,
<<"Cannot unify address\n"
" and bytes(32)\n"
"when checking the type of the expression at line 7, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n"
" bytes(32)">>]}
].
+2 -2
View File
@@ -48,7 +48,7 @@ all_tokens() ->
%% Literals
[ Lit(true), Lit(false)
, Tok(id, "foo"), Tok(id, "_"), Tok(con, "Foo")
, Tok(hash, Hash)
, Tok(bytes, Hash)
, Tok(int, 1234567890), Tok(hex, 9876543210)
, Tok(string, <<"bla\"\\\b\e\f\n\r\t\vbla">>)
].
@@ -78,7 +78,7 @@ show_token({param, _, P}) -> "@" ++ P;
show_token({string, _, S}) -> fmt(binary_to_list(S));
show_token({int, _, N}) -> fmt(N);
show_token({hex, _, N}) -> fmt("0x~.16b", N);
show_token({hash, _, <<N:256>>}) -> fmt("#~.16b", N);
show_token({bytes, _, <<N:256>>}) -> fmt("#~64.16.0b", N);
show_token({comment, _, S}) -> S;
show_token({_, _, _}) -> "TODO".
+14
View File
@@ -0,0 +1,14 @@
contract Remote =
function foo : () => ()
contract AddressLiterals =
function addr() : address =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
function oracle() : oracle(int, bool) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
function query() : oracle_query(int, bool) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
function contr() : Remote =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
+33
View File
@@ -0,0 +1,33 @@
contract Remote =
function foo : () => ()
contract AddressLiterals =
function addr1() : bytes(32) =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
function addr2() : Remote =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
function addr3() : oracle(int, bool) =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
function oracle1() : oracle_query(int, bool) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
function oracle2() : bytes(32) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
function oracle3() : Remote =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
function query1() : oracle(int, bool) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
function query2() : bytes(32) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
function query3() : Remote =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
function contr1() : address =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
function contr2() : oracle(int, bool) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
function contr3() : bytes(32) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
+18
View File
@@ -0,0 +1,18 @@
contract BitcoinAuth =
record state = { nonce : int, owner : bytes(64) }
function init(owner' : bytes(64)) = { nonce = 1, owner = owner' }
function authorize(n : int, s : signature) : bool =
require(n >= state.nonce, "Nonce too low")
require(n =< state.nonce, "Nonce too high")
put(state{ nonce = n + 1 })
switch(Auth.tx_hash)
None => abort("Not in Auth context")
Some(tx_hash) => Crypto.ecverify_secp256k1(to_sign(tx_hash, n), state.owner, s)
function to_sign(h : hash, n : int) : hash =
Crypto.blake2b((h, n))
private function require(b : bool, err : string) =
if(!b) abort(err)
+1 -1
View File
@@ -53,7 +53,7 @@ contract FundMe =
require(state.total >= state.goal, "Project was not funded")
spend({recipient = state.beneficiary,
amount = Contract.balance })
put(state{ beneficiary = #0 })
put(state{ beneficiary = ak_11111111111111111111111111111111273Yts })
private stateful function withdraw_contributor() =
if(state.total >= state.goal)