Merge branch 'master' into quickcheck-ci
This commit is contained in:
commit
be9935cd7e
37
.circleci/config.yml
Normal file
37
.circleci/config.yml
Normal file
@ -0,0 +1,37 @@
|
||||
version: 2.1
|
||||
|
||||
executors:
|
||||
aebuilder:
|
||||
docker:
|
||||
- image: aeternity/builder
|
||||
user: builder
|
||||
working_directory: ~/aesophia
|
||||
|
||||
jobs:
|
||||
build:
|
||||
executor: aebuilder
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- dialyzer-cache-v2-{{ .Branch }}-{{ .Revision }}
|
||||
- dialyzer-cache-v2-{{ .Branch }}-
|
||||
- dialyzer-cache-v2-
|
||||
- run:
|
||||
name: Build
|
||||
command: rebar3 compile
|
||||
- run:
|
||||
name: Static Analysis
|
||||
command: rebar3 dialyzer
|
||||
- run:
|
||||
name: Eunit
|
||||
command: rebar3 eunit
|
||||
- run:
|
||||
name: Common Tests
|
||||
command: rebar3 ct
|
||||
- save_cache:
|
||||
key: dialyzer-cache-v2-{{ .Branch }}-{{ .Revision }}
|
||||
paths:
|
||||
- _build/default/rebar3_20.3.8_plt
|
||||
- store_artifacts:
|
||||
path: _build/test/logs
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -18,3 +18,6 @@ _build
|
||||
rebar3.crashdump
|
||||
current_counterexample.eqc
|
||||
.qcci
|
||||
*.erl~
|
||||
*.aes~
|
||||
|
||||
|
@ -6,5 +6,11 @@ For more information about æternity smart contracts and the sophia language see
|
||||
|
||||
It is an OTP application written in Erlang and is by default included in
|
||||
[the æternity node](https://github.com/aeternity/epoch). However, it can
|
||||
also be included in other system to compile contracts coded in sophia which
|
||||
also be included in other systems to compile contracts coded in sophia which
|
||||
can then be loaded into the æternity system.
|
||||
|
||||
## Interface Modules
|
||||
|
||||
The basic modules for interfacing the compiler:
|
||||
|
||||
* [aeso_compiler: the Sophia compiler](./docs/aeso_compiler.md)
|
||||
|
86
docs/aeso_compiler.md
Normal file
86
docs/aeso_compiler.md
Normal file
@ -0,0 +1,86 @@
|
||||
# aeso_compiler
|
||||
|
||||
### Module
|
||||
|
||||
### aeso_compiler
|
||||
|
||||
The Sophia compiler
|
||||
|
||||
### Description
|
||||
|
||||
This module provides the interface to the standard Sophia compiler. It
|
||||
returns the compiled module in a map which can then be loaded.
|
||||
|
||||
### Types
|
||||
``` erlang
|
||||
contract_string() = string() | binary()
|
||||
contract_map() = #{bytecode => binary(),
|
||||
compiler_version => string(),
|
||||
contract_souce => string(),
|
||||
type_info => type_info()}
|
||||
type_info()
|
||||
errorstring() = binary()
|
||||
```
|
||||
### Exports
|
||||
|
||||
#### file(File)
|
||||
#### file(File, Options) -> CompRet
|
||||
#### from_string(ContractString, Options) -> CompRet
|
||||
|
||||
Types
|
||||
|
||||
``` erlang
|
||||
ContractString = contract_string()
|
||||
Options = [Option]
|
||||
CompRet = {ok,ContractMap} | {error,ErrorString}
|
||||
ContractMap = contract_map()
|
||||
ErrorString = errorstring()
|
||||
```
|
||||
|
||||
Compile a contract defined in a file or in a string.
|
||||
|
||||
The **pp_** options all print to standard output the following:
|
||||
|
||||
`pp_sophia_code` - print the input Sophia code.
|
||||
|
||||
`pp_ast` - print the AST of the code
|
||||
|
||||
`pp_types` - print information about the types
|
||||
|
||||
`pp_typed_ast` - print the AST with type information at each node
|
||||
|
||||
`pp_icode` - print the internal code structure
|
||||
|
||||
`pp_assembler` - print the generated assembler code
|
||||
|
||||
`pp_bytecode` - print the bytecode instructions
|
||||
|
||||
#### check_call(ContractString, Options) -> CheckRet
|
||||
|
||||
Types
|
||||
```
|
||||
ContractString = string() | binary()
|
||||
CheckRet = {ok,string(),{Types,Type | any()},Terms} | {error,Term}
|
||||
Types = [Type]
|
||||
Type = term()
|
||||
```
|
||||
Check a call in contract through the `__call` function.
|
||||
|
||||
#### sophia_type_to_typerep(String) -> TypeRep
|
||||
|
||||
Types
|
||||
``` erlang
|
||||
{ok,TypeRep} | {error, badtype}
|
||||
```
|
||||
|
||||
Get the type representation of a type declaration.
|
||||
|
||||
#### version() -> Version
|
||||
|
||||
Types
|
||||
|
||||
``` erlang
|
||||
Version = integer()
|
||||
```
|
||||
|
||||
Get the current version of the Sophia compiler.
|
18
rebar.config
18
rebar.config
@ -1,16 +1,32 @@
|
||||
{erl_opts, [debug_info]}.
|
||||
|
||||
%% NOTE: When possible deps are referenced by Git ref to ensure consistency between builds.
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
|
||||
{ref,"720510a"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
]}.
|
||||
|
||||
|
||||
{profiles, [ {eqc, [{erl_opts, [{parse_transform, eqc_cover}]},
|
||||
{deps, [{eqc_ci, "1.0.0"}]},
|
||||
{extra_src_dirs, ["quickcheck"]} %% May not be called eqc!
|
||||
]}
|
||||
]}.
|
||||
|
||||
{escript_incl_apps, [aesophia, aebytecode, getopt]}.
|
||||
{escript_main_app, aesophia}.
|
||||
{escript_name, aesophia}.
|
||||
{escript_emu_args, "%%! +sbtu +A0\n"}.
|
||||
{provider_hooks, [{post, [{compile, escriptize}]}]}.
|
||||
|
||||
{post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)",
|
||||
escriptize,
|
||||
"cp \"$REBAR_BUILD_DIR/bin/aesophia\" ./aesophia"},
|
||||
{"win32",
|
||||
escriptize,
|
||||
"robocopy \"%REBAR_BUILD_DIR%/bin/\" ./ aesophia* "
|
||||
"/njs /njh /nfl /ndl & exit /b 0"} % silence things
|
||||
]}.
|
||||
|
||||
{dialyzer, [
|
||||
{warnings, [unknown]},
|
||||
{plt_apps, all_deps},
|
||||
|
@ -1,4 +1,10 @@
|
||||
{"1.1.0",
|
||||
[{<<"aebytecode">>,
|
||||
{git,"https://github.com/aeternity/aebytecode.git",
|
||||
{ref,"720510a24de32c9bad6486f34ca7babde124bf1e"}},
|
||||
0}].
|
||||
0},
|
||||
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}]}.
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
|
||||
].
|
||||
|
@ -27,8 +27,8 @@
|
||||
-type typerep() :: aeso_sophia:type().
|
||||
-type function_type_info() :: { FunctionHash :: hash()
|
||||
, FunctionName :: function_name()
|
||||
, ArgType :: aeso_sophia:heap() %% binary typerep
|
||||
, OutType :: aeso_sophia:heap() %% binary typerep
|
||||
, ArgType :: binary() %% binary typerep
|
||||
, OutType :: binary() %% binary typerep
|
||||
}.
|
||||
-type type_info() :: [function_type_info()].
|
||||
|
||||
@ -84,8 +84,8 @@ check_given_type(FunName, GivenArgs, GivenRet, CalldataType, ExpectRet) ->
|
||||
{expected, ExpectArgs, '=>', ExpectRet}}}
|
||||
end.
|
||||
|
||||
-spec check_calldata(aeso_sophia:heap(), type_info()) ->
|
||||
{'ok', typerep()} | {'error', atom()}.
|
||||
-spec check_calldata(binary(), type_info()) ->
|
||||
{'ok', typerep(), typerep()} | {'error', atom()}.
|
||||
check_calldata(CallData, TypeInfo) ->
|
||||
%% The first element of the CallData should be the function name
|
||||
case get_function_hash_from_calldata(CallData) of
|
||||
@ -153,7 +153,7 @@ arg_typerep_from_function(Function, TypeInfo) ->
|
||||
end.
|
||||
|
||||
-spec typereps_from_type_hash(hash(), type_info()) ->
|
||||
{'ok', typerep()} | {'error', 'bad_type_data' | 'unknown_function'}.
|
||||
{'ok', typerep(), typerep()} | {'error', 'bad_type_data' | 'unknown_function'}.
|
||||
typereps_from_type_hash(TypeHash, TypeInfo) ->
|
||||
case lists:keyfind(TypeHash, 1, TypeInfo) of
|
||||
{TypeHash,_Function, ArgTypeBin, OutTypeBin} ->
|
||||
|
@ -17,11 +17,12 @@ line({symbol, Line, _}) -> Line.
|
||||
symbol_name({symbol, _, Name}) -> Name.
|
||||
|
||||
pp(Ast) ->
|
||||
%% TODO: Actually do *Pretty* printing.
|
||||
io:format("~p~n", [Ast]).
|
||||
%% io:format("Tree:\n~p\n",[Ast]),
|
||||
String = prettypr:format(aeso_pretty:decls(Ast, [])),
|
||||
io:format("Ast:\n~s\n", [String]).
|
||||
|
||||
pp_typed(TypedAst) ->
|
||||
String = prettypr:format(aeso_pretty:decls(TypedAst, [show_generated])),
|
||||
%% io:format("Typed tree:\n~p\n",[TypedAst]),
|
||||
io:format("Type info:\n~s\n",[String]).
|
||||
String = prettypr:format(aeso_pretty:decls(TypedAst, [show_generated])),
|
||||
io:format("Type ast:\n~s\n",[String]).
|
||||
|
||||
|
@ -79,6 +79,7 @@ global_env() ->
|
||||
Event = {id, Ann, "event"},
|
||||
State = {id, Ann, "state"},
|
||||
Hash = {id, Ann, "hash"},
|
||||
Bits = {id, Ann, "bits"},
|
||||
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, []},
|
||||
@ -159,6 +160,16 @@ global_env() ->
|
||||
{["String", "sha3"], Fun1(String, Hash)},
|
||||
{["String", "sha256"], Fun1(String, Hash)},
|
||||
{["String", "blake2b"], Fun1(String, Hash)},
|
||||
%% Bits
|
||||
{["Bits", "set"], Fun([Bits, Int], Bits)},
|
||||
{["Bits", "clear"], Fun([Bits, Int], Bits)},
|
||||
{["Bits", "test"], Fun([Bits, Int], Bool)},
|
||||
{["Bits", "sum"], Fun1(Bits, Int)},
|
||||
{["Bits", "intersection"], Fun([Bits, Bits], Bits)},
|
||||
{["Bits", "union"], Fun([Bits, Bits], Bits)},
|
||||
{["Bits", "difference"], Fun([Bits, Bits], Bits)},
|
||||
{["Bits", "none"], Bits},
|
||||
{["Bits", "all"], Bits},
|
||||
%% Conversion
|
||||
{["Int", "to_str"], Fun1(Int, String)},
|
||||
{["Address", "to_str"], Fun1(Address, String)}
|
||||
@ -691,8 +702,7 @@ infer_infix({BoolOp, As})
|
||||
{fun_t, As, [], [Bool,Bool], Bool};
|
||||
infer_infix({IntOp, As})
|
||||
when IntOp == '+'; IntOp == '-'; IntOp == '*'; IntOp == '/';
|
||||
IntOp == '^'; IntOp == 'mod'; IntOp == 'bsl'; IntOp == 'bsr';
|
||||
IntOp == 'band'; IntOp == 'bor'; IntOp == 'bxor' ->
|
||||
IntOp == '^'; IntOp == 'mod' ->
|
||||
Int = {id, As, "int"},
|
||||
{fun_t, As, [], [Int, Int], Int};
|
||||
infer_infix({RelOp, As})
|
||||
@ -714,8 +724,7 @@ infer_infix({'++', As}) ->
|
||||
infer_prefix({'!',As}) ->
|
||||
Bool = {id, As, "bool"},
|
||||
{fun_t, As, [], [Bool], Bool};
|
||||
infer_prefix({IntOp,As})
|
||||
when IntOp =:= '-'; IntOp =:= 'bnot' ->
|
||||
infer_prefix({IntOp,As}) when IntOp =:= '-' ->
|
||||
Int = {id, As, "int"},
|
||||
{fun_t, As, [], [Int], Int}.
|
||||
|
||||
|
@ -348,6 +348,33 @@ ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) ->
|
||||
ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) ->
|
||||
#unop{ op = 'sha3', rand = ast_body(String, Icode) };
|
||||
|
||||
%% -- Bits
|
||||
ast_body(?qid_app(["Bits", Fun], Args, _, _), Icode)
|
||||
when Fun == "test"; Fun == "set"; Fun == "clear";
|
||||
Fun == "union"; Fun == "intersection"; Fun == "difference" ->
|
||||
C = fun(N) when is_integer(N) -> #integer{ value = N };
|
||||
(X) -> X end,
|
||||
Bin = fun(O) -> fun(A, B) -> #binop{ op = O, left = C(A), right = C(B) } end end,
|
||||
And = Bin('band'),
|
||||
Or = Bin('bor'),
|
||||
Bsl = fun(A, B) -> (Bin('bsl'))(B, A) end, %% flipped arguments
|
||||
Bsr = fun(A, B) -> (Bin('bsr'))(B, A) end,
|
||||
Neg = fun(A) -> #unop{ op = 'bnot', rand = C(A) } end,
|
||||
case [Fun | [ ast_body(Arg, Icode) || Arg <- Args ]] of
|
||||
["test", Bits, Ix] -> And(Bsr(Bits, Ix), 1);
|
||||
["set", Bits, Ix] -> Or(Bits, Bsl(1, Ix));
|
||||
["clear", Bits, Ix] -> And(Bits, Neg(Bsl(1, Ix)));
|
||||
["union", A, B] -> Or(A, B);
|
||||
["intersection", A, B] -> And(A, B);
|
||||
["difference", A, B] -> And(A, Neg(And(A, B)))
|
||||
end;
|
||||
ast_body({qid, _, ["Bits", "none"]}, _Icode) ->
|
||||
#integer{ value = 0 };
|
||||
ast_body({qid, _, ["Bits", "all"]}, _Icode) ->
|
||||
#integer{ value = 1 bsl 256 - 1 };
|
||||
ast_body(?qid_app(["Bits", "sum"], [Bits], _, _), Icode) ->
|
||||
builtin_call(popcount, [ast_body(Bits, Icode), #integer{ value = 0 }]);
|
||||
|
||||
%% -- Conversion
|
||||
ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) ->
|
||||
builtin_call(int_to_str, [ast_body(Int, Icode)]);
|
||||
@ -526,9 +553,6 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode)
|
||||
ast_binop('++', _, A, B, Icode) ->
|
||||
#funcall{ function = #var_ref{ name = {builtin, list_concat} },
|
||||
args = [ast_body(A, Icode), ast_body(B, Icode)] };
|
||||
%% Bit shift operations takes their arguments backwards!?
|
||||
ast_binop(Op, _, A, B, Icode) when Op =:= 'bsl'; Op =:= 'bsr' ->
|
||||
#binop{op = Op, right = ast_body(A, Icode), left = ast_body(B, Icode)};
|
||||
ast_binop(Op, _, A, B, Icode) ->
|
||||
#binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}.
|
||||
|
||||
|
@ -38,7 +38,7 @@ builtin_deps1({map_upd, Type}) -> [{map_get, Type}, map_put];
|
||||
builtin_deps1({map_upd_default, Type}) -> [{map_lookup_default, Type}, map_put];
|
||||
builtin_deps1(map_from_list) -> [map_put];
|
||||
builtin_deps1(str_equal) -> [str_equal_p];
|
||||
builtin_deps1(string_concat) -> [string_concat_inner1, string_concat_inner2];
|
||||
builtin_deps1(string_concat) -> [string_concat_inner1, string_copy, string_shift_copy];
|
||||
builtin_deps1(int_to_str) -> [{baseX_int, 10}];
|
||||
builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
|
||||
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
|
||||
@ -144,9 +144,11 @@ builtin_function(BF) ->
|
||||
string_length -> bfun(BF, builtin_string_length());
|
||||
string_concat -> bfun(BF, builtin_string_concat());
|
||||
string_concat_inner1 -> bfun(BF, builtin_string_concat_inner1());
|
||||
string_concat_inner2 -> bfun(BF, builtin_string_concat_inner2());
|
||||
string_copy -> bfun(BF, builtin_string_copy());
|
||||
string_shift_copy -> bfun(BF, builtin_string_shift_copy());
|
||||
str_equal_p -> bfun(BF, builtin_str_equal_p());
|
||||
str_equal -> bfun(BF, builtin_str_equal());
|
||||
popcount -> bfun(BF, builtin_popcount());
|
||||
int_to_str -> bfun(BF, builtin_int_to_str());
|
||||
addr_to_str -> bfun(BF, builtin_addr_to_str());
|
||||
{baseX_int, X} -> bfun(BF, builtin_baseX_int(X));
|
||||
@ -322,6 +324,8 @@ builtin_string_concat() ->
|
||||
{[{"s1", string}, {"s2", string}],
|
||||
?DEREF(n1, s1,
|
||||
?DEREF(n2, s2,
|
||||
{ifte, ?EQ(n1, 0),
|
||||
?V(s2), %% First string is empty return second string
|
||||
{ifte, ?EQ(n2, 0),
|
||||
?V(s1), %% Second string is empty return first string
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
@ -330,6 +334,7 @@ builtin_string_concat() ->
|
||||
{inline_asm, [?A(?POP)]}, %% Discard fun ret val
|
||||
?V(ret) %% Put the actual return value
|
||||
]})}
|
||||
}
|
||||
)),
|
||||
word}.
|
||||
|
||||
@ -337,33 +342,33 @@ builtin_string_concat_inner1() ->
|
||||
%% Copy all whole words from the first string, and set up for word fusion
|
||||
%% Special case when the length of the first string is divisible by 32.
|
||||
{[{"n1", word}, {"p1", pointer}, {"n2", word}, {"p2", pointer}],
|
||||
?DEREF(w1, p1,
|
||||
{ifte, ?GT(n1, 32),
|
||||
{seq, [?V(w1), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call(string_concat_inner1, [?SUB(n1, 32), ?NXT(p1), ?V(n2), ?V(p2)])]},
|
||||
{ifte, ?EQ(n1, 0),
|
||||
?call(string_concat_inner2, [?I(32), ?I(0), ?V(n2), ?V(p2)]),
|
||||
?call(string_concat_inner2, [?SUB(32, n1), ?V(w1), ?V(n2), ?V(p2)])}
|
||||
?LET(w1, ?call(string_copy, [?V(n1), ?V(p1)]),
|
||||
?LET(nx, ?MOD(n1, 32),
|
||||
{ifte, ?EQ(nx, 0),
|
||||
?LET(w2, ?call(string_copy, [?V(n2), ?V(p2)]),
|
||||
{seq, [?V(w2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}),
|
||||
?call(string_shift_copy, [?V(nx), ?V(w1), ?V(n2), ?V(p2)])
|
||||
})),
|
||||
word}.
|
||||
|
||||
builtin_string_copy() ->
|
||||
{[{"n", word}, {"p", pointer}],
|
||||
?DEREF(w, p,
|
||||
{ifte, ?GT(n, 31),
|
||||
{seq, [?V(w), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call(string_copy, [?SUB(n, 32), ?NXT(p)])]},
|
||||
?V(w)
|
||||
}),
|
||||
word}.
|
||||
|
||||
builtin_string_concat_inner2() ->
|
||||
%% Current "work in progess" word 'x', has 'o' bytes that are "free" - fill them from
|
||||
%% words of the second string.
|
||||
{[{"o", word}, {"x", word}, {"n2", word}, {"p2", pointer}],
|
||||
{ifte, ?LT(n2, 1),
|
||||
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}, %% Use MSIZE as dummy return value
|
||||
?DEREF(w2, p2,
|
||||
{ifte, ?GT(n2, o),
|
||||
{seq, [?ADD(x, ?BSR(w2, ?SUB(32, o))),
|
||||
{inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call(string_concat_inner2,
|
||||
[?V(o), ?BSL(w2, o), ?SUB(n2, 32), ?NXT(p2)])
|
||||
]},
|
||||
{seq, [?ADD(x, ?BSR(w2, ?SUB(32, o))),
|
||||
{inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]} %% Use MSIZE as dummy return value
|
||||
})
|
||||
},
|
||||
builtin_string_shift_copy() ->
|
||||
{[{"off", word}, {"dst", word}, {"n", word}, {"p", pointer}],
|
||||
?DEREF(w, p,
|
||||
{seq, [?ADD(dst, ?BSR(w, off)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
{ifte, ?GT(n, ?SUB(32, off)),
|
||||
?call(string_shift_copy, [?V(off), ?BSL(w, ?SUB(32, off)), ?SUB(n, 32), ?NXT(p)]),
|
||||
{inline_asm, [?A(?MSIZE)]}}]
|
||||
}),
|
||||
word}.
|
||||
|
||||
builtin_str_equal_p() ->
|
||||
@ -394,6 +399,17 @@ builtin_str_equal() ->
|
||||
)),
|
||||
word}.
|
||||
|
||||
%% Count the number of 1s in a bit field.
|
||||
builtin_popcount() ->
|
||||
%% function popcount(bits, acc) =
|
||||
%% if (bits == 0) acc
|
||||
%% else popcount(bits bsr 1, acc + bits band 1)
|
||||
{[{"bits", word}, {"acc", word}],
|
||||
{ifte, ?EQ(bits, 0),
|
||||
?V(acc),
|
||||
?call(popcount, [op('bsr', 1, bits), ?ADD(acc, op('band', bits, 1))])
|
||||
}, word}.
|
||||
|
||||
builtin_int_to_str() ->
|
||||
{[{"i", word}], ?call({baseX_int, 10}, [?V(i)]), word}.
|
||||
|
||||
|
@ -21,8 +21,8 @@
|
||||
-include("aeso_icode.hrl").
|
||||
|
||||
|
||||
-type option() :: pp_sophia_code | pp_ast | pp_icode | pp_assembler |
|
||||
pp_bytecode.
|
||||
-type option() :: pp_sophia_code | pp_ast | pp_types | pp_typed_ast |
|
||||
pp_icode| pp_assembler | pp_bytecode.
|
||||
-type options() :: [option()].
|
||||
|
||||
-export_type([ option/0
|
||||
@ -38,17 +38,24 @@
|
||||
version() ->
|
||||
?COMPILER_VERSION.
|
||||
|
||||
-spec file(string()) -> map().
|
||||
-spec file(string()) -> {ok, map()} | {error, binary()}.
|
||||
file(Filename) ->
|
||||
file(Filename, []).
|
||||
|
||||
-spec file(string(), options()) -> map().
|
||||
file(Filename, Options) ->
|
||||
C = read_contract(Filename),
|
||||
from_string(C, Options).
|
||||
-spec file(string(), options()) -> {ok, map()} | {error, binary()}.
|
||||
file(File, Options) ->
|
||||
case read_contract(File) of
|
||||
{ok, Bin} -> from_string(Bin, Options);
|
||||
{error, Error} ->
|
||||
ErrorString = [File,": ",file:format_error(Error)],
|
||||
{error, join_errors("File errors", [ErrorString], fun(E) -> E end)}
|
||||
end.
|
||||
|
||||
-spec from_string(string(), options()) -> map().
|
||||
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}.
|
||||
from_string(ContractBin, Options) when is_binary(ContractBin) ->
|
||||
from_string(binary_to_list(ContractBin), Options);
|
||||
from_string(ContractString, Options) ->
|
||||
try
|
||||
Ast = parse(ContractString, Options),
|
||||
ok = pp_sophia_code(Ast, Options),
|
||||
ok = pp_ast(Ast, Options),
|
||||
@ -63,9 +70,26 @@ from_string(ContractString, Options) ->
|
||||
ByteCodeList = to_bytecode(Assembler, Options),
|
||||
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
|
||||
ok = pp_bytecode(ByteCode, Options),
|
||||
#{byte_code => ByteCode, type_info => TypeInfo,
|
||||
{ok, #{byte_code => ByteCode,
|
||||
compiler_version => version(),
|
||||
contract_source => ContractString,
|
||||
compiler_version => version()}.
|
||||
type_info => TypeInfo
|
||||
}}
|
||||
catch
|
||||
%% The compiler errors.
|
||||
error:{parse_errors, Errors} ->
|
||||
{error, join_errors("Parse errors", Errors, fun(E) -> E end)};
|
||||
error:{type_errors, Errors} ->
|
||||
{error, join_errors("Type errors", Errors, fun(E) -> E end)};
|
||||
error:{code_errors, Errors} ->
|
||||
{error, join_errors("Code errors", Errors,
|
||||
fun (E) -> io_lib:format("~p", [E]) end)}
|
||||
%% General programming errors in the compiler just signal error.
|
||||
end.
|
||||
|
||||
join_errors(Prefix, Errors, Pfun) ->
|
||||
Ess = [ Pfun(E) || E <- Errors ],
|
||||
list_to_binary(string:join([Prefix|Ess], "\n")).
|
||||
|
||||
-define(CALL_NAME, "__call").
|
||||
|
||||
@ -76,6 +100,7 @@ from_string(ContractString, Options) ->
|
||||
-spec check_call(string(), options()) -> {ok, string(), {[Type], Type | any}, [term()]} | {error, term()}
|
||||
when Type :: term().
|
||||
check_call(ContractString, Options) ->
|
||||
try
|
||||
Ast = parse(ContractString, Options),
|
||||
ok = pp_sophia_code(Ast, Options),
|
||||
ok = pp_ast(Ast, Options),
|
||||
@ -91,15 +116,24 @@ check_call(ContractString, Options) ->
|
||||
ok = pp_icode(Icode, Options),
|
||||
#{ functions := Funs } = Icode,
|
||||
ArgIcode = get_arg_icode(Funs),
|
||||
try [ icode_to_term(T, Arg) || {T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ] of
|
||||
ArgTerms ->
|
||||
ArgTerms = [ icode_to_term(T, Arg) ||
|
||||
{T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
|
||||
{ok, FunName, {ArgVMTypes, RetVMType}, ArgTerms}
|
||||
catch throw:Err ->
|
||||
{error, Err}
|
||||
catch
|
||||
error:{parse_errors, Errors} ->
|
||||
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
|
||||
error:{type_errors, Errors} ->
|
||||
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
|
||||
error:{badmatch, {error, missing_call_function}} ->
|
||||
{error, join_errors("Type errors", ["missing __call function"],
|
||||
fun (E) -> E end)};
|
||||
throw:Error -> %Don't ask
|
||||
{error, join_errors("Code errors", [Error],
|
||||
fun (E) -> io_lib:format("~p", [E]) end)}
|
||||
end.
|
||||
|
||||
-spec create_calldata(map(), string(), string()) ->
|
||||
{ok, aeso_sophia:heap(), aeso_sophia:type(), aeso_sophia:type()}
|
||||
{ok, binary(), aeso_sophia:type(), aeso_sophia:type()}
|
||||
| {error, argument_syntax_error}.
|
||||
create_calldata(Contract, "", CallCode) when is_map(Contract) ->
|
||||
case check_call(CallCode, []) of
|
||||
@ -252,8 +286,4 @@ parse_error({Line,Pos}, ErrorString) ->
|
||||
error({parse_errors, [Error]}).
|
||||
|
||||
read_contract(Name) ->
|
||||
{ok, Bin} = file:read_file(filename:join(contract_path(), lists:concat([Name, ".aes"]))),
|
||||
binary_to_list(Bin).
|
||||
|
||||
contract_path() ->
|
||||
"apps/aesophia/test/contracts".
|
||||
file:read_file(Name).
|
||||
|
@ -59,6 +59,7 @@ builtin_types() ->
|
||||
Word = fun([]) -> word end,
|
||||
#{ "bool" => Word
|
||||
, "int" => Word
|
||||
, "bits" => Word
|
||||
, "string" => fun([]) -> string end
|
||||
, "address" => Word
|
||||
, "hash" => Word
|
||||
|
@ -176,11 +176,11 @@ expr200() -> infixr(expr300(), binop('||')).
|
||||
expr300() -> infixr(expr400(), binop('&&')).
|
||||
expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])).
|
||||
expr500() -> infixr(expr600(), binop(['::', '++'])).
|
||||
expr600() -> infixl(expr650(), binop(['+', '-', 'bor', 'bxor', 'bsr', 'bsl'])).
|
||||
expr600() -> infixl(expr650(), binop(['+', '-'])).
|
||||
expr650() -> ?RULE(many(token('-')), expr700(), prefixes(_1, _2)).
|
||||
expr700() -> infixl(expr750(), binop(['*', '/', mod, 'band'])).
|
||||
expr700() -> infixl(expr750(), binop(['*', '/', mod])).
|
||||
expr750() -> infixl(expr800(), binop(['^'])).
|
||||
expr800() -> ?RULE(many(choice(token('!'), token('bnot'))), expr900(), prefixes(_1, _2)).
|
||||
expr800() -> ?RULE(many(token('!')), expr900(), prefixes(_1, _2)).
|
||||
expr900() -> ?RULE(exprAtom(), many(elim()), elim(_1, _2)).
|
||||
|
||||
exprAtom() ->
|
||||
|
@ -170,7 +170,7 @@ expr(E) -> expr_p(0, E).
|
||||
|
||||
%% -- Not exported -----------------------------------------------------------
|
||||
|
||||
-spec name(aeso_syntax:id() | aeso_syntax:con() | aeso_syntax:tvar()) -> doc().
|
||||
-spec name(aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon() | aeso_syntax:tvar()) -> doc().
|
||||
name({id, _, Name}) -> text(Name);
|
||||
name({con, _, Name}) -> text(Name);
|
||||
name({qid, _, Names}) -> text(string:join(Names, "."));
|
||||
@ -359,20 +359,14 @@ bin_prec('++') -> {500, 600, 500};
|
||||
bin_prec('::') -> {500, 600, 500};
|
||||
bin_prec('+') -> {600, 600, 650};
|
||||
bin_prec('-') -> {600, 600, 650};
|
||||
bin_prec('bor') -> {600, 600, 650};
|
||||
bin_prec('bxor') -> {600, 600, 650};
|
||||
bin_prec('bsl') -> {600, 600, 650};
|
||||
bin_prec('bsr') -> {600, 600, 650};
|
||||
bin_prec('*') -> {700, 700, 750};
|
||||
bin_prec('/') -> {700, 700, 750};
|
||||
bin_prec(mod) -> {700, 700, 750};
|
||||
bin_prec('band') -> {700, 700, 750};
|
||||
bin_prec('^') -> {750, 750, 800}.
|
||||
|
||||
-spec un_prec(aeso_syntax:un_op()) -> {integer(), integer()}.
|
||||
un_prec('-') -> {650, 650};
|
||||
un_prec('!') -> {800, 800};
|
||||
un_prec('bnot') -> {800, 800}.
|
||||
un_prec('!') -> {800, 800}.
|
||||
|
||||
equals(Ann, A, B) ->
|
||||
{app, [{format, infix} | Ann], {'=', Ann}, [A, B]}.
|
||||
|
@ -37,8 +37,7 @@ lexer() ->
|
||||
, {"[^/*]+|[/*]", skip()} ],
|
||||
|
||||
Keywords = ["contract", "import", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
||||
"stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal",
|
||||
"band", "bor", "bxor", "bsl", "bsr", "bnot"],
|
||||
"stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal"],
|
||||
KW = string:join(Keywords, "|"),
|
||||
|
||||
Rules =
|
||||
|
@ -75,10 +75,10 @@
|
||||
|
||||
-type op() :: bin_op() | un_op().
|
||||
|
||||
-type bin_op() :: '+' | '-' | '*' | '/' | mod | '^' | 'band' | 'bor' | 'bsl' | 'bsr' | 'bxor'
|
||||
-type bin_op() :: '+' | '-' | '*' | '/' | mod | '^'
|
||||
| '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!='
|
||||
| '||' | '&&' | '..'.
|
||||
-type un_op() :: '-' | '!' | 'bnot'.
|
||||
-type un_op() :: '-' | '!'.
|
||||
|
||||
-type expr()
|
||||
:: {lam, ann(), [arg()], expr()}
|
||||
|
@ -5,8 +5,8 @@
|
||||
{applications,
|
||||
[kernel,
|
||||
stdlib,
|
||||
enacl,
|
||||
syntax_tools,
|
||||
getopt,
|
||||
aebytecode
|
||||
]},
|
||||
{env,[]},
|
||||
|
71
src/aesophia.erl
Normal file
71
src/aesophia.erl
Normal file
@ -0,0 +1,71 @@
|
||||
-module(aesophia).
|
||||
|
||||
-export([main/1]).
|
||||
|
||||
-define(OPT_SPEC,
|
||||
[ {src_file, undefined, undefined, string, "Sophia source code file"}
|
||||
, {verbose, $v, "verbose", undefined, "Verbose output"}
|
||||
, {help, $h, "help", undefined, "Show this message"}
|
||||
, {outfile, $o, "out", string, "Output file (experimental)"} ]).
|
||||
|
||||
usage() ->
|
||||
getopt:usage(?OPT_SPEC, "aesophia").
|
||||
|
||||
main(Args) ->
|
||||
case getopt:parse(?OPT_SPEC, Args) of
|
||||
{ok, {Opts, []}} ->
|
||||
case proplists:get_value(help, Opts, false) of
|
||||
false ->
|
||||
compile(Opts);
|
||||
true ->
|
||||
usage()
|
||||
end;
|
||||
|
||||
{ok, {_, NonOpts}} ->
|
||||
io:format("Can't understand ~p\n\n", [NonOpts]),
|
||||
usage();
|
||||
|
||||
{error, {Reason, Data}} ->
|
||||
io:format("Error: ~s ~p\n\n", [Reason, Data]),
|
||||
usage()
|
||||
end.
|
||||
|
||||
|
||||
compile(Opts) ->
|
||||
case proplists:get_value(src_file, Opts, undefined) of
|
||||
undefined ->
|
||||
io:format("Error: no input source file\n\n"),
|
||||
usage();
|
||||
File ->
|
||||
compile(File, Opts)
|
||||
end.
|
||||
|
||||
compile(File, Opts) ->
|
||||
Verbose = proplists:get_value(verbose, Opts, false),
|
||||
OutFile = proplists:get_value(outfile, Opts, undefined),
|
||||
|
||||
try
|
||||
Res = aeso_compiler:file(File, [pp_ast || Verbose]),
|
||||
write_outfile(OutFile, Res),
|
||||
io:format("\nCompiled successfully!\n")
|
||||
catch
|
||||
%% The compiler errors.
|
||||
error:{type_errors, Errors} ->
|
||||
io:format("\n~s\n", [string:join(["** Type errors\n" | Errors], "\n")]);
|
||||
error:{parse_errors, Errors} ->
|
||||
io:format("\n~s\n", [string:join(["** Parse errors\n" | Errors], "\n")]);
|
||||
error:{code_errors, Errors} ->
|
||||
ErrorStrings = [ io_lib:format("~p", [E]) || E <- Errors ],
|
||||
io:format("\n~s\n", [string:join(["** Code errors\n" | ErrorStrings], "\n")]);
|
||||
%% General programming errors in the compiler.
|
||||
error:Error ->
|
||||
Where = hd(erlang:get_stacktrace()),
|
||||
ErrorString = io_lib:format("Error: ~p in\n ~p", [Error,Where]),
|
||||
io:format("\n~s\n", [ErrorString])
|
||||
end.
|
||||
|
||||
write_outfile(undefined, _) -> ok;
|
||||
write_outfile(Out, ResMap) ->
|
||||
%% Lazy approach
|
||||
file:write_file(Out, term_to_binary(ResMap)),
|
||||
io:format("Output written to: ~s\n", [Out]).
|
@ -28,13 +28,15 @@ simple_compile_test_() ->
|
||||
end} || ContractName <- compilable_contracts() ] ++
|
||||
[ {"Testing error messages of " ++ ContractName,
|
||||
fun() ->
|
||||
{type_errors, Errors} = compile(ContractName),
|
||||
check_errors(lists:sort(ExpectedErrors), lists:sort(Errors))
|
||||
<<"Type errors\n",ErrorString/binary>> = compile(ContractName),
|
||||
check_errors(lists:sort(ExpectedErrors), ErrorString)
|
||||
end} ||
|
||||
{ContractName, ExpectedErrors} <- failing_contracts() ]
|
||||
}.
|
||||
|
||||
check_errors(Expect, Actual) ->
|
||||
check_errors(Expect, ErrorString) ->
|
||||
%% This removes the final single \n as well.
|
||||
Actual = binary:split(<<ErrorString/binary,$\n>>, <<"\n\n">>, [global,trim]),
|
||||
case {Expect -- Actual, Actual -- Expect} of
|
||||
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
|
||||
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
|
||||
@ -42,10 +44,10 @@ check_errors(Expect, Actual) ->
|
||||
end.
|
||||
|
||||
compile(Name) ->
|
||||
try
|
||||
aeso_compiler:from_string(aeso_test_utils:read_contract(Name), [])
|
||||
catch _:{type_errors, _} = E ->
|
||||
E
|
||||
String = aeso_test_utils:read_contract(Name),
|
||||
case aeso_compiler:from_string(String, []) of
|
||||
{ok,Map} -> Map;
|
||||
{error,ErrorString} -> ErrorString
|
||||
end.
|
||||
|
||||
%% compilable_contracts() -> [ContractName].
|
||||
@ -75,82 +77,94 @@ compilable_contracts() ->
|
||||
|
||||
failing_contracts() ->
|
||||
[ {"name_clash",
|
||||
["Duplicate definitions of abort at\n - (builtin location)\n - line 14, column 3\n",
|
||||
"Duplicate definitions of double_def at\n - line 10, column 3\n - line 11, column 3\n",
|
||||
"Duplicate definitions of double_proto at\n - line 4, column 3\n - line 5, column 3\n",
|
||||
"Duplicate definitions of proto_and_def at\n - line 7, column 3\n - line 8, column 3\n",
|
||||
"Duplicate definitions of put at\n - (builtin location)\n - line 15, column 3\n",
|
||||
"Duplicate definitions of state at\n - (builtin location)\n - line 16, column 3\n"]}
|
||||
[<<"Duplicate definitions of abort at\n"
|
||||
" - (builtin location)\n"
|
||||
" - line 14, column 3">>,
|
||||
<<"Duplicate definitions of double_def at\n"
|
||||
" - line 10, column 3\n"
|
||||
" - line 11, column 3">>,
|
||||
<<"Duplicate definitions of double_proto at\n"
|
||||
" - line 4, column 3\n"
|
||||
" - line 5, column 3">>,
|
||||
<<"Duplicate definitions of proto_and_def at\n"
|
||||
" - line 7, column 3\n"
|
||||
" - line 8, column 3">>,
|
||||
<<"Duplicate definitions of put at\n"
|
||||
" - (builtin location)\n"
|
||||
" - line 15, column 3">>,
|
||||
<<"Duplicate definitions of state at\n"
|
||||
" - (builtin location)\n"
|
||||
" - line 16, column 3">>]}
|
||||
, {"type_errors",
|
||||
["Unbound variable zz at line 17, column 21\n",
|
||||
"Cannot unify int\n"
|
||||
[<<"Unbound variable zz at line 17, column 21">>,
|
||||
<<"Cannot unify int\n"
|
||||
" and list(int)\n"
|
||||
"when checking the application at line 26, column 9 of\n"
|
||||
" (::) : (int, list(int)) => list(int)\n"
|
||||
"to arguments\n"
|
||||
" x : int\n"
|
||||
" x : int\n",
|
||||
"Cannot unify string\n"
|
||||
" x : int">>,
|
||||
<<"Cannot unify string\n"
|
||||
" and int\n"
|
||||
"when checking the assignment of the field\n"
|
||||
" x : map(string, string) (at line 9, column 46)\n"
|
||||
"to the old value __x and the new value\n"
|
||||
" __x {[\"foo\"] @ x = x + 1} : map(string, int)\n",
|
||||
"Cannot unify int\n"
|
||||
" __x {[\"foo\"] @ x = x + 1} : map(string, int)">>,
|
||||
<<"Cannot unify int\n"
|
||||
" and string\n"
|
||||
"when checking the type of the expression at line 34, column 45\n"
|
||||
" 1 : int\n"
|
||||
"against the expected type\n"
|
||||
" string\n",
|
||||
"Cannot unify string\n"
|
||||
" string">>,
|
||||
<<"Cannot unify string\n"
|
||||
" and int\n"
|
||||
"when checking the type of the expression at line 34, column 50\n"
|
||||
" \"bla\" : string\n"
|
||||
"against the expected type\n"
|
||||
" int\n",
|
||||
"Cannot unify string\n"
|
||||
" int">>,
|
||||
<<"Cannot unify string\n"
|
||||
" and int\n"
|
||||
"when checking the type of the expression at line 32, column 18\n"
|
||||
" \"x\" : string\n"
|
||||
"against the expected type\n"
|
||||
" int\n",
|
||||
"Cannot unify string\n"
|
||||
" int">>,
|
||||
<<"Cannot unify string\n"
|
||||
" and int\n"
|
||||
"when checking the type of the expression at line 11, column 56\n"
|
||||
" \"foo\" : string\n"
|
||||
"against the expected type\n"
|
||||
" int\n",
|
||||
"Cannot unify int\n"
|
||||
" int">>,
|
||||
<<"Cannot unify int\n"
|
||||
" and string\n"
|
||||
"when comparing the types of the if-branches\n"
|
||||
" - w : int (at line 38, column 13)\n"
|
||||
" - z : string (at line 39, column 10)\n",
|
||||
"Not a record type: string\n"
|
||||
"arising from the projection of the field y (at line 22, column 38)\n",
|
||||
"Not a record type: string\n"
|
||||
"arising from an assignment of the field y (at line 21, column 42)\n",
|
||||
"Not a record type: string\n"
|
||||
"arising from an assignment of the field y (at line 20, column 38)\n",
|
||||
"Not a record type: string\n"
|
||||
"arising from an assignment of the field y (at line 19, column 35)\n",
|
||||
"Ambiguous record type with field y (at line 13, column 25) could be one of\n"
|
||||
" - z : string (at line 39, column 10)">>,
|
||||
<<"Not a record type: string\n"
|
||||
"arising from the projection of the field y (at line 22, column 38)">>,
|
||||
<<"Not a record type: string\n"
|
||||
"arising from an assignment of the field y (at line 21, column 42)">>,
|
||||
<<"Not a record type: string\n"
|
||||
"arising from an assignment of the field y (at line 20, column 38)">>,
|
||||
<<"Not a record type: string\n"
|
||||
"arising from an assignment of the field y (at line 19, column 35)">>,
|
||||
<<"Ambiguous record type with field y (at line 13, column 25) could be one of\n"
|
||||
" - r (at line 4, column 10)\n"
|
||||
" - r' (at line 5, column 10)\n",
|
||||
"Record type r2 does not have field y (at line 15, column 22)\n",
|
||||
"The field z is missing when constructing an element of type r2 (at line 15, column 24)\n",
|
||||
"Repeated name x in pattern\n"
|
||||
" x :: x (at line 26, column 7)\n",
|
||||
"No record type with fields y, z (at line 14, column 22)\n"]}
|
||||
" - r' (at line 5, column 10)">>,
|
||||
<<"Record type r2 does not have field y (at line 15, column 22)">>,
|
||||
<<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
|
||||
<<"Repeated name x in pattern\n"
|
||||
" x :: x (at line 26, column 7)">>,
|
||||
<<"No record type with fields y, z (at line 14, column 22)">>]}
|
||||
, {"init_type_error",
|
||||
["Cannot unify string\n"
|
||||
[<<"Cannot unify string\n"
|
||||
" and map(int, int)\n"
|
||||
"when checking that 'init' returns a value of type 'state' at line 7, column 3\n"]}
|
||||
"when checking that 'init' returns a value of type 'state' at line 7, column 3">>]}
|
||||
, {"missing_state_type",
|
||||
["Cannot unify string\n"
|
||||
[<<"Cannot unify string\n"
|
||||
" and ()\n"
|
||||
"when checking that 'init' returns a value of type 'state' at line 5, column 3\n"]}
|
||||
"when checking that 'init' returns a value of type 'state' at line 5, column 3">>]}
|
||||
, {"missing_fields_in_record_expression",
|
||||
["The field x is missing when constructing an element of type r('a) (at line 7, column 40)\n",
|
||||
"The field y is missing when constructing an element of type r(int) (at line 8, column 40)\n",
|
||||
"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)\n"]}
|
||||
[<<"The field x is missing when constructing an element of type r('a) (at line 7, column 40)">>,
|
||||
<<"The field y is missing when constructing an element of type r(int) (at line 8, column 40)">>,
|
||||
<<"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)">>]}
|
||||
].
|
||||
|
@ -36,8 +36,6 @@ contract AllSyntax =
|
||||
(x, [y, z]) => bar({x = z, y = -y + - -z * (-1)})
|
||||
(x, y :: _) => ()
|
||||
|
||||
function bitOperations(x, y) = bnot (0xff00 band x bsl 4 bxor 0xa5a5a5 bsr 4 bor y)
|
||||
|
||||
function mutual() =
|
||||
let rec recFun(x : int) = mutFun(x)
|
||||
and mutFun(x) = if(x =< 0) 1 else x * recFun(x - 1)
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
contract MultiSig =
|
||||
|
||||
record pending_state = { yetNeeded : uint, ownersDone : uint, index : uint }
|
||||
record pending_state = { yetNeeded : int, ownersDone : bits, index : int }
|
||||
|
||||
datatype event =
|
||||
Confirmation (address, hash) // of { .owner : Address, .operation : Hash }
|
||||
@ -13,18 +13,18 @@ contract MultiSig =
|
||||
| OwnerChanged (address, address) // of { .oldOwner : Address, .newOwner : Address }
|
||||
| OwnerAdded (address) // of { .newOwner : Address }
|
||||
| OwnerRemoved (address) // of { .removedOwner : Address }
|
||||
| ReqChanged (uint) // of { .newReq : uint }
|
||||
| ReqChanged (int) // of { .newReq : int }
|
||||
|
||||
let maxOwners : uint = 250
|
||||
let maxOwners : int = 250
|
||||
|
||||
record state = { nRequired : uint
|
||||
, nOwners : uint
|
||||
, owners : map(uint, address)
|
||||
, ownerIndex : map(address, uint)
|
||||
record state = { nRequired : int
|
||||
, nOwners : int
|
||||
, owners : map(int, address)
|
||||
, ownerIndex : map(address, int)
|
||||
, pending : map(hash, pending_state)
|
||||
, pendingIndex : list(address) }
|
||||
|
||||
function init (owners : list(address), nRequired : uint) : state =
|
||||
function init (owners : list(address), nRequired : int) : state =
|
||||
let n = length(owners) + 1
|
||||
{ nRequired = nRequired,
|
||||
nOwners = n,
|
||||
@ -39,10 +39,9 @@ contract MultiSig =
|
||||
function revoke(operation : hash) =
|
||||
let ownerIx = lookup(state.ownerIndex, caller())
|
||||
let pending = lookup(state.pendingIndex, operation)
|
||||
let ownerIxBit = 1 bsl (ownerIx - 1)
|
||||
let _ = require(pending.ownersDone band ownerIxBit > 0)
|
||||
let _ = require(Bits.test(pending.ownersDone, ownerIx))
|
||||
let pending' = pending { yetNeeded = pending.yetNeeded + 1
|
||||
, ownersDone = pending.ownersDone - ownerIxBit }
|
||||
, ownersDone = Bits.clear(pending.ownersDone, ownerIx - 1) }
|
||||
put(state{ pendingIndex.operator = pending' })
|
||||
event(Revoke(caller, operation))
|
||||
|
||||
@ -91,7 +90,7 @@ contract MultiSig =
|
||||
, pendingIx = [] },
|
||||
event = [OwnerRemoved(oldOwner)] }
|
||||
|
||||
function changeRequirement(newReq : uint) =
|
||||
function changeRequirement(newReq : int) =
|
||||
let _ = require(newReq =< state.nOwners)
|
||||
switch(check_pending(callhash()))
|
||||
CheckFail(state') => { state = state' }
|
||||
@ -102,7 +101,7 @@ contract MultiSig =
|
||||
event = [ReqChanged(newReq)] }
|
||||
|
||||
|
||||
function getOwner(ownerIx0 : uint) =
|
||||
function getOwner(ownerIx0 : int) =
|
||||
lookup(state.owners, ownerIx0 + 1)
|
||||
|
||||
function isOwner(owner : address) =
|
||||
@ -116,8 +115,7 @@ contract MultiSig =
|
||||
Some(pending) =>
|
||||
let _ = require(isOwner(owner))
|
||||
let ownerIx = lookup(state.ownerIndex, owner)
|
||||
let ownerIxBit = 1 bsl (ownerIx - 1)
|
||||
(pending.ownersDone band ownerIxBit) != 0
|
||||
Bits.test(pending.ownersDone, ownerIx - 1)
|
||||
|
||||
/* Leave the rest for now... */
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
// - + * / mod arithmetic operators
|
||||
// bnot band bor bxor bsl bsr bitwise operators
|
||||
// ! && || logical operators
|
||||
// == != < > =< >= comparison operators
|
||||
// :: ++ list operators
|
||||
@ -13,12 +12,6 @@ contract Operators =
|
||||
"/" => a / b
|
||||
"mod" => a mod b
|
||||
"^" => a ^ b
|
||||
"bnot" => bnot a
|
||||
"band" => a band b
|
||||
"bor" => a bor b
|
||||
"bxor" => a bxor b
|
||||
"bsl" => a bsl b
|
||||
"bsr" => a bsr b
|
||||
|
||||
function bool_op(a : bool, b : bool, op : string) =
|
||||
switch(op)
|
||||
|
Loading…
x
Reference in New Issue
Block a user