First test work commit, don't touch
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
-module(aeso_abi_tests).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-compile(export_all).
|
||||
|
||||
-define(SANDBOX(Code), sandbox(fun() -> Code end)).
|
||||
|
||||
sandbox(Code) ->
|
||||
Parent = self(),
|
||||
Tag = make_ref(),
|
||||
{Pid, Ref} = spawn_monitor(fun() -> Parent ! {Tag, Code()} end),
|
||||
receive
|
||||
{Tag, Res} -> erlang:demonitor(Ref, [flush]), {ok, Res};
|
||||
{'DOWN', Ref, process, Pid, Reason} -> {error, Reason}
|
||||
after 100 ->
|
||||
exit(Pid, kill),
|
||||
{error, loop}
|
||||
end.
|
||||
|
||||
malicious_from_binary_test() ->
|
||||
CircularList = from_words([32, 1, 32]), %% Xs = 1 :: Xs
|
||||
{ok, {error, circular_references}} = ?SANDBOX(aeso_heap:from_binary({list, word}, CircularList)),
|
||||
{ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeso_heap:from_binary(word, <<1, 2, 3, 4>>)),
|
||||
ok.
|
||||
|
||||
from_words(Ws) ->
|
||||
<< <<(from_word(W))/binary>> || W <- Ws >>.
|
||||
|
||||
from_word(W) when is_integer(W) ->
|
||||
<<W:256>>;
|
||||
from_word(S) when is_list(S) ->
|
||||
Len = length(S),
|
||||
Bin = <<(list_to_binary(S))/binary, 0:(32 - Len)/unit:8>>,
|
||||
<<Len:256, Bin/binary>>.
|
||||
|
||||
encode_decode_test() ->
|
||||
encode_decode(word, 42),
|
||||
42 = encode_decode(word, 42),
|
||||
-1 = encode_decode(signed_word, -1),
|
||||
<<"Hello world">> = encode_decode(string, <<"Hello world">>),
|
||||
{} = encode_decode({tuple, []}, {}),
|
||||
{42} = encode_decode({tuple, [word]}, {42}),
|
||||
{42, 0} = encode_decode({tuple, [word, word]}, {42, 0}),
|
||||
[] = encode_decode({list, word}, []),
|
||||
[32] = encode_decode({list, word}, [32]),
|
||||
none = encode_decode({option, word}, none),
|
||||
{some, 1} = encode_decode({option, word}, {some, 1}),
|
||||
string = encode_decode(typerep, string),
|
||||
word = encode_decode(typerep, word),
|
||||
{list, word} = encode_decode(typerep, {list, word}),
|
||||
{tuple, [word]} = encode_decode(typerep, {tuple, [word]}),
|
||||
1 = encode_decode(word, 1),
|
||||
0 = encode_decode(word, 0),
|
||||
ok.
|
||||
|
||||
encode_decode_sophia_test() ->
|
||||
{42} = encode_decode_sophia_string("int", "42"),
|
||||
{1} = encode_decode_sophia_string("bool", "true"),
|
||||
{0} = encode_decode_sophia_string("bool", "false"),
|
||||
{<<"Hello">>} = encode_decode_sophia_string("string", "\"Hello\""),
|
||||
{<<"Hello">>, [1,2,3], {variant, 1, [1]}} =
|
||||
encode_decode_sophia_string(
|
||||
"(string, list(int), option(bool))",
|
||||
"\"Hello\", [1,2,3], Some(true)"),
|
||||
ok.
|
||||
|
||||
encode_decode_sophia_string(SophiaType, String) ->
|
||||
io:format("String ~p~n", [String]),
|
||||
Code = [ "contract Call =\n"
|
||||
, " function foo : ", SophiaType, " => _\n"
|
||||
, " function __call() = foo(", String, ")\n" ],
|
||||
{ok, _, {Types, _}, Args} = aeso_compiler:check_call(lists:flatten(Code), []),
|
||||
Arg = list_to_tuple(Args),
|
||||
Type = {tuple, Types},
|
||||
io:format("Type ~p~n", [Type]),
|
||||
Data = encode(Arg),
|
||||
decode(Type, Data).
|
||||
|
||||
encode_decode(T, D) ->
|
||||
?assertEqual(D, decode(T, encode(D))),
|
||||
D.
|
||||
|
||||
encode(D) ->
|
||||
aeso_heap:to_binary(D).
|
||||
|
||||
decode(T,B) ->
|
||||
{ok, D} = aeso_heap:from_binary(T, B),
|
||||
D.
|
||||
@@ -0,0 +1,136 @@
|
||||
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc Test Sophia language compiler.
|
||||
%%%
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeso_compiler_tests).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
%% simple_compile_test_() -> ok.
|
||||
%% Very simply test compile the given contracts. Only basic checks
|
||||
%% are made on the output, just that it is a binary which indicates
|
||||
%% that the compilation worked.
|
||||
|
||||
simple_compile_test_() ->
|
||||
{setup,
|
||||
fun () -> ok end, %Setup
|
||||
fun (_) -> ok end, %Cleanup
|
||||
[ {"Testing the " ++ ContractName ++ " contract",
|
||||
fun() ->
|
||||
#{byte_code := ByteCode,
|
||||
contract_source := _,
|
||||
type_info := _} = compile(ContractName),
|
||||
?assertMatch(Code when is_binary(Code), ByteCode)
|
||||
end} || ContractName <- compilable_contracts() ] ++
|
||||
[ {"Testing error messages of " ++ ContractName,
|
||||
fun() ->
|
||||
{type_errors, Errors} = compile(ContractName),
|
||||
?assertEqual(lists:sort(ExpectedErrors), lists:sort(Errors))
|
||||
end} ||
|
||||
{ContractName, ExpectedErrors} <- failing_contracts() ]
|
||||
}.
|
||||
|
||||
compile(Name) ->
|
||||
try
|
||||
aeso_compiler:from_string(aeso_test_utils:read_contract(Name), [])
|
||||
catch _:{type_errors, _} = E ->
|
||||
E
|
||||
end.
|
||||
|
||||
%% compilable_contracts() -> [ContractName].
|
||||
%% The currently compilable contracts.
|
||||
|
||||
compilable_contracts() ->
|
||||
["complex_types",
|
||||
"counter",
|
||||
"dutch_auction",
|
||||
"environment",
|
||||
"factorial",
|
||||
"fundme",
|
||||
"identity",
|
||||
"maps",
|
||||
"oracles",
|
||||
"remote_call",
|
||||
"simple",
|
||||
"simple_storage",
|
||||
"spend_test",
|
||||
"stack",
|
||||
"test",
|
||||
"builtin_bug",
|
||||
"builtin_map_get_bug"
|
||||
].
|
||||
|
||||
%% Contracts that should produce type errors
|
||||
|
||||
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"]}
|
||||
, {"type_errors",
|
||||
["Unbound variable zz at line 17, column 21\n",
|
||||
"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"
|
||||
" 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"
|
||||
" 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"
|
||||
" 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"
|
||||
" 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"
|
||||
" 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"
|
||||
" 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"
|
||||
" - 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",
|
||||
"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"]}
|
||||
].
|
||||
@@ -0,0 +1,20 @@
|
||||
-module(aeso_eunit_SUITE).
|
||||
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
all() ->
|
||||
[{group, eunit}].
|
||||
|
||||
groups() ->
|
||||
[{eunit, [], [ aeso_scan_tests
|
||||
, aeso_parser_tests
|
||||
, aeso_compiler_tests
|
||||
, aeso_abi_tests
|
||||
]}].
|
||||
|
||||
aeso_scan_tests(_Config) -> ok = eunit:test(aeso_scan_tests).
|
||||
aeso_parser_tests(_Config) -> ok = eunit:test(aeso_parser_tests).
|
||||
aeso_compiler_tests(_Config) -> ok = eunit:test(aeso_compiler_tests).
|
||||
aeso_abi_tests(_Config) -> ok = eunit:test(aeso_abi_tests).
|
||||
@@ -0,0 +1,111 @@
|
||||
-module(aeso_parser_tests).
|
||||
|
||||
-export([parse_contract/1]).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
simple_contracts_test_() ->
|
||||
{foreach,
|
||||
fun() -> ok end,
|
||||
fun(_) -> ok end,
|
||||
[{"Parse a contract with an identity function.",
|
||||
fun() ->
|
||||
Text = "contract Identity =\n"
|
||||
" function id(x) = x\n",
|
||||
?assertMatch(
|
||||
[{contract, _, {con, _, "Identity"},
|
||||
[{letfun, _, {id, _, "id"}, [{arg, _, {id, _, "x"}, {id, _, "_"}}], {id, _, "_"},
|
||||
{id, _, "x"}}]}], parse_string(Text)),
|
||||
ok
|
||||
end},
|
||||
{"Operator precedence test.",
|
||||
fun() ->
|
||||
NoPar = fun NoPar(X) when is_atom(X) -> atom_to_list(X);
|
||||
NoPar({A, Op, B}) -> lists:concat([NoPar(A), " ", Op, " ", NoPar(B)]);
|
||||
NoPar({Op, A}) -> lists:concat([Op, " ", NoPar(A)])
|
||||
end,
|
||||
Par = fun Par(X) when is_atom(X) -> atom_to_list(X);
|
||||
Par({A, Op, B}) -> lists:concat(["(", Par(A), " ", Op, " ", Par(B), ")"]);
|
||||
Par({Op, A}) -> lists:concat(["(", Op, " ", Par(A), ")"])
|
||||
end,
|
||||
Parse = fun(S) ->
|
||||
try remove_line_numbers(parse_expr(S))
|
||||
catch _:_ -> ?assertMatch(ok, {parse_fail, S}) end
|
||||
end,
|
||||
CheckParens = fun(Expr) ->
|
||||
?assertEqual(Parse(NoPar(Expr)), Parse(Par(Expr)))
|
||||
end,
|
||||
LeftAssoc = fun(Op) -> CheckParens({{a, Op, b}, Op, c}) end,
|
||||
RightAssoc = fun(Op) -> CheckParens({a, Op, {b, Op, c}}) end,
|
||||
NonAssoc = fun(Op) ->
|
||||
OpAtom = list_to_atom(Op),
|
||||
?assertError({error, {_, parse_error, _}},
|
||||
parse_expr(NoPar({a, Op, {b, Op, c}}))) end,
|
||||
Stronger = fun(Op1, Op2) ->
|
||||
CheckParens({{a, Op1, b}, Op2, c}),
|
||||
CheckParens({a, Op2, {b, Op1, c}})
|
||||
end,
|
||||
|
||||
Tiers = [["||"], ["&&"], ["==", "!=", "<", ">", "=<", ">="], ["::", "++"],
|
||||
["+", "-"], ["*", "/", "mod"]],
|
||||
|
||||
%% associativity
|
||||
[ RightAssoc(Op) || Op <- ["||", "&&", "::", "++"] ],
|
||||
[ NonAssoc(Op) || Op <- ["==", "!=", "<", ">", "=<", ">="] ],
|
||||
[ LeftAssoc(Op) || Op <- ["+", "-", "*", "/", "mod"] ],
|
||||
|
||||
%% precedence
|
||||
[ Stronger(Op2, Op1) || [T1 , T2 | _] <- tails(Tiers), Op1 <- T1, Op2 <- T2 ],
|
||||
ok
|
||||
end}
|
||||
] ++
|
||||
%% Parse tests of example contracts
|
||||
[ {lists:concat(["Parse the ", Contract, " contract."]),
|
||||
fun() -> roundtrip_contract(Contract) end}
|
||||
|| Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, withdrawal, fundme, dutch_auction] ]
|
||||
}.
|
||||
|
||||
parse_contract(Name) ->
|
||||
parse_string(aeso_test_utils:read_contract(Name)).
|
||||
|
||||
roundtrip_contract(Name) ->
|
||||
round_trip(aeso_test_utils:read_contract(Name)).
|
||||
|
||||
parse_string(Text) ->
|
||||
case aeso_parser:string(Text) of
|
||||
{ok, Contract} -> Contract;
|
||||
Err -> error(Err)
|
||||
end.
|
||||
|
||||
parse_expr(Text) ->
|
||||
[{letval, _, _, _, Expr}] =
|
||||
parse_string("let _ = " ++ Text),
|
||||
Expr.
|
||||
|
||||
round_trip(Text) ->
|
||||
Contract = parse_string(Text),
|
||||
Text1 = prettypr:format(aeso_pretty:decls(Contract)),
|
||||
Contract1 = parse_string(Text1),
|
||||
NoSrcLoc = remove_line_numbers(Contract),
|
||||
NoSrcLoc1 = remove_line_numbers(Contract1),
|
||||
?assertMatch(NoSrcLoc, diff(NoSrcLoc, NoSrcLoc1)).
|
||||
|
||||
remove_line_numbers({line, _L}) -> {line, 0};
|
||||
remove_line_numbers({col, _C}) -> {col, 0};
|
||||
remove_line_numbers([H|T]) ->
|
||||
[remove_line_numbers(H) | remove_line_numbers(T)];
|
||||
remove_line_numbers(T) when is_tuple(T) ->
|
||||
list_to_tuple(remove_line_numbers(tuple_to_list(T)));
|
||||
remove_line_numbers(M) when is_map(M) ->
|
||||
maps:from_list(remove_line_numbers(maps:to_list(M)));
|
||||
remove_line_numbers(X) -> X.
|
||||
|
||||
diff(X, X) -> X;
|
||||
diff([H | T], [H1 | T1]) ->
|
||||
[diff(H, H1) | diff(T, T1)];
|
||||
diff(T, T1) when tuple_size(T) == tuple_size(T1) ->
|
||||
list_to_tuple(diff(tuple_to_list(T), tuple_to_list(T1)));
|
||||
diff(X, Y) -> {X, '/=', Y}.
|
||||
|
||||
tails(Zs) -> lists:foldr(fun(X, [Xs|Xss]) -> [[X|Xs], Xs | Xss] end, [[]], Zs).
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
-module(aeso_scan_tests).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
empty_contract_test_() ->
|
||||
{foreach,
|
||||
fun() ->
|
||||
ok
|
||||
end,
|
||||
fun(_) ->
|
||||
ok
|
||||
end,
|
||||
[{"Scan an empty contract.",
|
||||
fun() ->
|
||||
Text = " ",
|
||||
{ok, []} = aeso_scan:scan(Text),
|
||||
ok
|
||||
end}
|
||||
]}.
|
||||
|
||||
all_tokens_test_() ->
|
||||
{foreach, fun() -> ok end,
|
||||
fun(_) -> ok end,
|
||||
[{"Check that we can scan all tokens.",
|
||||
fun() ->
|
||||
Tokens = all_tokens(),
|
||||
Text = string:join(lists:map(fun show_token/1, Tokens), " "),
|
||||
io:format("~s\n", [Text]),
|
||||
{ok, Tokens1} = aeso_scan:scan(Text),
|
||||
true = compare_tokens(Tokens, Tokens1),
|
||||
ok
|
||||
end}
|
||||
]}.
|
||||
|
||||
all_tokens() ->
|
||||
Lit = fun(T) -> {T, 1} end,
|
||||
Tok = fun(T, V) -> {T, 1, V} end,
|
||||
Hash = list_to_binary([ I * 8 || I <- lists:seq(0, 31) ]),
|
||||
%% Symbols
|
||||
lists:map(Lit, [',', '.', ';', '|', ':', '(', ')', '[', ']', '{', '}']) ++
|
||||
%% Operators
|
||||
lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++
|
||||
%% Keywords
|
||||
lists:map(Lit, [contract, type, 'let', switch, rec, 'and']) ++
|
||||
%% Comment token (not an actual token), just for tests
|
||||
[{comment, 0, "// *Comment!\"\n"},
|
||||
{comment, 0, "/* bla /* bla bla */*/"}] ++
|
||||
%% Literals
|
||||
[ Lit(true), Lit(false)
|
||||
, Tok(id, "foo"), Tok(id, "_"), Tok(con, "Foo")
|
||||
, Tok(hash, Hash)
|
||||
, Tok(int, 1234567890), Tok(hex, 9876543210)
|
||||
, Tok(string, <<"bla\"\\\b\e\f\n\r\t\vbla">>)
|
||||
].
|
||||
|
||||
compare_tokens([], []) -> true;
|
||||
compare_tokens([{T, _} | Ts1], [{T, _} | Ts2]) ->
|
||||
compare_tokens(Ts1, Ts2);
|
||||
compare_tokens([{T, _, V} | Ts1], [{T, _, V} | Ts2]) ->
|
||||
compare_tokens(Ts1, Ts2);
|
||||
compare_tokens([{comment, _, _} | Ts1], Ts2) ->
|
||||
compare_tokens(Ts1, Ts2);
|
||||
compare_tokens(Ts1, Ts2) ->
|
||||
case length(Ts1) == length(Ts2) of
|
||||
true ->
|
||||
{token_mismatch, [ {expected, T1, got, T2} || {T1, T2} <- lists:zip(Ts1, Ts2), T1 /= T2]};
|
||||
false ->
|
||||
{token_mismatch, {expected, Ts1, got, Ts2}}
|
||||
end.
|
||||
|
||||
fmt(X) -> fmt("~p", X).
|
||||
fmt(Fmt, X) -> lists:flatten(io_lib:format(Fmt, [X])).
|
||||
|
||||
show_token({T, _}) -> atom_to_list(T);
|
||||
show_token({id, _, X}) -> X;
|
||||
show_token({con, _, C}) -> C;
|
||||
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({comment, _, S}) -> S;
|
||||
show_token({_, _, _}) -> "TODO".
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc Test utilities for the Sophia language tests.
|
||||
%%%
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeso_test_utils).
|
||||
|
||||
-include("apps/aecontract/src/aecontract.hrl").
|
||||
|
||||
-export([read_contract/1, contract_path/0, pp/1, pp/2,
|
||||
dump_words/1, show_heap/1, show_heap/2, show_heap_value/1, compile/1]).
|
||||
|
||||
-export([spend/3, get_balance/2, call_contract/6, get_store/1, set_store/2,
|
||||
aens_lookup/4]).
|
||||
|
||||
contract_path() ->
|
||||
{ok, Cwd} = file:get_cwd(),
|
||||
N = length(filename:split(Cwd)),
|
||||
Rel = ["apps", "aesophia", "test", "contracts"],
|
||||
%% Try the first matching directory (../)*Rel
|
||||
Cand = fun(I) -> filename:join(lists:duplicate(I, "..") ++ Rel) end,
|
||||
case [ Dir || Dir <- lists:map(Cand, lists:seq(0, N)), filelib:is_dir(Dir) ] of
|
||||
[Dir | _] -> Dir;
|
||||
[] -> error(failed_to_find_contract_dir)
|
||||
end.
|
||||
|
||||
%% Read a contract file from the test/contracts directory.
|
||||
-spec read_contract(string() | atom()) -> string().
|
||||
read_contract(Name) ->
|
||||
{ok, Bin} = file:read_file(filename:join(contract_path(), lists:concat([Name, ".aes"]))),
|
||||
binary_to_list(Bin).
|
||||
|
||||
pp(Name) -> pp(Name, []).
|
||||
|
||||
pp(Name, Options) ->
|
||||
case aeso_parser:string(read_contract(Name)) of
|
||||
{ok, AST} ->
|
||||
[ io:format("~s\n", [prettypr:format(aeso_pretty:decls(AST))]) || not lists:member(quiet, Options) ];
|
||||
{error, {{L, C}, parse_error, Err}} ->
|
||||
io:format("Parse error at ~p:~p:~p\n~s\n", [Name, L, C, Err])
|
||||
end.
|
||||
|
||||
compile(Name) ->
|
||||
aeso_compiler:from_string(read_contract(Name),
|
||||
[pp_sophia_code, pp_typed_ast, pp_icode]).
|
||||
|
||||
%% Stack simulator
|
||||
|
||||
simulate([],Stack) ->
|
||||
Stack;
|
||||
simulate(['PUSH1',X|More],S) ->
|
||||
simulate(More,[X|S]);
|
||||
simulate([Op|More],Stack) ->
|
||||
simulate(More,simulate(Op,Stack));
|
||||
simulate('MSIZE',S) ->
|
||||
A = new_atom(),
|
||||
io:format("~p = MSIZE\n",[A]),
|
||||
[A|S];
|
||||
simulate('DUP2',[A,B|S]) ->
|
||||
[B,A,B|S];
|
||||
simulate('DUP3',[A,B,C|S]) ->
|
||||
[C,A,B,C|S];
|
||||
simulate('ADD',[A,B|S]) ->
|
||||
[add(A,B)|S];
|
||||
simulate('MSTORE',[Addr,X|S]) ->
|
||||
io:format("mem(~p) <- ~p\n",[Addr,X]),
|
||||
S;
|
||||
simulate('MLOAD',[Addr|S]) ->
|
||||
A = new_atom(),
|
||||
io:format("~p = mem(~p)\n",[A,Addr]),
|
||||
[A|S];
|
||||
simulate('SWAP1',[A,B|S]) ->
|
||||
[B,A|S];
|
||||
simulate('SWAP2',[A,B,C|S]) ->
|
||||
[C,B,A|S];
|
||||
simulate('SUB',[A,B|S]) ->
|
||||
[{A,'-',B}|S];
|
||||
simulate('POP',[_|S]) ->
|
||||
S.
|
||||
|
||||
add(0,X) ->
|
||||
X;
|
||||
add(X,0) ->
|
||||
X;
|
||||
add(X,{A,'-',X}) ->
|
||||
A;
|
||||
add(X,{A,'+',B}) ->
|
||||
{A,'+',add(X,B)};
|
||||
add(A,B) ->
|
||||
{A,'+',B}.
|
||||
|
||||
new_atom() ->
|
||||
catch ets:new(names,[set,public,named_table]),
|
||||
case ets:lookup(names,index) of
|
||||
[] -> I = 0;
|
||||
[{index,I}] -> ok
|
||||
end,
|
||||
ets:insert(names,{index,I+1}),
|
||||
list_to_atom([$a+I]).
|
||||
|
||||
show_heap(Bin) ->
|
||||
show_heap(0, Bin).
|
||||
|
||||
show_heap(Offs, Bin) ->
|
||||
Words = dump_words(Bin),
|
||||
Addrs = lists:seq(0, (length(Words) - 1) * 32, 32),
|
||||
lists:flatten([io_lib:format("~4b ~p\n", [Addr + Offs, Word]) || {Addr, Word} <- lists:zip(Addrs, Words)]).
|
||||
|
||||
show_heap_value(HeapValue) ->
|
||||
Maps = aeso_heap:heap_value_maps(HeapValue),
|
||||
Offs = aeso_heap:heap_value_offset(HeapValue),
|
||||
Ptr = aeso_heap:heap_value_pointer(HeapValue),
|
||||
Mem = aeso_heap:heap_value_heap(HeapValue),
|
||||
Words = dump_words(Mem),
|
||||
Addrs = lists:seq(Offs, Offs + (length(Words) - 1) * 32, 32),
|
||||
lists:flatten(
|
||||
io_lib:format(" Maps: ~p\n Ptr: ~p\n Heap: ~p",
|
||||
[Maps, Ptr, lists:zip(Addrs, Words)])).
|
||||
|
||||
%% Translate a blob of 256-bit words into readable form. Does a bit of guessing
|
||||
%% to recover strings. TODO: strings longer than 32 bytes
|
||||
dump_words(Bin) -> dump_words(Bin, []).
|
||||
|
||||
dump_words(<<N:256, W:32/binary, Rest/binary>>, Acc) when N < 32 ->
|
||||
NotN = (32 - N) * 8,
|
||||
case W of
|
||||
<<S:N/binary, 0:NotN>> ->
|
||||
Str = binary_to_list(S),
|
||||
case lists:member(0, Str) of
|
||||
true -> dump_words(<<W/binary, Rest/binary>>, [N | Acc]); %% Not a string
|
||||
false -> dump_words(Rest, [binary_to_list(S), N | Acc])
|
||||
end;
|
||||
_ -> dump_words(<<W/binary, Rest/binary>>, [N | Acc])
|
||||
end;
|
||||
dump_words(<<N:256/signed, Rest/binary>>, Acc) ->
|
||||
dump_words(Rest, [N | Acc]);
|
||||
dump_words(<<>>, Acc) -> lists:reverse(Acc);
|
||||
dump_words(Rest, Acc) -> lists:reverse([{error, Rest} | Acc]).
|
||||
|
||||
%% -- Chain API for test -----------------------------------------------------
|
||||
|
||||
aens_lookup(Name, Key, Type, _S) ->
|
||||
io:format("aens_lookup(~p, ~p, ~p)\n", [Name, Key, Type]),
|
||||
{ok, {some, <<0:32/unit:8>>}}.
|
||||
|
||||
spend(Recipient, Amount, S) ->
|
||||
io:format("+++ SPEND(~p, ~p)\n", [Recipient, Amount]),
|
||||
{ok, S}.
|
||||
|
||||
get_balance(_, _) -> 1000000.
|
||||
|
||||
call_contract(Contract, Gas, Value, CallData, CallStack, S) ->
|
||||
io:format("+++ CALL(~p, ~p, ~p, ~p, ~p)\n", [Contract, Gas, Value, CallData, CallStack]),
|
||||
{ok, <<42:256>>, S}.
|
||||
|
||||
get_store(_) -> #{}.
|
||||
set_store(_, _) -> ok.
|
||||
@@ -0,0 +1,28 @@
|
||||
-module(contract_tests).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
make_cmd() -> "make -C " ++ aeso_test_utils:contract_path().
|
||||
|
||||
contracts_test_() ->
|
||||
{setup,
|
||||
fun() -> os:cmd(make_cmd()) end,
|
||||
fun(_) -> os:cmd(make_cmd() ++ " clean") end,
|
||||
[ {"Testing the " ++ Contract ++ " contract",
|
||||
fun() ->
|
||||
?assertCmdOutput(Expected, filename:join(aeso_test_utils:contract_path(), Contract ++ "_test"))
|
||||
end} || {Contract, Expected} <- contracts() ]}.
|
||||
|
||||
contracts() ->
|
||||
[].
|
||||
%% [{"voting",
|
||||
%% "Delegate before vote\n"
|
||||
%% "Cake: 1\n"
|
||||
%% "Beer: 2\n"
|
||||
%% "Winner: Beer\n"
|
||||
%% "Delegate after vote\n"
|
||||
%% "Cake: 1\n"
|
||||
%% "Beer: 2\n"
|
||||
%% "Winner: Beer\n"
|
||||
%% }].
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/* https://github.com/fivedogit/solidity-baby-steps/blob/master/contracts/05_greeter.sol
|
||||
|
||||
/*
|
||||
The following is an extremely basic example of a solidity contract.
|
||||
It takes a string upon creation and then repeats it when greet() is called.
|
||||
*/
|
||||
|
||||
contract Greeter // The contract definition. A constructor of the same name will be automatically called on contract creation.
|
||||
{
|
||||
address creator; // At first, an empty "address"-type variable of the name "creator". Will be set in the constructor.
|
||||
string greeting; // At first, an empty "string"-type variable of the name "greeting". Will be set in constructor and can be changed.
|
||||
|
||||
function Greeter(string _greeting) public // The constructor. It accepts a string input and saves it to the contract's "greeting" variable.
|
||||
{
|
||||
creator = msg.sender
|
||||
greeting = _greeting
|
||||
}
|
||||
|
||||
function greet() constant returns (string)
|
||||
{
|
||||
return greeting
|
||||
}
|
||||
|
||||
function getBlockNumber() constant returns (uint) // this doesn't have anything to do with the act of greeting
|
||||
{ // just demonstrating return of some global variable
|
||||
return block.number
|
||||
}
|
||||
|
||||
function setGreeting(string _newgreeting)
|
||||
{
|
||||
greeting = _newgreeting
|
||||
}
|
||||
|
||||
/**********
|
||||
Standard kill() function to recover funds
|
||||
**********/
|
||||
|
||||
function kill()
|
||||
{
|
||||
if (msg.sender == creator) // only allow this action if the account sending the signal is the creator
|
||||
suicide(creator); // kills this contract and sends remaining funds back to creator
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
contract Greeter =
|
||||
|
||||
|
||||
/* The creator of the contract will automatically
|
||||
be set in creator() by the transaction creating the contract. */
|
||||
function blockheight : unit => uint
|
||||
record transaction = { tx : string }
|
||||
record state = { greeting: string }
|
||||
record retval = { state: state,
|
||||
transactions: list(transaction)}
|
||||
|
||||
let state = { greeting = "Hello" }
|
||||
|
||||
let setGreeting =
|
||||
(greeting: string) =>
|
||||
state{ greeting = greeting }
|
||||
|
||||
|
||||
|
||||
/* this doesn't have anything to do with the act of greeting
|
||||
just demonstrating return of some global variable */
|
||||
function getBlockNumber() = blockheight()
|
||||
|
||||
/* There is no suicide functionallity in Sophia */
|
||||
function kill() =
|
||||
if ((caller() == creator()) /* only allow this action if the account sending the signal is the creator */
|
||||
&& (balance() > 0)) /* only creata a transaction if there is something to send */
|
||||
state{ transactions = [spend_tx(creator(), balance())] }
|
||||
else state
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
## Requires ocaml >= 4.02, < 4.06
|
||||
## and reason-3.0.0 (opam install reason).
|
||||
|
||||
default : voting_test
|
||||
|
||||
%.ml : %.re
|
||||
refmt -p ml $< > $@
|
||||
|
||||
|
||||
voting_test : rte.ml voting.ml voting_test.ml
|
||||
ocamlopt -o $@ $^
|
||||
|
||||
clean :
|
||||
rm -f *.cmi *.cmx *.ml *.o voting_test
|
||||
@@ -0,0 +1,31 @@
|
||||
// A simple test of the abort built-in function.
|
||||
|
||||
contract AbortTest =
|
||||
|
||||
record state = { value : int }
|
||||
|
||||
public function init(v : int) =
|
||||
{ value = v }
|
||||
|
||||
// Aborting
|
||||
public function do_abort(v : int, s : string) : () =
|
||||
put_value(v)
|
||||
revert_abort(s)
|
||||
|
||||
// Accessing the value
|
||||
public function get_value() = state.value
|
||||
public function put_value(v : int) = put(state{value = v})
|
||||
public function get_values() : list(int) = [state.value]
|
||||
public function put_values(v : int) = put(state{value = v})
|
||||
|
||||
// Some basic statistics
|
||||
public function get_stats(acct : address) =
|
||||
( Contract.balance, Chain.balance(acct) )
|
||||
|
||||
// Abort functions.
|
||||
private function revert_abort(s : string) =
|
||||
abort(s)
|
||||
|
||||
// This is still legal but will be stripped out.
|
||||
// TODO: This function confuses the type inference, so it cannot be present.
|
||||
//private function abort(s : string) = 42
|
||||
@@ -0,0 +1,27 @@
|
||||
contract Interface =
|
||||
function do_abort : (int, string) => ()
|
||||
function get_value : () => int
|
||||
function put_value : (int) => ()
|
||||
function get_values : () => list(int)
|
||||
function put_values : (int) => ()
|
||||
|
||||
contract AbortTestInt =
|
||||
|
||||
record state = {r : Interface, value : int}
|
||||
|
||||
public function init(r : Interface, value : int) =
|
||||
{r = r, value = value}
|
||||
|
||||
// Aborting
|
||||
public function do_abort(v : int, s : string) =
|
||||
put_value(v)
|
||||
state.r.do_abort(v + 100, s)
|
||||
|
||||
// Accessing the value
|
||||
public function put_value(v : int) = put(state{value = v})
|
||||
public function get_value() = state.value
|
||||
public function get_values() : list(int) =
|
||||
state.value :: state.r.get_values()
|
||||
public function put_values(v : int) =
|
||||
put_value(v)
|
||||
state.r.put_values(v + 1000)
|
||||
@@ -0,0 +1,55 @@
|
||||
// AENS tests
|
||||
contract AENSTest =
|
||||
|
||||
// Name resolution
|
||||
|
||||
function resolve_word(name : string, key : string) : option(address) =
|
||||
AENS.resolve(name, key)
|
||||
|
||||
function resolve_string(name : string, key : string) : option(string) =
|
||||
AENS.resolve(name, key)
|
||||
|
||||
// Transactions
|
||||
|
||||
function preclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
|
||||
chash : hash) : () = // Commitment hash
|
||||
AENS.preclaim(addr, chash)
|
||||
|
||||
function signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
|
||||
chash : hash, // Commitment hash
|
||||
sign : signature) : () = // Signed by addr (if not Contract.address)
|
||||
AENS.preclaim(addr, chash, signature = sign)
|
||||
|
||||
function claim(addr : address,
|
||||
name : string,
|
||||
salt : int) : () =
|
||||
AENS.claim(addr, name, salt)
|
||||
|
||||
function signedClaim(addr : address,
|
||||
name : string,
|
||||
salt : int,
|
||||
sign : signature) : () =
|
||||
AENS.claim(addr, name, salt, signature = sign)
|
||||
|
||||
// TODO: update() -- how to handle pointers?
|
||||
|
||||
function transfer(owner : address,
|
||||
new_owner : address,
|
||||
name_hash : hash) : () =
|
||||
AENS.transfer(owner, new_owner, name_hash)
|
||||
|
||||
function signedTransfer(owner : address,
|
||||
new_owner : address,
|
||||
name_hash : hash,
|
||||
sign : signature) : () =
|
||||
AENS.transfer(owner, new_owner, name_hash, signature = sign)
|
||||
|
||||
function revoke(owner : address,
|
||||
name_hash : hash) : () =
|
||||
AENS.revoke(owner, name_hash)
|
||||
|
||||
function signedRevoke(owner : address,
|
||||
name_hash : hash,
|
||||
sign : signature) : () =
|
||||
AENS.revoke(owner, name_hash, signature = sign)
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
contract AeToken {
|
||||
function balanceOf(address addr) returns (uint256)
|
||||
}
|
||||
|
||||
contract AEProof {
|
||||
|
||||
AeToken aeToken
|
||||
mapping (bytes32 => Proof) private proofs
|
||||
mapping (address => bytes32[]) private proofsByOwner
|
||||
|
||||
function AEProof(address tokenAddress) {
|
||||
aeToken = AeToken(tokenAddress)
|
||||
}
|
||||
|
||||
struct Proof {
|
||||
address owner
|
||||
uint timestamp
|
||||
uint proofBlock
|
||||
string comment
|
||||
string ipfsHash
|
||||
string document
|
||||
}
|
||||
|
||||
function notarize(string document, string comment, string ipfsHash) onlyTokenHolder {
|
||||
var proofHash = calculateHash(document)
|
||||
var proof = proofs[proofHash]
|
||||
require(proof.owner == address(0))
|
||||
proof.owner = msg.sender
|
||||
proof.timestamp = block.timestamp
|
||||
proof.proofBlock = block.number
|
||||
proof.comment = comment
|
||||
proof.ipfsHash = ipfsHash
|
||||
proof.document = document
|
||||
|
||||
proofsByOwner[msg.sender].push(proofHash)
|
||||
}
|
||||
|
||||
function calculateHash(string document) constant returns (bytes32) {
|
||||
return sha256(document)
|
||||
}
|
||||
|
||||
function getProof(string document) constant returns (address owner, uint timestamp, uint proofBlock, string comment, string ipfsHash, string storedDocument) {
|
||||
var calcHash = calculateHash(document)
|
||||
var proof = proofs[calcHash]
|
||||
require(proof.owner != address(0))
|
||||
owner = proof.owner
|
||||
timestamp = proof.timestamp
|
||||
proofBlock = proof.proofBlock
|
||||
comment = proof.comment
|
||||
ipfsHash = proof.ipfsHash
|
||||
storedDocument = proof.document
|
||||
}
|
||||
|
||||
function getProofByHash(bytes32 hash) constant returns (address owner, uint timestamp, uint proofBlock, string comment, string ipfsHash, string storedDocument) {
|
||||
var proof = proofs[hash]
|
||||
require(proof.owner != address(0))
|
||||
owner = proof.owner
|
||||
timestamp = proof.timestamp
|
||||
proofBlock = proof.proofBlock
|
||||
comment = proof.comment
|
||||
ipfsHash = proof.ipfsHash
|
||||
storedDocument = proof.document
|
||||
}
|
||||
|
||||
function hasProof(string document) constant returns (bool) {
|
||||
var calcHash = calculateHash(document)
|
||||
var storedProof = proofs[calcHash]
|
||||
if (storedProof.owner == address(0)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function getProofsByOwner(address owner) constant returns (bytes32[]) {
|
||||
return proofsByOwner[owner]
|
||||
}
|
||||
|
||||
modifier onlyTokenHolder() {
|
||||
uint balance = aeToken.balanceOf(msg.sender)
|
||||
require(balance > 0)
|
||||
_
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// No imports yet
|
||||
// import contract aetoken
|
||||
// fun balanceOf(addr : Address) : uint
|
||||
|
||||
contract AEProof =
|
||||
|
||||
record proof = { owner: address
|
||||
, timestamp: uint
|
||||
, proofBlock: uint
|
||||
, comment: string
|
||||
, ipfsHash: string
|
||||
, document: string
|
||||
}
|
||||
|
||||
|
||||
record state = { aeToken : aetoken,
|
||||
proofs : map(uint, proof),
|
||||
proofsByOwner : map(address, array(uint)) }
|
||||
|
||||
function notarize(document:string, comment:string, ipfsHash:hash) =
|
||||
let _ = require(aetoken.balanceOf(caller()) > 0)
|
||||
let proofHash: uint = calculateHash(document)
|
||||
let proof : proof = Map.get_(proofHash, state().proofs)
|
||||
let _ = require(proof.owner == #0)
|
||||
let proof' : proof = proof { owner = caller()
|
||||
, timestamp = block().timestamp
|
||||
, proofBlock = block().height
|
||||
, comment = comment
|
||||
, ipfsHash = ipfsHash
|
||||
, document = document
|
||||
}
|
||||
state{ proofsByOwner = Map.insert(caller, proofHash, state.proofsByOwner),
|
||||
proofs = Map.insert(proofHash, proof', state.proofs) }
|
||||
|
||||
|
||||
function calculateHash(document: string) : uint = sha256(document)
|
||||
|
||||
function getProof(document) : proof =
|
||||
let calcHash = calculateHash(document)
|
||||
let proof = Map.get_(calcHash, state().proofs)
|
||||
let _ = require(proof.owner != #0)
|
||||
proof
|
||||
|
||||
function getProofByHash(hash: uint) : proof =
|
||||
let proof = Map.get_(hash, state().proofs)
|
||||
let _ = require(proof.owner != #0)
|
||||
proof
|
||||
|
||||
|
||||
function hasProof(document: string) : bool =
|
||||
let calcHash = calculateHash(document)
|
||||
let storedProof = Map.get_(calcHash, state().proofs)
|
||||
storedProof.owner != #0
|
||||
|
||||
function getProofsByOwner(owner: address): array(uint) =
|
||||
Map.get(owner, state())
|
||||
|
||||
function require(x : bool) : unit = if(x) () else abort("false")
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Try to cover all syntactic constructs.
|
||||
|
||||
contract AllSyntaxType =
|
||||
type typeDecl /* bla */
|
||||
type paramTypeDecl('a, 'b)
|
||||
|
||||
/** Multi-
|
||||
* line
|
||||
* comment
|
||||
*/
|
||||
function foo : _
|
||||
|
||||
contract AllSyntax =
|
||||
|
||||
type typeDecl = int
|
||||
type paramTypeDecl('a, 'b) = (('a, 'b) => 'b) => list('a) => 'b => 'b
|
||||
|
||||
record nestedRecord = { x : int }
|
||||
record recordType = { z : nestedRecord, y : int }
|
||||
datatype variantType('a) = None | Some('a)
|
||||
|
||||
let valWithType : map(int, int) => option(int) = (m) => Map.get(m, 42)
|
||||
let valNoType =
|
||||
if(valWithType(Map.empty) == None)
|
||||
print(42 mod 10 * 5 / 3)
|
||||
|
||||
function funWithType(x : int, y) : (int, list(int)) = (x, 0 :: [y] ++ [])
|
||||
function funNoType() =
|
||||
let foo = (x, y : bool) =>
|
||||
if (! (y && x =< 0x0b || true)) [x]
|
||||
else [11..20]
|
||||
let setY(r : recordType) : unit = r{ y = 5 }
|
||||
let setX(r : recordType, x : int) : recordType = r { z.x = x } // nested record update
|
||||
let getY(r) = switch(r) {y = y} => y
|
||||
switch (funWithType(1, -2))
|
||||
(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)
|
||||
recFun(0)
|
||||
|
||||
let hash : address = #01ab0fff11
|
||||
let b = false
|
||||
let qcon = Mod.Con
|
||||
let str = "blabla\nfoo"
|
||||
let chr = '"'
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
// Compiler failed to detect the map lookup nested in the state update and
|
||||
// generate the appropriate builtin for it.
|
||||
contract BuiltinBug =
|
||||
|
||||
record state = {proofs : map(address, list(string))}
|
||||
|
||||
public function init() = {proofs = {}}
|
||||
|
||||
public stateful function createProof(hash : string) =
|
||||
put( state{ proofs[Call.caller] = hash :: state.proofs[Call.caller] } )
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
contract TestContract =
|
||||
record state = {
|
||||
_allowed : map(address, map(address, int))}
|
||||
|
||||
public stateful function init() = {
|
||||
_allowed = {}}
|
||||
|
||||
public stateful function approve(spender: address, value: int) : bool =
|
||||
|
||||
put(state{_allowed[Call.caller][spender] = value})
|
||||
|
||||
true
|
||||
@@ -0,0 +1,13 @@
|
||||
// Test more advanced chain interactions
|
||||
|
||||
contract Chain =
|
||||
|
||||
record state = { last_bf : address }
|
||||
|
||||
function init() : state =
|
||||
{last_bf = Contract.address}
|
||||
|
||||
function miner() = Chain.coinbase
|
||||
|
||||
function save_coinbase() =
|
||||
put(state{last_bf = Chain.coinbase})
|
||||
@@ -0,0 +1,8 @@
|
||||
contract ChannelEnv =
|
||||
public function coinbase() : address = Chain.coinbase
|
||||
|
||||
public function timestamp() : int = Chain.timestamp
|
||||
|
||||
public function block_height() : int = Chain.block_height
|
||||
|
||||
public function difficulty() : int = Chain.difficulty
|
||||
@@ -0,0 +1,7 @@
|
||||
contract ChannelOnChainContractNameResolution =
|
||||
|
||||
public function can_resolve(name: string, key: string) : bool =
|
||||
switch(AENS.resolve(name, key) : option(string))
|
||||
None => false
|
||||
Some(_address) => true
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
contract ChannelOnChainContractOracle =
|
||||
|
||||
type query_t = string
|
||||
type answer_t = string
|
||||
type oracle_id = oracle(query_t, answer_t)
|
||||
type query_id = oracle_query(query_t, answer_t)
|
||||
|
||||
record state = { oracle : oracle_id,
|
||||
question : string,
|
||||
bets : map(string, address)
|
||||
}
|
||||
|
||||
|
||||
public function init(oracle: oracle_id, question: string) : state =
|
||||
{ oracle = oracle,
|
||||
question = question,
|
||||
bets = {}
|
||||
}
|
||||
|
||||
public stateful function place_bet(answer: string) =
|
||||
switch(Map.lookup(answer, state.bets))
|
||||
None =>
|
||||
put(state{ bets = state.bets{[answer] = Call.caller}})
|
||||
"ok"
|
||||
Some(_value) =>
|
||||
"bet_already_taken"
|
||||
|
||||
public function query_fee() =
|
||||
Oracle.query_fee(state.oracle)
|
||||
|
||||
public function get_question(q: query_id) =
|
||||
Oracle.get_question(state.oracle, q)
|
||||
|
||||
public stateful function resolve(q: query_id) =
|
||||
switch(Oracle.get_answer(state.oracle, q))
|
||||
None =>
|
||||
"no response"
|
||||
Some(result) =>
|
||||
if(state.question == Oracle.get_question(state.oracle, q))
|
||||
switch(Map.lookup(result, state.bets))
|
||||
None =>
|
||||
"no winning bet"
|
||||
Some(winner) =>
|
||||
Chain.spend(winner, Contract.balance)
|
||||
"ok"
|
||||
else
|
||||
"different question"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
contract Remote =
|
||||
function get : () => int
|
||||
function can_resolve : (string, string) => bool
|
||||
|
||||
contract RemoteCall =
|
||||
|
||||
function remote_resolve(r : Remote, name: string, key: string) : bool =
|
||||
r.can_resolve(name, key)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
contract Chess =
|
||||
|
||||
type board = map(int, map(int, string))
|
||||
type state = board
|
||||
|
||||
private function get_row(r, m : board) =
|
||||
Map.lookup_default(r, m, {})
|
||||
|
||||
private function set_piece(r, c, p, m : board) =
|
||||
m { [r] = get_row(r, m) { [c] = p } }
|
||||
|
||||
private function get_piece(r, c, m : board) =
|
||||
Map.lookup(c, get_row(r, m))
|
||||
|
||||
private function from_list(xs, m : board) =
|
||||
switch(xs)
|
||||
[] => m
|
||||
(r, c, p) :: xs => from_list(xs, set_piece(r, c, p, m))
|
||||
|
||||
function init() =
|
||||
from_list([ (2, 1, "white pawn"), (7, 1, "black pawn")
|
||||
, (2, 2, "white pawn"), (7, 2, "black pawn")
|
||||
, (2, 3, "white pawn"), (7, 3, "black pawn")
|
||||
, (2, 4, "white pawn"), (7, 4, "black pawn")
|
||||
, (2, 5, "white pawn"), (7, 5, "black pawn")
|
||||
, (2, 6, "white pawn"), (7, 6, "black pawn")
|
||||
, (2, 7, "white pawn"), (7, 7, "black pawn")
|
||||
, (2, 8, "white pawn"), (7, 8, "black pawn")
|
||||
, (1, 1, "white rook"), (8, 1, "black rook")
|
||||
, (1, 2, "white knight"), (8, 2, "black knight")
|
||||
, (1, 3, "white bishop"), (8, 3, "black bishop")
|
||||
, (1, 4, "white queen"), (8, 4, "black queen")
|
||||
, (1, 5, "white king"), (8, 5, "black king")
|
||||
, (1, 6, "white bishop"), (8, 6, "black bishop")
|
||||
, (1, 7, "white knight"), (8, 7, "black knight")
|
||||
, (1, 8, "white rook"), (8, 8, "black rook")
|
||||
], {})
|
||||
|
||||
function piece(r, c) = get_piece(r, c, state)
|
||||
|
||||
function move_piece(r, c, r1, c1) =
|
||||
switch(piece(r, c))
|
||||
Some(p) => put(set_piece(r1, c1, p, state))
|
||||
|
||||
function destroy_piece(r, c) =
|
||||
put(state{ [r] = Map.delete(c, get_row(r, state)) })
|
||||
|
||||
function delete_row(r) =
|
||||
put(Map.delete(r, state))
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
contract Remote =
|
||||
function up_to : (int) => list(int)
|
||||
function sum : (list(int)) => int
|
||||
function some_string : () => string
|
||||
function pair : (int, string) => (int, string)
|
||||
function squares : (int) => list((int, int))
|
||||
function filter_some : (list(option(int))) => list(int)
|
||||
function all_some : (list(option(int))) => option(list(int))
|
||||
|
||||
contract ComplexTypes =
|
||||
|
||||
record state = { worker : Remote }
|
||||
|
||||
function init(worker) = {worker = worker}
|
||||
|
||||
function sum_acc(xs, n) =
|
||||
switch(xs)
|
||||
[] => n
|
||||
x :: xs => sum_acc(xs, x + n)
|
||||
|
||||
// Sum a list of integers
|
||||
function sum(xs : list(int)) =
|
||||
sum_acc(xs, 0)
|
||||
|
||||
function up_to_acc(n, xs) =
|
||||
switch(n)
|
||||
0 => xs
|
||||
_ => up_to_acc(n - 1, n :: xs)
|
||||
|
||||
function up_to(n) = up_to_acc(n, [])
|
||||
|
||||
record answer('a) = {label : string, result : 'a}
|
||||
|
||||
function remote_triangle(worker, n) : answer(int) =
|
||||
let xs = worker.up_to(gas = 100000, n)
|
||||
let t = worker.sum(xs)
|
||||
{ label = "answer:", result = t }
|
||||
|
||||
function remote_list(n) : list(int) =
|
||||
state.worker.up_to(n)
|
||||
|
||||
function some_string() = "string"
|
||||
|
||||
function remote_string() : string =
|
||||
state.worker.some_string()
|
||||
|
||||
function pair(x : int, y : string) = (x, y)
|
||||
|
||||
function remote_pair(n : int, s : string) : (int, string) =
|
||||
state.worker.pair(gas = 10000, n, s)
|
||||
|
||||
function map(f, xs) =
|
||||
switch(xs)
|
||||
[] => []
|
||||
x :: xs => f(x) :: map(f, xs)
|
||||
|
||||
function squares(n) =
|
||||
map((i) => (i, i * i), up_to(n))
|
||||
|
||||
function remote_squares(n) : list((int, int)) =
|
||||
state.worker.squares(n)
|
||||
|
||||
// option types
|
||||
|
||||
function filter_some(xs : list(option(int))) : list(int) =
|
||||
switch(xs)
|
||||
[] => []
|
||||
None :: ys => filter_some(ys)
|
||||
Some(x) :: ys => x :: filter_some(ys)
|
||||
|
||||
function remote_filter_some(xs : list(option(int))) : list(int) =
|
||||
state.worker.filter_some(xs)
|
||||
|
||||
function all_some(xs : list(option(int))) : option(list(int)) =
|
||||
switch(xs)
|
||||
[] => Some([])
|
||||
None :: ys => None
|
||||
Some(x) :: ys =>
|
||||
switch(all_some(ys))
|
||||
Some(xs) => Some(x :: xs)
|
||||
None => None
|
||||
|
||||
function remote_all_some(xs : list(option(int))) : option(list(int)) =
|
||||
state.worker.all_some(gas = 10000, xs)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
contract OtherContract =
|
||||
|
||||
function multiply : (int, int) => int
|
||||
|
||||
contract ThisContract =
|
||||
|
||||
record state = { server : OtherContract, n : int }
|
||||
|
||||
function init(server : OtherContract) =
|
||||
{ server = server, n = 2 }
|
||||
|
||||
function square() =
|
||||
put(state{ n @ n = state.server.multiply(value = 100, n, n) })
|
||||
|
||||
function get_n() = state.n
|
||||
|
||||
function tip_server() =
|
||||
Chain.spend(state.server.address, Call.value)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
contract Counter =
|
||||
|
||||
record state = { value : int }
|
||||
|
||||
function init(val) = { value = val }
|
||||
function get() = state.value
|
||||
function tick() = put(state{ value = state.value + 1 })
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Dutch auction example
|
||||
//
|
||||
contract DutchAuction =
|
||||
|
||||
record state = { start_amount : int,
|
||||
start_height : int,
|
||||
dec : int,
|
||||
beneficiary : address,
|
||||
sold : bool }
|
||||
|
||||
// Add to work around current lack of predefined functions
|
||||
private function spend(to, amount) =
|
||||
let total = Contract.balance
|
||||
Chain.spend(to, amount)
|
||||
total - amount
|
||||
|
||||
private function require(b : bool, err : string) =
|
||||
if(!b) abort(err)
|
||||
|
||||
// TTL set by user on posting contract, typically (start - end ) div dec
|
||||
public function init(beneficiary, start, decrease) : state =
|
||||
require(start > 0 && decrease > 0, "bad args")
|
||||
{ start_amount = start,
|
||||
start_height = Chain.block_height,
|
||||
beneficiary = beneficiary,
|
||||
dec = decrease,
|
||||
sold = false }
|
||||
|
||||
// -- API
|
||||
|
||||
// We are the buyer... interesting case to buy for someone else and keep 10%
|
||||
public stateful function bid() =
|
||||
require( !(state.sold), "sold")
|
||||
let cost =
|
||||
state.start_amount - (Chain.block_height - state.start_height) * state.dec
|
||||
require( Contract.balance >= cost, "no money")
|
||||
|
||||
// transaction(SpendTx({recipient = state.beneficiary,
|
||||
// amount = cost })) // or self.balance ** burn money **
|
||||
spend(state.beneficiary, cost)
|
||||
spend(Call.caller, Contract.balance)
|
||||
put(state{sold = true})
|
||||
@@ -0,0 +1,69 @@
|
||||
|
||||
// Testing primitives for accessing the block chain environment
|
||||
contract Interface =
|
||||
function contract_address : () => address
|
||||
function call_origin : () => address
|
||||
function call_caller : () => address
|
||||
function call_value : () => int
|
||||
|
||||
contract Environment =
|
||||
|
||||
record state = {remote : Interface}
|
||||
|
||||
function init(remote) = {remote = remote}
|
||||
|
||||
function set_remote(remote) = put({remote = remote})
|
||||
|
||||
// -- Information about the this contract ---
|
||||
|
||||
// Address
|
||||
function contract_address() : address = Contract.address
|
||||
function nested_address(who) : address =
|
||||
who.contract_address(gas = 1000)
|
||||
|
||||
// Balance
|
||||
function contract_balance() : int = Contract.balance
|
||||
|
||||
// -- Information about the current call ---
|
||||
|
||||
// Origin
|
||||
function call_origin() : address = Call.origin
|
||||
function nested_origin() : address =
|
||||
state.remote.call_origin()
|
||||
|
||||
// Caller
|
||||
function call_caller() : address = Call.caller
|
||||
function nested_caller() : address =
|
||||
state.remote.call_caller()
|
||||
|
||||
// Value
|
||||
function call_value() : int = Call.value
|
||||
function nested_value(value : int) : int =
|
||||
state.remote.call_value(value = value / 2)
|
||||
|
||||
// Gas price
|
||||
function call_gas_price() : int = Call.gas_price
|
||||
|
||||
// -- Information about the chain ---
|
||||
|
||||
// Account balances
|
||||
function get_balance(acct : address) : int = Chain.balance(acct)
|
||||
|
||||
// Block hash
|
||||
function block_hash(height : int) : int = Chain.block_hash(height)
|
||||
|
||||
// Coinbase
|
||||
function coinbase() : address = Chain.coinbase
|
||||
|
||||
// Block timestamp
|
||||
function timestamp() : int = Chain.timestamp
|
||||
|
||||
// Block height
|
||||
function block_height() : int = Chain.block_height
|
||||
|
||||
// Difficulty
|
||||
function difficulty() : int = Chain.difficulty
|
||||
|
||||
// Gas limit
|
||||
function gas_limit() : int = Chain.gas_limit
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
contract ERC20Token =
|
||||
record state = {
|
||||
totalSupply : int,
|
||||
decimals : int,
|
||||
name : string,
|
||||
symbol : string,
|
||||
balances : map(address, int),
|
||||
allowed : map(address, map(address,int)),
|
||||
// Logs, remove when native Events are there
|
||||
transfer_log : list((address,address,int)),
|
||||
approval_log : list((address,address,int))}
|
||||
|
||||
// init(100000000, 10, "Token Name", "TKN")
|
||||
public stateful function init(_totalSupply : int, _decimals : int, _name : string, _symbol : string ) = {
|
||||
totalSupply = _totalSupply,
|
||||
decimals = _decimals,
|
||||
name = _name,
|
||||
symbol = _symbol,
|
||||
balances = {[Call.caller] = _totalSupply }, // creator gets all Tokens
|
||||
allowed = {},
|
||||
// Logs, remove when native Events are there
|
||||
transfer_log = [],
|
||||
approval_log = []}
|
||||
|
||||
public stateful function totalSupply() : int = state.totalSupply
|
||||
public stateful function decimals() : int = state.decimals
|
||||
public stateful function name() : string = state.name
|
||||
public stateful function symbol() : string = state.symbol
|
||||
|
||||
public stateful function balanceOf(tokenOwner : address ) : int =
|
||||
Map.lookup_default(tokenOwner, state.balances, 0)
|
||||
|
||||
public stateful function transfer(to : address, tokens : int) =
|
||||
put( state{balances[Call.caller] = sub(state.balances[Call.caller], tokens) })
|
||||
put( state{balances[to] = add(Map.lookup_default(to, state.balances, 0), tokens) })
|
||||
transferEvent(Call.caller, to, tokens)
|
||||
true
|
||||
|
||||
public stateful function approve(spender : address, tokens : int) =
|
||||
// allowed[Call.caller] field must have a value!
|
||||
ensure_allowed(Call.caller)
|
||||
put( state{allowed[Call.caller][spender] = tokens} )
|
||||
approvalEvent(Call.caller, spender, tokens)
|
||||
true
|
||||
|
||||
public stateful function transferFrom(from : address, to : address, tokens : int) =
|
||||
put( state{ balances[from] = sub(state.balances[from], tokens) })
|
||||
put( state{ allowed[from][Call.caller] = sub(state.allowed[from][Call.caller], tokens) })
|
||||
put( state{ balances[to] = add(balanceOf(to), tokens) })
|
||||
transferEvent(from, to, tokens)
|
||||
true
|
||||
|
||||
public function allowance(_owner : address, _spender : address) : int =
|
||||
state.allowed[_owner][_spender]
|
||||
|
||||
public stateful function getTransferLog() : list((address,address,int)) =
|
||||
state.transfer_log
|
||||
public stateful function getApprovalLog() : list((address,address,int)) =
|
||||
state.approval_log
|
||||
|
||||
//
|
||||
// Private Functions
|
||||
//
|
||||
|
||||
private function ensure_allowed(key : address) =
|
||||
switch(Map.lookup(key, state.allowed))
|
||||
None => put(state{allowed[key] = {}})
|
||||
Some(_) => ()
|
||||
|
||||
private function transferEvent(from : address, to : address, tokens : int) =
|
||||
let e = (from, to, tokens)
|
||||
put( state{transfer_log = e :: state.transfer_log })
|
||||
e
|
||||
|
||||
private function approvalEvent(from : address, to : address, tokens : int) =
|
||||
let e = (from, to, tokens)
|
||||
put( state{approval_log = e :: state.approval_log })
|
||||
e
|
||||
|
||||
private function require(b : bool, err : string) =
|
||||
if(!b) abort(err)
|
||||
|
||||
private function sub(_a : int, _b : int) : int =
|
||||
require(_b =< _a, "Error")
|
||||
_a - _b
|
||||
|
||||
private function add(_a : int, _b : int) : int =
|
||||
let c : int = _a + _b
|
||||
require(c >= _a, "Error")
|
||||
c
|
||||
@@ -0,0 +1,22 @@
|
||||
contract Events =
|
||||
type alias_int = int
|
||||
type alias_address = address
|
||||
type alias_string = string
|
||||
|
||||
datatype event =
|
||||
Event1(indexed alias_int, indexed int, string)
|
||||
| Event2(alias_string, indexed alias_address)
|
||||
// | BadEvent1(indexed string, string)
|
||||
// | BadEvent2(indexed int, int)
|
||||
|
||||
function f1(x : int, y : string) =
|
||||
Chain.event(Event1(x, x+1, y))
|
||||
|
||||
function f2(s : string) =
|
||||
Chain.event(Event2(s, Call.caller))
|
||||
|
||||
function f3(x : int) =
|
||||
Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
|
||||
|
||||
function i2s(i : int) = Int.to_str(i)
|
||||
function a2s(a : address) = Address.to_str(a)
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
contract Exploits =
|
||||
|
||||
// We'll hack the bytecode of this changing the return type to string.
|
||||
function pair(n : int) = (n, 0)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// An implementation of the factorial function where each recursive
|
||||
// call is to another contract. Not the cheapest way to compute factorial.
|
||||
contract FactorialServer =
|
||||
function fac : (int) => int
|
||||
|
||||
contract Factorial =
|
||||
|
||||
record state = {worker : FactorialServer}
|
||||
|
||||
function init(worker) = {worker = worker}
|
||||
|
||||
function set_worker(worker) = put(state{worker = worker})
|
||||
|
||||
function fac(x : int) : int =
|
||||
if(x == 0) 1
|
||||
else x * state.worker.fac(x - 1)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* A simple crowd-funding example
|
||||
*/
|
||||
contract FundMe =
|
||||
|
||||
record spend_args = { recipient : address,
|
||||
amount : int }
|
||||
|
||||
record state = { contributions : map(address, int),
|
||||
total : int,
|
||||
beneficiary : address,
|
||||
deadline : int,
|
||||
goal : int }
|
||||
|
||||
private function require(b : bool, err : string) =
|
||||
if(!b) abort(err)
|
||||
|
||||
private function spend(args : spend_args) =
|
||||
Chain.spend(args.recipient, args.amount)
|
||||
|
||||
public function init(beneficiary, deadline, goal) : state =
|
||||
{ contributions = {},
|
||||
beneficiary = beneficiary,
|
||||
deadline = deadline,
|
||||
total = 0,
|
||||
goal = goal }
|
||||
|
||||
private function is_contributor(addr) =
|
||||
Map.member(addr, state.contributions)
|
||||
|
||||
public stateful function contribute() =
|
||||
if(Chain.block_height >= state.deadline)
|
||||
spend({ recipient = Call.caller, amount = Call.value }) // Refund money
|
||||
false
|
||||
else
|
||||
let amount =
|
||||
Map.lookup_default(Call.caller, state.contributions, 0) + Call.value
|
||||
put(state{ contributions[Call.caller] = amount,
|
||||
total @ tot = tot + Call.value })
|
||||
true
|
||||
|
||||
public stateful function withdraw() =
|
||||
if(Chain.block_height < state.deadline)
|
||||
abort("Cannot withdraw before deadline")
|
||||
if(Call.caller == state.beneficiary)
|
||||
withdraw_beneficiary()
|
||||
elif(is_contributor(Call.caller))
|
||||
withdraw_contributor()
|
||||
else
|
||||
abort("Not a contributor or beneficiary")
|
||||
|
||||
private stateful function withdraw_beneficiary() =
|
||||
require(state.total >= state.goal, "Project was not funded")
|
||||
spend({recipient = state.beneficiary,
|
||||
amount = Contract.balance })
|
||||
put(state{ beneficiary = #0 })
|
||||
|
||||
private stateful function withdraw_contributor() =
|
||||
if(state.total >= state.goal)
|
||||
abort("Project was funded")
|
||||
let to = Call.caller
|
||||
spend({recipient = to,
|
||||
amount = state.contributions[to]})
|
||||
put(state{ contributions @ c = Map.delete(to, c) })
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
contract Identity =
|
||||
function main (x:int) = x
|
||||
@@ -0,0 +1,9 @@
|
||||
contract Remote =
|
||||
function missing : (int) => int
|
||||
|
||||
contract Init_error =
|
||||
|
||||
record state = {value : int}
|
||||
|
||||
function init(r : Remote, x : int) =
|
||||
{value = r.missing(x)}
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
contract MapOfMaps =
|
||||
|
||||
type board = map(int, map(int, string))
|
||||
type map2('a, 'b, 'c) = map('a, map('b, 'c))
|
||||
|
||||
record state = { big1 : map2(string, string, string),
|
||||
big2 : map2(string, string, string),
|
||||
small1 : map(string, string),
|
||||
small2 : map(string, string) }
|
||||
|
||||
private function empty_state() =
|
||||
{ big1 = {}, big2 = {},
|
||||
small1 = {}, small2 = {} }
|
||||
|
||||
function init() = empty_state()
|
||||
|
||||
function setup_state() =
|
||||
let small = {["key"] = "val"}
|
||||
put({ big1 = {["one"] = small},
|
||||
big2 = {["two"] = small},
|
||||
small1 = small,
|
||||
small2 = small })
|
||||
|
||||
// -- Garbage collection of inner map when outer map is garbage collected
|
||||
function test1_setup() =
|
||||
let inner = {["key"] = "val"}
|
||||
put(empty_state() { big1 = {["one"] = inner} })
|
||||
|
||||
function test1_execute() =
|
||||
put(state{ big1 = {} })
|
||||
|
||||
function test1_check() =
|
||||
state.big1
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
contract Maps =
|
||||
|
||||
record pt = {x : int, y : int}
|
||||
record state = { map_i : map(int, pt),
|
||||
map_s : map(string, pt) }
|
||||
|
||||
function init() = { map_i = {}, map_s = {} }
|
||||
|
||||
function get_state() = state
|
||||
|
||||
// {[k] = v}
|
||||
function map_i() =
|
||||
{ [1] = {x = 1, y = 2},
|
||||
[2] = {x = 3, y = 4},
|
||||
[3] = {x = 5, y = 6} }
|
||||
function map_s() =
|
||||
{ ["one"] = {x = 1, y = 2},
|
||||
["two"] = {x = 3, y = 4},
|
||||
["three"] = {x = 5, y = 6} }
|
||||
function map_state_i() = put(state{ map_i = map_i() })
|
||||
function map_state_s() = put(state{ map_s = map_s() })
|
||||
|
||||
// m[k]
|
||||
function get_i(k, m : map(int, pt)) = m[k]
|
||||
function get_s(k, m : map(string, pt)) = m[k]
|
||||
function get_state_i(k) = get_i(k, state.map_i)
|
||||
function get_state_s(k) = get_s(k, state.map_s)
|
||||
|
||||
// m[k = v]
|
||||
function get_def_i(k, v, m : map(int, pt)) = m[k = v]
|
||||
function get_def_s(k, v, m : map(string, pt)) = m[k = v]
|
||||
function get_def_state_i(k, v) = get_def_i(k, v, state.map_i)
|
||||
function get_def_state_s(k, v) = get_def_s(k, v, state.map_s)
|
||||
|
||||
// m{[k] = v}
|
||||
function set_i(k, p, m : map(int, pt)) = m{ [k] = p }
|
||||
function set_s(k, p, m : map(string, pt)) = m{ [k] = p }
|
||||
function set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) })
|
||||
function set_state_s(k, p) = put(state{ map_s = set_s(k, p, state.map_s) })
|
||||
|
||||
// m{f[k].x = v}
|
||||
function setx_i(k, x, m : map(int, pt)) = m{ [k].x = x }
|
||||
function setx_s(k, x, m : map(string, pt)) = m{ [k].x = x }
|
||||
function setx_state_i(k, x) = put(state{ map_i[k].x = x })
|
||||
function setx_state_s(k, x) = put(state{ map_s[k].x = x })
|
||||
|
||||
// m{[k] @ x = v }
|
||||
function addx_i(k, d, m : map(int, pt)) = m{ [k].x @ x = x + d }
|
||||
function addx_s(k, d, m : map(string, pt)) = m{ [k].x @ x = x + d }
|
||||
function addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d })
|
||||
function addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d })
|
||||
|
||||
// m{[k = def] @ x = v }
|
||||
function addx_def_i(k, v, d, m : map(int, pt)) = m{ [k = v].x @ x = x + d }
|
||||
function addx_def_s(k, v, d, m : map(string, pt)) = m{ [k = v].x @ x = x + d }
|
||||
|
||||
// Map.member
|
||||
function member_i(k, m : map(int, pt)) = Map.member(k, m)
|
||||
function member_s(k, m : map(string, pt)) = Map.member(k, m)
|
||||
function member_state_i(k) = member_i(k, state.map_i)
|
||||
function member_state_s(k) = member_s(k, state.map_s)
|
||||
|
||||
// Map.lookup
|
||||
function lookup_i(k, m : map(int, pt)) = Map.lookup(k, m)
|
||||
function lookup_s(k, m : map(string, pt)) = Map.lookup(k, m)
|
||||
function lookup_state_i(k) = lookup_i(k, state.map_i)
|
||||
function lookup_state_s(k) = lookup_s(k, state.map_s)
|
||||
|
||||
// Map.lookup_default
|
||||
function lookup_def_i(k, m : map(int, pt), def : pt) =
|
||||
Map.lookup_default(k, m, def)
|
||||
function lookup_def_s(k, m : map(string, pt), def : pt) =
|
||||
Map.lookup_default(k, m, def)
|
||||
function lookup_def_state_i(k, def) = lookup_def_i(k, state.map_i, def)
|
||||
function lookup_def_state_s(k, def) = lookup_def_s(k, state.map_s, def)
|
||||
|
||||
// Map.delete
|
||||
function delete_i(k, m : map(int, pt)) = Map.delete(k, m)
|
||||
function delete_s(k, m : map(string, pt)) = Map.delete(k, m)
|
||||
function delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) })
|
||||
function delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) })
|
||||
|
||||
// Map.size
|
||||
function size_i(m : map(int, pt)) = Map.size(m)
|
||||
function size_s(m : map(string, pt)) = Map.size(m)
|
||||
function size_state_i() = size_i(state.map_i)
|
||||
function size_state_s() = size_s(state.map_s)
|
||||
|
||||
// Map.to_list
|
||||
function tolist_i(m : map(int, pt)) = Map.to_list(m)
|
||||
function tolist_s(m : map(string, pt)) = Map.to_list(m)
|
||||
function tolist_state_i() = tolist_i(state.map_i)
|
||||
function tolist_state_s() = tolist_s(state.map_s)
|
||||
|
||||
// Map.from_list
|
||||
function fromlist_i(xs : list((int, pt))) = Map.from_list(xs)
|
||||
function fromlist_s(xs : list((string, pt))) = Map.from_list(xs)
|
||||
function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) })
|
||||
function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) })
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
contract MapUpdater =
|
||||
function update_map : (int, string, map(int, string)) => map(int, string)
|
||||
|
||||
contract Benchmark =
|
||||
|
||||
record state = { updater : MapUpdater,
|
||||
map : map(int, string) }
|
||||
|
||||
function init(u, m) = { updater = u, map = m }
|
||||
|
||||
function set_updater(u) = put(state{ updater = u })
|
||||
|
||||
function update_map(k : int, v : string, m) = m{ [k] = v }
|
||||
|
||||
function update(a : int, b : int, v : string) =
|
||||
if (a > b) ()
|
||||
else
|
||||
put(state{ map[a] = v })
|
||||
update(a + 1, b, v)
|
||||
|
||||
function get(k) = state.map[k]
|
||||
function noop() = ()
|
||||
|
||||
function benchmark(k, v) =
|
||||
let m = state.updater.update_map(k, v, state.map)
|
||||
put(state{ map = m })
|
||||
m
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
contract MinimalInit =
|
||||
|
||||
record state = {foo : int}
|
||||
|
||||
function init() =
|
||||
{ foo = 0 }
|
||||
@@ -0,0 +1,123 @@
|
||||
/* Multi-signature wallet from
|
||||
https://github.com/ethereum/dapp-bin/blob/master/wallet/wallet.sol
|
||||
|
||||
*/
|
||||
|
||||
contract MultiSig =
|
||||
|
||||
record pending_state = { yetNeeded : uint, ownersDone : uint, index : uint }
|
||||
|
||||
datatype event =
|
||||
Confirmation (address, hash) // of { .owner : Address, .operation : Hash }
|
||||
| Revoke (address, hash) // of { .owner : Address, .operation : Hash }
|
||||
| OwnerChanged (address, address) // of { .oldOwner : Address, .newOwner : Address }
|
||||
| OwnerAdded (address) // of { .newOwner : Address }
|
||||
| OwnerRemoved (address) // of { .removedOwner : Address }
|
||||
| ReqChanged (uint) // of { .newReq : uint }
|
||||
|
||||
let maxOwners : uint = 250
|
||||
|
||||
record state = { nRequired : uint
|
||||
, nOwners : uint
|
||||
, owners : map(uint, address)
|
||||
, ownerIndex : map(address, uint)
|
||||
, pending : map(hash, pending_state)
|
||||
, pendingIndex : list(address) }
|
||||
|
||||
function init (owners : list(address), nRequired : uint) : state =
|
||||
let n = length(owners) + 1
|
||||
{ nRequired = nRequired,
|
||||
nOwners = n,
|
||||
owners = Map.from_list(List.zip([1..n], caller() :: owners)),
|
||||
ownerIndex = Map.from_list(List.zip(caller() :: owners, [1..n])) }
|
||||
|
||||
function lookup(map, key) =
|
||||
switch(Map.get(key, map))
|
||||
None => abort("not found")
|
||||
Some(value) => value
|
||||
|
||||
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 pending' = pending { yetNeeded = pending.yetNeeded + 1
|
||||
, ownersDone = pending.ownersDone - ownerIxBit }
|
||||
put(state{ pendingIndex.operator = pending' })
|
||||
event(Revoke(caller, operation))
|
||||
|
||||
|
||||
datatype check_pending = CheckOk(state) | CheckFail(state)
|
||||
|
||||
function changeOwner(fromOwner : address, toOwner : address) =
|
||||
switch(check_pending(callhash()))
|
||||
CheckFail(state') => { state = state' }
|
||||
CheckOk(state') =>
|
||||
if(isOwner(toOwner)) put(state')
|
||||
else
|
||||
switch(Map.get(fromOwner, state.ownerIndex))
|
||||
None => { state = state' }
|
||||
Some(ownerIx) =>
|
||||
{ state = state' { owners = Map.insert(ownerIx, toOwner, state'.owners)
|
||||
, ownerIndex = Map.delete(fromOwner, Map.insert(toOwner, ownerIx, state'.ownerIndex))
|
||||
, pending = Map.empty
|
||||
, pendingIx = [] },
|
||||
events = [OwnerChanged(fromOwner, toOwner)] }
|
||||
|
||||
function addOwner(newOwner : address) =
|
||||
let _ = require (!isOwner(newOwner))
|
||||
switch(check_pending(callhash()))
|
||||
CheckFail(state') => { state = state' }
|
||||
CheckOk(state') =>
|
||||
if(state.nOwners >= maxOwners) () /* TODO */
|
||||
else
|
||||
let nOwners' = state'.nOwners + 1
|
||||
{ state = state' { owners = Map.insert(nOwners', newOwner, state'.owners)
|
||||
, ownerIndex = Map.insert(newOwner, nOwners', state'.ownerIndex)
|
||||
, pending = Map.empty
|
||||
, pendingIx = [] },
|
||||
event = [OwnerAdded(newOwner)] }
|
||||
|
||||
function removeOwner(oldOwner : address) =
|
||||
let _ = require(isOwner(oldOwner))
|
||||
let _ = require(state.nRequired > state.nOwners - 1)
|
||||
switch(check_pending(callhash()))
|
||||
CheckFail(state') => { state = state' }
|
||||
CheckOk(state') =>
|
||||
let ownerIx = lookup(state'.ownerIndex, oldOwner)
|
||||
{ state = state' { owners = Map.delete(ownerIx, state'.owners)
|
||||
, ownerIndex = Map.delete(newOwner, state'.ownerIndex)
|
||||
, pending = Map.empty
|
||||
, pendingIx = [] },
|
||||
event = [OwnerRemoved(oldOwner)] }
|
||||
|
||||
function changeRequirement(newReq : uint) =
|
||||
let _ = require(newReq =< state.nOwners)
|
||||
switch(check_pending(callhash()))
|
||||
CheckFail(state') => { state = state' }
|
||||
CheckOk(state') =>
|
||||
{ state = state' { nRequired = newReq
|
||||
, pending = Map.empty
|
||||
, pendingIx = [] },
|
||||
event = [ReqChanged(newReq)] }
|
||||
|
||||
|
||||
function getOwner(ownerIx0 : uint) =
|
||||
lookup(state.owners, ownerIx0 + 1)
|
||||
|
||||
function isOwner(owner : address) =
|
||||
switch(Map.get(owner, state.ownerIndex))
|
||||
None => false
|
||||
Some(_) => true
|
||||
|
||||
function hasConfirmed(operation : hash, owner : address) =
|
||||
switch(Map.get(operation, state.pending))
|
||||
None => false
|
||||
Some(pending) =>
|
||||
let _ = require(isOwner(owner))
|
||||
let ownerIx = lookup(state.ownerIndex, owner)
|
||||
let ownerIxBit = 1 bsl (ownerIx - 1)
|
||||
(pending.ownersDone band ownerIxBit) != 0
|
||||
|
||||
/* Leave the rest for now... */
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
contract MultiplicationServer =
|
||||
|
||||
function multiply(x : int, y : int) =
|
||||
switch(Call.value >= 100)
|
||||
true => x * y
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
contract NameClash =
|
||||
|
||||
function double_proto : () => int
|
||||
function double_proto : () => int
|
||||
|
||||
function proto_and_def : int => int
|
||||
function proto_and_def(n) = n + 1
|
||||
|
||||
function double_def(x) = x
|
||||
function double_def(y) = 0
|
||||
|
||||
// abort, put and state are builtin
|
||||
function abort() : int = 0
|
||||
function put(x) = x
|
||||
function state(x, y) = x + y
|
||||
@@ -0,0 +1,44 @@
|
||||
// - + * / mod arithmetic operators
|
||||
// bnot band bor bxor bsl bsr bitwise operators
|
||||
// ! && || logical operators
|
||||
// == != < > =< >= comparison operators
|
||||
// :: ++ list operators
|
||||
|
||||
contract Operators =
|
||||
function int_op(a : int, b : int, op : string) =
|
||||
switch(op)
|
||||
"+" => a + b
|
||||
"-" => a - b
|
||||
"*" => a * b
|
||||
"/" => 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)
|
||||
"!" => !a
|
||||
"&&" => a && b
|
||||
"||" => a || b
|
||||
|
||||
function cmp_op(a : int, b : int, op : string) =
|
||||
switch(op)
|
||||
"==" => a == b
|
||||
"!=" => a != b
|
||||
"<" => a < b
|
||||
">" => a > b
|
||||
"=<" => a =< b
|
||||
">=" => a >= b
|
||||
|
||||
function cons(a, l) = a :: l
|
||||
function concat(l1, l2) = l1 ++ l2
|
||||
|
||||
function hash(s) = // String.sha3(s)
|
||||
let x = String.sha3(s)
|
||||
let y = String.sha3(s)
|
||||
(x, y)
|
||||
@@ -0,0 +1,112 @@
|
||||
contract Oracles =
|
||||
|
||||
type fee = int
|
||||
type ttl = Chain.ttl
|
||||
|
||||
type query_t = string
|
||||
type answer_t = int
|
||||
|
||||
type oracle_id = oracle(query_t, answer_t)
|
||||
type query_id = oracle_query(query_t, answer_t)
|
||||
|
||||
function registerOracle(acct : address,
|
||||
qfee : fee,
|
||||
ttl : ttl) : oracle_id =
|
||||
Oracle.register(acct, qfee, ttl)
|
||||
|
||||
function registerIntIntOracle(acct : address,
|
||||
qfee : fee,
|
||||
ttl : ttl) : oracle(int, int) =
|
||||
Oracle.register(acct, qfee, ttl)
|
||||
|
||||
function registerStringStringOracle(acct : address,
|
||||
qfee : fee,
|
||||
ttl : ttl) : oracle(string, string) =
|
||||
Oracle.register(acct, qfee, ttl)
|
||||
|
||||
function signedRegisterOracle(acct : address,
|
||||
sign : signature,
|
||||
qfee : fee,
|
||||
ttl : ttl) : oracle_id =
|
||||
Oracle.register(acct, qfee, ttl, signature = sign)
|
||||
|
||||
function queryFee(o : oracle_id) : fee =
|
||||
Oracle.query_fee(o)
|
||||
|
||||
function createQuery(o : oracle_id,
|
||||
q : query_t,
|
||||
qfee : fee,
|
||||
qttl : ttl,
|
||||
rttl : ttl) : query_id =
|
||||
require(qfee =< Call.value, "insufficient value for qfee")
|
||||
Oracle.query(o, q, qfee, qttl, rttl)
|
||||
|
||||
// Do not use in production!
|
||||
function unsafeCreateQuery(o : oracle_id,
|
||||
q : query_t,
|
||||
qfee : fee,
|
||||
qttl : ttl,
|
||||
rttl : ttl) : query_id =
|
||||
Oracle.query(o, q, qfee, qttl, rttl)
|
||||
|
||||
// Do not use in production!
|
||||
function unsafeCreateQueryThenErr(o : oracle_id,
|
||||
q : query_t,
|
||||
qfee : fee,
|
||||
qttl : ttl,
|
||||
rttl : ttl) : query_id =
|
||||
let res = Oracle.query(o, q, qfee, qttl, rttl)
|
||||
require(qfee >= 100000000000000000, "causing a late error")
|
||||
res
|
||||
|
||||
function extendOracle(o : oracle_id,
|
||||
ttl : ttl) : () =
|
||||
Oracle.extend(o, ttl)
|
||||
|
||||
function signedExtendOracle(o : oracle_id,
|
||||
sign : signature, // Signed oracle address
|
||||
ttl : ttl) : () =
|
||||
Oracle.extend(o, signature = sign, ttl)
|
||||
|
||||
function respond(o : oracle_id,
|
||||
q : query_id,
|
||||
r : answer_t) : () =
|
||||
Oracle.respond(o, q, r)
|
||||
|
||||
function signedRespond(o : oracle_id,
|
||||
q : query_id,
|
||||
sign : signature,
|
||||
r : answer_t) : () =
|
||||
Oracle.respond(o, q, signature = sign, r)
|
||||
|
||||
function getQuestion(o : oracle_id,
|
||||
q : query_id) : query_t =
|
||||
Oracle.get_question(o, q)
|
||||
|
||||
function hasAnswer(o : oracle_id,
|
||||
q : query_id) =
|
||||
switch(Oracle.get_answer(o, q))
|
||||
None => false
|
||||
Some(_) => true
|
||||
|
||||
function getAnswer(o : oracle_id,
|
||||
q : query_id) : option(answer_t) =
|
||||
Oracle.get_answer(o, q)
|
||||
|
||||
datatype complexQuestion = Why(int) | How(string)
|
||||
datatype complexAnswer = NoAnswer | Answer(complexQuestion, string, int)
|
||||
|
||||
function complexOracle(question) =
|
||||
let o = Oracle.register(Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
|
||||
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
|
||||
Oracle.respond(o, q, Answer(question, "magic", 1337))
|
||||
Oracle.get_answer(o, q)
|
||||
|
||||
function signedComplexOracle(question, sig) =
|
||||
let o = Oracle.register(signature = sig, Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
|
||||
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
|
||||
Oracle.respond(o, q, Answer(question, "magic", 1337), signature = sig)
|
||||
Oracle.get_answer(o, q)
|
||||
|
||||
private function require(b : bool, err : string) =
|
||||
if(!b) abort(err)
|
||||
@@ -0,0 +1,11 @@
|
||||
contract OraclesErr =
|
||||
|
||||
public function unsafeCreateQueryThenErr(
|
||||
o : oracle(string, int),
|
||||
q : string,
|
||||
qfee : int,
|
||||
qttl : Chain.ttl,
|
||||
rttl : Chain.ttl) : oracle_query(string, int) =
|
||||
let x = Oracle.query(o, q, qfee, qttl, rttl)
|
||||
switch(0) 1 => ()
|
||||
x // Never reached.
|
||||
@@ -0,0 +1,24 @@
|
||||
contract OraclesGas =
|
||||
|
||||
type fee = int
|
||||
type question_t = string
|
||||
type answer_t = int
|
||||
|
||||
public function happyPathWithAllBuiltinsAtSameHeight(
|
||||
qfee : fee,
|
||||
ottl : Chain.ttl,
|
||||
ettl : Chain.ttl,
|
||||
qttl : Chain.ttl,
|
||||
rttl : Chain.ttl
|
||||
) =
|
||||
let question = "why"
|
||||
let answer = 42
|
||||
let o = Oracle.register(Contract.address, qfee, ottl) : oracle(question_t, answer_t)
|
||||
Oracle.extend(o, ettl)
|
||||
require(qfee =< Call.value, "insufficient value for qfee")
|
||||
let q = Oracle.query(o, question, qfee, qttl, rttl)
|
||||
Oracle.respond(o, q, answer)
|
||||
()
|
||||
|
||||
private function require(b : bool, err : string) =
|
||||
if(!b) abort(err)
|
||||
@@ -0,0 +1,37 @@
|
||||
contract Oracles =
|
||||
|
||||
type fee = int
|
||||
type ttl = Chain.ttl
|
||||
|
||||
type query_t = string
|
||||
type answer_t = string
|
||||
|
||||
type oracle_id = oracle(query_t, answer_t)
|
||||
type query_id = oracle_query(query_t, answer_t)
|
||||
|
||||
function createQuery(o : oracle_id,
|
||||
q : query_t,
|
||||
qfee : fee,
|
||||
qttl : ttl,
|
||||
rttl : ttl) : query_id =
|
||||
require(qfee =< Call.value, "insufficient value for qfee")
|
||||
Oracle.query(o, q, qfee, qttl, rttl)
|
||||
|
||||
|
||||
function respond(o : oracle_id,
|
||||
q : query_id,
|
||||
sign : signature,
|
||||
r : answer_t) : () =
|
||||
Oracle.respond(o, q, signature = sign, r)
|
||||
|
||||
|
||||
function getQuestion(o : oracle_id,
|
||||
q : query_id) : query_t =
|
||||
Oracle.get_question(o, q)
|
||||
|
||||
function getAnswer(o : oracle_id,
|
||||
q : query_id) : option(answer_t) =
|
||||
Oracle.get_answer(o, q)
|
||||
|
||||
private function require(b : bool, err : string) =
|
||||
if(!b) abort(err)
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
contract Identity =
|
||||
|
||||
function zip_with(f, xs, ys) =
|
||||
switch((xs, ys))
|
||||
(x :: xs, y :: ys) => f(x, y) :: zip_with(f, xs, ys)
|
||||
_ => []
|
||||
|
||||
// Check that we can use zip_with at different types
|
||||
|
||||
function foo() =
|
||||
zip_with((x, y) => x + y, [1, 2, 3], [4, 5, 6, 7])
|
||||
|
||||
function bar() =
|
||||
zip_with((x, y) => if(x) y else 0, [true, false, true, false], [1, 2, 3])
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
|
||||
contract MapServer =
|
||||
|
||||
function insert : (string, string, map(string, string)) => map(string, string)
|
||||
function delete : (string, map(string, string)) => map(string, string)
|
||||
|
||||
contract PrimitiveMaps =
|
||||
|
||||
record state = { remote : MapServer,
|
||||
map : map(string, string),
|
||||
map2 : map(string, string) }
|
||||
|
||||
function init(r) =
|
||||
let m = {}
|
||||
{ remote = r, map = m, map2 = m }
|
||||
|
||||
function set_remote(r) = put(state{ remote = r })
|
||||
|
||||
function insert(k, v, m) : map(string, string) = m{ [k] = v }
|
||||
function delete(k, m) : map(string, string) = Map.delete(k, m)
|
||||
|
||||
function remote_insert(k, v, m) =
|
||||
state.remote.insert(k, v, m)
|
||||
|
||||
function remote_delete(k, m) =
|
||||
state.remote.delete(k, m)
|
||||
|
||||
function get_state_map() = state.map
|
||||
function set_state_map(m) = put(state{ map = m })
|
||||
|
||||
function clone_state() = put(state{ map2 = state.map })
|
||||
|
||||
function insert_state(k, v) = put(state{ map @ m = m { [k] = v } })
|
||||
function delete_state(k) = put(state{ map @ m = Map.delete(k, m) })
|
||||
function lookup_state(k) = Map.lookup(k, state.map)
|
||||
|
||||
function double_insert_state(k, v1, v2) =
|
||||
put(state{ map @ m = m { [k] = v1 },
|
||||
map2 @ m = m { [k] = v2 } })
|
||||
|
||||
function test() =
|
||||
let m = {} : map(string, string)
|
||||
let m1 = m { ["foo"] = "value_of_foo",
|
||||
["bla"] = "value_of_bla" }
|
||||
let m2 = Map.delete("foo", m1)
|
||||
let m3 = m2 { ["bla"] = "new_value_of_bla" }
|
||||
[Map.lookup("foo", m), Map.lookup("bla", m),
|
||||
Map.lookup("foo", m1), Map.lookup("bla", m1),
|
||||
Map.lookup("foo", m2), Map.lookup("bla", m2),
|
||||
Map.lookup("foo", m3), Map.lookup("bla", m3)]
|
||||
|
||||
function return_map() =
|
||||
Map.delete("goo", {["foo"] = "bar", ["goo"] = "gaa"})
|
||||
|
||||
function argument_map(m : map(string, string)) =
|
||||
m["foo"]
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
/* Primitive types */
|
||||
type address = string;
|
||||
type uint = int;
|
||||
exception Abort;
|
||||
|
||||
type env =
|
||||
{ mutable _caller : address
|
||||
};
|
||||
|
||||
let env = { _caller: "" };
|
||||
|
||||
/* Builtin functions */
|
||||
let caller() : address = env._caller;
|
||||
let abort() = raise(Abort);
|
||||
|
||||
let call(who, fn) = {
|
||||
env._caller = who;
|
||||
let res = try(fn()) { | e => env._caller = ""; raise(e) };
|
||||
env._caller = "";
|
||||
res
|
||||
};
|
||||
|
||||
/* Library functions */
|
||||
let require(x) = x ? () : abort();
|
||||
|
||||
/* -- Managing contract state ------------------------------------------------ */
|
||||
|
||||
type state_rep('s) = { mutable state : option('s) };
|
||||
|
||||
let newStateRep() : state_rep('s) = { state: None };
|
||||
|
||||
exception UninitializedState;
|
||||
exception ReinitializedState;
|
||||
|
||||
let getState(rep) =
|
||||
switch(rep.state) {
|
||||
| None => raise(UninitializedState)
|
||||
| Some(s) => s
|
||||
};
|
||||
|
||||
let setState(rep, s) =
|
||||
switch(rep.state) {
|
||||
| None => rep.state = Some(s)
|
||||
| Some(_) => raise(ReinitializedState)
|
||||
};
|
||||
|
||||
module type Contract = {
|
||||
type state;
|
||||
type args;
|
||||
let init : args => state;
|
||||
let stateRep : state_rep(state);
|
||||
};
|
||||
|
||||
module Setup = (C : Contract) => {
|
||||
let init(creator, args) =
|
||||
setState(C.stateRep, call(creator, () => C.init(args)));
|
||||
let reset() = C.stateRep.state = None;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
|
||||
open Rte;
|
||||
|
||||
/* Contract type */
|
||||
module type Voting = {
|
||||
type state;
|
||||
type args = list(string);
|
||||
let stateRep : state_rep(state);
|
||||
|
||||
let init : args => state;
|
||||
let giveRightToVote : address => unit;
|
||||
let delegate : address => unit;
|
||||
let vote : int => unit;
|
||||
let winnerName : unit => string;
|
||||
let currentTally : unit => list((string, int));
|
||||
};
|
||||
|
||||
/* Contract implementation */
|
||||
module Voting : Voting = {
|
||||
|
||||
/* Not so nice */
|
||||
module AddrKey = { type t = address; let compare = Pervasives.compare };
|
||||
module AddrMap = Map.Make(AddrKey);
|
||||
type addr_map('a) = AddrMap.t('a);
|
||||
|
||||
/* Types */
|
||||
|
||||
type proposal =
|
||||
{ name: string
|
||||
, mutable voteCount: uint
|
||||
};
|
||||
|
||||
type voter =
|
||||
{ mutable weight: int
|
||||
, mutable delegate: option(address)
|
||||
, mutable vote: option(int)
|
||||
};
|
||||
|
||||
type state =
|
||||
{ chairPerson: address
|
||||
, mutable voters: addr_map(voter)
|
||||
, proposals: list(proposal)
|
||||
};
|
||||
|
||||
/* Initialization */
|
||||
type args = list(string);
|
||||
let init(proposalNames: args): state =
|
||||
{ chairPerson: caller(),
|
||||
voters: AddrMap.empty,
|
||||
proposals: List.map((name) => {name: name, voteCount: 0}, proposalNames)
|
||||
};
|
||||
|
||||
/* Boilerplate */
|
||||
let stateRep = newStateRep();
|
||||
let state() = getState(stateRep);
|
||||
|
||||
let initVoter() = { weight: 1, delegate: None, vote: None };
|
||||
|
||||
let giveRightToVote(voter: address) = {
|
||||
require(caller() == state().chairPerson);
|
||||
require(!AddrMap.mem(voter, state().voters));
|
||||
state().voters = AddrMap.add(voter, initVoter(), state().voters);
|
||||
()
|
||||
};
|
||||
|
||||
let rec delegateChain(delegate: address) = {
|
||||
require(delegate != caller()); /* Delegation loop! */
|
||||
let voter = AddrMap.find(delegate, state().voters);
|
||||
switch(voter.delegate) {
|
||||
| None => delegate;
|
||||
| Some(d) => delegateChain(d)
|
||||
}
|
||||
};
|
||||
|
||||
let addVote(candidate, weight) = {
|
||||
let proposal = List.nth(state().proposals, candidate);
|
||||
proposal.voteCount = proposal.voteCount + weight;
|
||||
};
|
||||
|
||||
let delegateVote(delegateTo: address, weight: uint) = {
|
||||
let voter = AddrMap.find(delegateTo, state().voters);
|
||||
switch(voter.vote) {
|
||||
| Some(vote) => addVote(vote, weight)
|
||||
| None => voter.weight = voter.weight + weight
|
||||
}
|
||||
};
|
||||
|
||||
let delegate(delegateTo: address) = {
|
||||
require(delegateTo != caller());
|
||||
let voter = AddrMap.find(caller(), state().voters);
|
||||
require(voter.vote == None);
|
||||
let finalDelegate = delegateChain(delegateTo);
|
||||
voter.vote = Some(0); /* Hm... */
|
||||
voter.delegate = Some(finalDelegate);
|
||||
delegateVote(finalDelegate, voter.weight)
|
||||
};
|
||||
|
||||
let vote(candidate: uint) = {
|
||||
let voter = AddrMap.find(caller(), state().voters);
|
||||
require(voter.vote == None);
|
||||
voter.vote = Some(candidate);
|
||||
addVote(candidate, voter.weight);
|
||||
};
|
||||
|
||||
let rec winningProposal'(current, rest) =
|
||||
switch(rest) {
|
||||
| [] => current;
|
||||
| [p, ...ps] => winningProposal'(p.voteCount > current.voteCount ? p : current, ps)
|
||||
};
|
||||
|
||||
/* const */
|
||||
let winningProposal() : proposal = {
|
||||
switch(state().proposals) {
|
||||
| [] => abort()
|
||||
| [p, ...ps] => winningProposal'(p, ps)
|
||||
}
|
||||
};
|
||||
|
||||
/* const */
|
||||
let winnerName() = winningProposal().name;
|
||||
|
||||
/* const */
|
||||
let currentTally() =
|
||||
List.map((p) => (p.name, p.voteCount), state().proposals);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
open Rte;
|
||||
open Voting;
|
||||
|
||||
let creator = "0x123";
|
||||
let voter1 = "0x1001";
|
||||
let voter2 = "0x1002";
|
||||
let voter3 = "0x1003";
|
||||
let other = "0xffff";
|
||||
|
||||
module SetupVote = Setup(Voting);
|
||||
open Voting;
|
||||
|
||||
let print_tally() = {
|
||||
let tally = call(other, () => currentTally());
|
||||
List.map(((name, count)) => Printf.printf("%s: %d\n", name, count), tally);
|
||||
let winner = call(other, () => winnerName());
|
||||
Printf.printf("Winner: %s\n", winner);
|
||||
};
|
||||
|
||||
Printf.printf("Delegate before vote\n");
|
||||
SetupVote.init(creator, ["Cake", "Beer"]);
|
||||
call(creator, () => giveRightToVote(voter1));
|
||||
call(creator, () => giveRightToVote(voter2));
|
||||
call(creator, () => giveRightToVote(voter3));
|
||||
call(voter3, () => delegate(voter1));
|
||||
call(voter1, () => vote(1));
|
||||
call(voter2, () => vote(0));
|
||||
print_tally();
|
||||
|
||||
SetupVote.reset();
|
||||
|
||||
Printf.printf("Delegate after vote\n");
|
||||
SetupVote.init(creator, ["Cake", "Beer"]);
|
||||
call(creator, () => giveRightToVote(voter1));
|
||||
call(creator, () => giveRightToVote(voter2));
|
||||
call(creator, () => giveRightToVote(voter3));
|
||||
call(voter1, () => vote(1));
|
||||
call(voter3, () => delegate(voter1));
|
||||
call(voter2, () => vote(0));
|
||||
print_tally();
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
contract Remote1 =
|
||||
function main : (int) => int
|
||||
|
||||
contract Remote2 =
|
||||
function call : (Remote1, int) => int
|
||||
|
||||
contract Remote3 =
|
||||
function get : () => int
|
||||
function tick : () => ()
|
||||
|
||||
contract RemoteCall =
|
||||
|
||||
function call(r : Remote1, x : int) : int =
|
||||
r.main(gas = 10000, value = 10, x)
|
||||
|
||||
function staged_call(r1 : Remote1, r2 : Remote2, x : int) =
|
||||
r2.call(r1, x)
|
||||
|
||||
function increment(r3 : Remote3) =
|
||||
r3.tick()
|
||||
|
||||
function get(r3 : Remote3) =
|
||||
r3.get()
|
||||
|
||||
function plus(x, y) = x + y
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
contract Remote1 =
|
||||
function set : (int) => int
|
||||
|
||||
contract RemoteCall =
|
||||
record state = { i : int }
|
||||
|
||||
function init(x) = { i = x }
|
||||
|
||||
function set( x : int) : int =
|
||||
let old = state.i
|
||||
put(state{ i = x })
|
||||
old
|
||||
|
||||
function call(r : Remote1, x : int, g : int) : int =
|
||||
r.set(gas = g, value = 10, x)
|
||||
|
||||
function get() = state.i
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
contract Oracles =
|
||||
|
||||
function registerOracle :
|
||||
(address,
|
||||
int,
|
||||
Chain.ttl) => oracle(string, int)
|
||||
|
||||
function createQuery :
|
||||
(oracle(string, int),
|
||||
string,
|
||||
int,
|
||||
Chain.ttl,
|
||||
Chain.ttl) => oracle_query(string, int)
|
||||
|
||||
function unsafeCreateQuery :
|
||||
(oracle(string, int),
|
||||
string,
|
||||
int,
|
||||
Chain.ttl,
|
||||
Chain.ttl) => oracle_query(string, int)
|
||||
|
||||
function respond :
|
||||
(oracle(string, int),
|
||||
oracle_query(string, int),
|
||||
int) => ()
|
||||
|
||||
contract OraclesErr =
|
||||
|
||||
function unsafeCreateQueryThenErr :
|
||||
(oracle(string, int),
|
||||
string,
|
||||
int,
|
||||
Chain.ttl,
|
||||
Chain.ttl) => oracle_query(string, int)
|
||||
|
||||
contract RemoteOracles =
|
||||
|
||||
public function callRegisterOracle(
|
||||
r : Oracles,
|
||||
acct : address,
|
||||
qfee : int,
|
||||
ttl : Chain.ttl) : oracle(string, int) =
|
||||
r.registerOracle(acct, qfee, ttl)
|
||||
|
||||
public function callCreateQuery(
|
||||
r : Oracles,
|
||||
value : int,
|
||||
o : oracle(string, int),
|
||||
q : string,
|
||||
qfee : int,
|
||||
qttl : Chain.ttl,
|
||||
rttl : Chain.ttl) : oracle_query(string, int) =
|
||||
require(value =< Call.value, "insufficient value")
|
||||
r.createQuery(value = value, o, q, qfee, qttl, rttl)
|
||||
|
||||
// Do not use in production!
|
||||
public function callUnsafeCreateQuery(
|
||||
r : Oracles,
|
||||
value : int,
|
||||
o : oracle(string, int),
|
||||
q : string,
|
||||
qfee : int,
|
||||
qttl : Chain.ttl,
|
||||
rttl : Chain.ttl) : oracle_query(string, int) =
|
||||
r.unsafeCreateQuery(value = value, o, q, qfee, qttl, rttl)
|
||||
|
||||
// Do not use in production!
|
||||
public function callUnsafeCreateQueryThenErr(
|
||||
r : OraclesErr,
|
||||
value : int,
|
||||
o : oracle(string, int),
|
||||
q : string,
|
||||
qfee : int,
|
||||
qttl : Chain.ttl,
|
||||
rttl : Chain.ttl) : oracle_query(string, int) =
|
||||
r.unsafeCreateQueryThenErr(value = value, o, q, qfee, qttl, rttl)
|
||||
|
||||
// Do not use in production!
|
||||
public function callUnsafeCreateQueryAndThenErr(
|
||||
r : Oracles,
|
||||
value : int,
|
||||
o : oracle(string, int),
|
||||
q : string,
|
||||
qfee : int,
|
||||
qttl : Chain.ttl,
|
||||
rttl : Chain.ttl) : oracle_query(string, int) =
|
||||
let x = r.unsafeCreateQuery(value = value, o, q, qfee, qttl, rttl)
|
||||
switch(0) 1 => ()
|
||||
x // Never reached.
|
||||
|
||||
public function callRespond(
|
||||
r : Oracles,
|
||||
o : oracle(string, int),
|
||||
q : oracle_query(string, int),
|
||||
qr : int) =
|
||||
r.respond(o, q, qr)
|
||||
|
||||
private function require(b : bool, err : string) =
|
||||
if(!b) abort(err)
|
||||
@@ -0,0 +1,23 @@
|
||||
contract RemoteState =
|
||||
record rstate = { i : int, s : string, m : map(int, int) }
|
||||
|
||||
function look_at(s : rstate) = ()
|
||||
function return_s(big : bool) =
|
||||
let x = "short"
|
||||
let y = "______longer_string_at_least_32_bytes_long___________longer_string_at_least_32_bytes_long___________longer_string_at_least_32_bytes_long_____"
|
||||
if(big) y else x
|
||||
function return_m(big : bool) =
|
||||
let x = { [1] = 2 }
|
||||
let y = { [1] = 2, [3] = 4, [5] = 6 }
|
||||
if(big) y else x
|
||||
|
||||
function get(s : rstate) = s
|
||||
function get_i(s : rstate) = s.i
|
||||
function get_s(s : rstate) = s.s
|
||||
function get_m(s : rstate) = s.m
|
||||
|
||||
function fun_update_i(s : rstate, ni) = s{ i = ni }
|
||||
function fun_update_s(s : rstate, ns) = s{ s = ns }
|
||||
function fun_update_m(s : rstate, nm) = s{ m = nm }
|
||||
function fun_update_mk(s : rstate, k, v) = s{ m = s.m{[k] = v} }
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
contract Remote =
|
||||
function id : ('a) => 'a
|
||||
function missing : ('a) => 'a
|
||||
function wrong_type : (string) => string
|
||||
|
||||
contract Main =
|
||||
|
||||
function id(x : int) =
|
||||
x
|
||||
|
||||
function wrong_type(x : int) =
|
||||
x
|
||||
|
||||
function remote_id(r : Remote, x) =
|
||||
r.id(x)
|
||||
|
||||
function remote_missing(r : Remote, x) =
|
||||
r.missing(x)
|
||||
|
||||
function remote_wrong_type(r : Remote, x) =
|
||||
r.wrong_type(x)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
contract ValueOnErr =
|
||||
function err : () => int
|
||||
function ok : () => int
|
||||
|
||||
contract RemoteValueOnErr =
|
||||
|
||||
public function callErr(
|
||||
r : ValueOnErr,
|
||||
value : int) : int =
|
||||
r.err(value = value)
|
||||
|
||||
public function callErrLimitGas(
|
||||
r : ValueOnErr,
|
||||
value : int,
|
||||
gas : int) : int =
|
||||
r.err(value = value, gas = gas)
|
||||
|
||||
public function callOk(
|
||||
r : ValueOnErr,
|
||||
value : int) : int =
|
||||
r.ok(value = value)
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
contract Simple =
|
||||
type t = int => int
|
||||
@@ -0,0 +1,29 @@
|
||||
/* Example from Solidity by Example
|
||||
http://solidity.readthedocs.io/en/develop/introduction-to-smart-contracts.html
|
||||
|
||||
The Solidity code:
|
||||
|
||||
contract SimpleStorage {
|
||||
uint storedData
|
||||
|
||||
function set(uint x) {
|
||||
storedData = x
|
||||
}
|
||||
|
||||
function get() constant returns (uint) {
|
||||
return storedData
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
contract SimpleStorage =
|
||||
|
||||
type event = int
|
||||
record state = { data : int }
|
||||
|
||||
function init(value : int) : state = { data = value }
|
||||
|
||||
function get() : int = state.data
|
||||
|
||||
function set(value : int) =
|
||||
put(state{data = value})
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
contract SpendContract =
|
||||
function withdraw : (int) => int
|
||||
|
||||
contract SpendTest =
|
||||
|
||||
function spend(to, amount) =
|
||||
let total = Contract.balance
|
||||
Chain.spend(to, amount)
|
||||
total - amount
|
||||
|
||||
function withdraw(amount) : int =
|
||||
spend(Call.caller, amount)
|
||||
|
||||
function withdraw_from(account, amount) =
|
||||
account.withdraw(amount)
|
||||
withdraw(amount)
|
||||
|
||||
function spend_from(from, to, amount) =
|
||||
from.withdraw(amount)
|
||||
Chain.spend(to, amount)
|
||||
Chain.balance(to)
|
||||
|
||||
function get_balance() = Contract.balance
|
||||
function get_balance_of(a) = Chain.balance(a)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Testing more interesting state types
|
||||
contract Stack =
|
||||
|
||||
type stack('a) = list('a)
|
||||
|
||||
record state = { stack : stack(string),
|
||||
size : int }
|
||||
|
||||
function init(ss : list(string)) = { stack = ss, size = length(ss) }
|
||||
|
||||
private function length(xs) =
|
||||
switch(xs)
|
||||
[] => 0
|
||||
_ :: xs => length(xs) + 1
|
||||
|
||||
stateful function pop() : string =
|
||||
switch(state.stack)
|
||||
s :: ss =>
|
||||
put(state{ stack = ss, size = state.size - 1 })
|
||||
s
|
||||
|
||||
stateful function push(s) =
|
||||
put(state{ stack = s :: state.stack, size = state.size + 1 })
|
||||
state.size
|
||||
|
||||
function all() = state.stack
|
||||
|
||||
function size() = state.size
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
contract Remote =
|
||||
function look_at : (state) => ()
|
||||
function return_s : (bool) => string
|
||||
function return_m : (bool) => map(int, int)
|
||||
function get : (state) => state
|
||||
function get_i : (state) => int
|
||||
function get_s : (state) => string
|
||||
function get_m : (state) => map(int, int)
|
||||
|
||||
function fun_update_i : (state, int) => state
|
||||
function fun_update_s : (state, string) => state
|
||||
function fun_update_m : (state, map(int, int)) => state
|
||||
function fun_update_mk : (state, int, int) => state
|
||||
|
||||
contract StateHandling =
|
||||
record state = { i : int, s : string, m : map(int, int) }
|
||||
|
||||
function init(r : Remote, i : int) =
|
||||
let state0 = { i = 0, s = "undefined", m = {} }
|
||||
r.fun_update_i(state0, i)
|
||||
|
||||
function read() = state
|
||||
function read_i() = state.i
|
||||
function read_s() = state.s
|
||||
function read_m() = state.m
|
||||
|
||||
function update(new_state : state) = put(new_state)
|
||||
function update_i(new_i) = put(state{ i = new_i })
|
||||
function update_s(new_s) = put(state{ s = new_s })
|
||||
function update_m(new_m) = put(state{ m = new_m })
|
||||
|
||||
function pass_it(r : Remote) = r.look_at(state)
|
||||
function nop(r : Remote) = put(state{ i = state.i })
|
||||
function return_it_s(r : Remote, big : bool) =
|
||||
let x = r.return_s(big)
|
||||
String.length(x)
|
||||
function return_it_m(r : Remote, big : bool) =
|
||||
let x = r.return_m(big)
|
||||
Map.size(x)
|
||||
|
||||
function pass(r : Remote) = r.get(state)
|
||||
function pass_i(r : Remote) = r.get_i(state)
|
||||
function pass_s(r : Remote) = r.get_s(state)
|
||||
function pass_m(r : Remote) = r.get_m(state)
|
||||
|
||||
function pass_update_i(r : Remote, i) = r.fun_update_i(state, i)
|
||||
function pass_update_s(r : Remote, s) = r.fun_update_s(state, s)
|
||||
function pass_update_m(r : Remote, m) = r.fun_update_m(state, m)
|
||||
|
||||
function remote_update_i (r : Remote, i) = put(r.fun_update_i(state, i))
|
||||
function remote_update_s (r : Remote, s) = put(r.fun_update_s(state, s))
|
||||
function remote_update_m (r : Remote, m) = put(r.fun_update_m(state, m))
|
||||
function remote_update_mk(r : Remote, k, v) = put(r.fun_update_mk(state, k, v))
|
||||
|
||||
// remote called
|
||||
function look_at(s : state) = ()
|
||||
|
||||
function get(s : state) = s
|
||||
function get_i(s : state) = s.i
|
||||
function get_s(s : state) = s.s
|
||||
function get_m(s : state) = s.m
|
||||
|
||||
function fun_update_i(st, ni) = st{ i = ni }
|
||||
function fun_update_s(st, ns) = st{ s = ns }
|
||||
function fun_update_m(st, nm) = st{ m = nm }
|
||||
function fun_update_mk(st, k, v) = st{ m = st.m{[k] = v} }
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
contract Strings =
|
||||
function str_len(s) = String.length(s)
|
||||
function str_concat(s1, s2) = String.concat(s1, s2)
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
|
||||
contract Identity =
|
||||
// type xy = {x:int, y:int}
|
||||
// type xz = {x:int, z:int}
|
||||
// type yz = {y:int, z:int}
|
||||
record point = {x:int,y:int}
|
||||
record cp('a) = {color: string, p:'a}
|
||||
//type intpoint = point(int)
|
||||
// //if (x==42) 1 else (x*x)
|
||||
// }
|
||||
//let baz() = {age:3, name:(4:int)}
|
||||
//let foo(a,b,c) = c
|
||||
// let rec fac(n) = if((n:int)==0) 1 else (n*fac(n-1))
|
||||
// and main(x) = x::[x+1]
|
||||
// let lentr(l) = lent(0,l)
|
||||
// let rec len(l) =
|
||||
// switch(l) {
|
||||
// | [] => 0
|
||||
// | x::xs => 1+len(xs)
|
||||
// }
|
||||
// let lent(n,l) =
|
||||
// switch (l) {
|
||||
// | [] => n
|
||||
// | (x::xs) => lent(n+1,xs)
|
||||
// }
|
||||
// let rec app(a,b) =
|
||||
// switch(a) {
|
||||
// | [] => b
|
||||
// | (x::xs) => x::app(xs,b)
|
||||
// }
|
||||
// let rec revt(l,r) =
|
||||
// switch(l) {
|
||||
// | [] => r
|
||||
// | x::xs => revt(xs,x::r)
|
||||
// }
|
||||
// let rev(l) = revt(l,[])
|
||||
// let main(x:int) = {
|
||||
// switch(rev([1,2,3])) {
|
||||
// | h::_ => h
|
||||
// }
|
||||
// }
|
||||
//let fac(n:int) = {
|
||||
// if (n==0) 1 else (n*fac(n-1))
|
||||
//}
|
||||
//let main(x) = switch((12,34)) {
|
||||
//| (13,_) => x
|
||||
//| (_,a) => x+a
|
||||
// | y => y+1
|
||||
// }
|
||||
//let main(x) = ({y:0>1, x:x==0}:point(bool))
|
||||
//let main(x) = x
|
||||
//let main(x) = len(1::2::[])
|
||||
//let main(x) = ((x,x):list('a))
|
||||
// let main(x) = switch("a") {
|
||||
// | "b" => 0
|
||||
// | "a" => 1
|
||||
// | "c" => 2
|
||||
// }
|
||||
//let main(x) = x.color+1
|
||||
//let main(x) = switch(({x:x, y:x+1}:cp(int))) {
|
||||
// | {y:xx} => xx
|
||||
// }
|
||||
//let main(x) = {x:0, y:1, z:2}
|
||||
// let id(x) = x
|
||||
// let double(x) = x+x
|
||||
// let pair(x) = (1,2)
|
||||
// let unit(x) = ()
|
||||
// let tuples(x) = ((1,x),(2,3,4))
|
||||
// let singleton(x) = [x]
|
||||
// let rec seq(n) = if (n==0) [] else (app(seq(n-1),[n]))
|
||||
// let idString(s:string) = s
|
||||
// let pairString(s:string) = (s,s)
|
||||
// let revStrings(ss:list(string))=rev(ss)
|
||||
// let makePoint(x,y) = {x:x, y:y}
|
||||
// let getx(x) = x.x
|
||||
// let updatex(p,x) = p{x:x}
|
||||
// let quad(x) = {let y=x+x; let z=y+y; z;}
|
||||
// let noblock(x) = {x; x}
|
||||
// let unit(x) = ()
|
||||
// let foo(x) = switch (x) {
|
||||
// | y => y+1
|
||||
// }
|
||||
// let p(x) = {color:"blue", p:{x:x, y:x+1}}
|
||||
//let twice(f,x) = f(f(x))
|
||||
// let twice(f,x) = f(f(x))
|
||||
// let double(x) = x+x
|
||||
// let main(x) = twice((y=>y+y),x)
|
||||
// let rec map(f,xs) = switch(xs) {
|
||||
// | [] => []
|
||||
// | (x::ys) => f(x)::map(f,ys)
|
||||
// }
|
||||
// let id(x) = x
|
||||
// let main(xs) = map(double,xs)
|
||||
function z(f,x) = x
|
||||
private function s(n) = (f,x)=>f(n(f,x))
|
||||
private function add(m,n) = (f,x)=>m(f,n(f,x))
|
||||
function main(_) =
|
||||
let three=s(s(s(z)))
|
||||
add(three,three)
|
||||
(((i)=>i+1),0)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
contract Test =
|
||||
|
||||
record r = { x : map(string, string), y : int }
|
||||
record r' = { y : string }
|
||||
record r2 = { z : int, w : int }
|
||||
record r3 = { x : int, z : int }
|
||||
|
||||
function set_x(r : r, z) = r{ x["foo"] @ x = x + 1 }
|
||||
|
||||
function bla(m : map(string, int)) = { [0] = "bla", ["foo"] = "" }
|
||||
|
||||
function foo(r) = r { y = 0 }
|
||||
function bar() = { y = "foo", z = 0 }
|
||||
function baz() = { y = "foo", w = 0 }
|
||||
|
||||
function foo1() = zz
|
||||
|
||||
function test1() : string = { y = 0 }
|
||||
function test2(x : string) = x { y = 0 }
|
||||
function test3(x : string) = x { y @ y = y + 1 }
|
||||
function test4(x : string) : int = x.y
|
||||
|
||||
function test5(xs) =
|
||||
switch(xs)
|
||||
x :: x => x
|
||||
[] => 0
|
||||
|
||||
function case_pat(xs) =
|
||||
switch(xs)
|
||||
[] => 0
|
||||
x :: xs => "x"
|
||||
|
||||
function foo2(m : map(string, int)) = m{ [1] = "bla" }
|
||||
|
||||
function bad_if(x, y : int, w : int, z : string) =
|
||||
if(x) y
|
||||
elif(x) w
|
||||
else z
|
||||
|
||||
function type_error(r, x) =
|
||||
set_x(set_x(x, r), x)
|
||||
@@ -0,0 +1,6 @@
|
||||
contract UpfrontCharges =
|
||||
record state = { b : int } // For enabling retrieval of sender balance observed inside init.
|
||||
public function init() : state = { b = b() }
|
||||
public function initialSenderBalance() : int = state.b
|
||||
public function senderBalance() : int = b()
|
||||
private function b() = Chain.balance(Call.origin)
|
||||
@@ -0,0 +1,7 @@
|
||||
contract ValueOnErr =
|
||||
|
||||
public function err() : int =
|
||||
switch(0) 1 => 5
|
||||
|
||||
public function ok() : int =
|
||||
11
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
contract VariantTypes =
|
||||
|
||||
datatype state = Started(started_state) | Stopped
|
||||
|
||||
record started_state = {owner : address, balance : int, color : color}
|
||||
|
||||
datatype color = Red | Green | Blue | Grey(int)
|
||||
|
||||
function init() = Stopped
|
||||
|
||||
function require(b) = if(!b) abort("required")
|
||||
|
||||
function start(bal : int) =
|
||||
switch(state)
|
||||
Stopped => put(Started({owner = Call.caller, balance = bal, color = Grey(0)}))
|
||||
|
||||
function stop() =
|
||||
switch(state)
|
||||
Started(st) =>
|
||||
require(Call.caller == st.owner)
|
||||
put(Stopped)
|
||||
st.balance
|
||||
|
||||
function get_color() = switch(state) Started(st) => st.color
|
||||
function set_color(c) = switch(state) Started(st) => put(Started(st{color = c}))
|
||||
|
||||
function get_state() = state
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
|
||||
/* Contract type */
|
||||
contract VotingType =
|
||||
type state
|
||||
function init : list(string) => state
|
||||
|
||||
function giveRightToVote : address => unit
|
||||
function delegate : address => unit
|
||||
function vote : int => unit
|
||||
function winnerName : unit => string
|
||||
function currentTally : unit => list((string, int))
|
||||
|
||||
/* Contract implementation */
|
||||
contract Voting =
|
||||
|
||||
// Types
|
||||
record proposal =
|
||||
{ name : string
|
||||
, voteCount : uint
|
||||
}
|
||||
|
||||
datatype vote = NotVoted | Voted(int) | Delegated(address)
|
||||
|
||||
record voter =
|
||||
{ weight : int
|
||||
, vote : vote
|
||||
}
|
||||
|
||||
record state =
|
||||
{ chairPerson : address
|
||||
, voters : map(address, voter)
|
||||
, proposals : list(proposal)
|
||||
}
|
||||
|
||||
// Initialization
|
||||
function init(proposalNames: list(string)): state =
|
||||
{ chairPerson = caller(),
|
||||
voters = Map.empty,
|
||||
proposals = List.map((name) => {name = name, voteCount = 0}, proposalNames) }
|
||||
|
||||
function initVoter() = { weight = 1, vote = NotVoted}
|
||||
|
||||
function giveRightToVote(voter: address) =
|
||||
require(caller() == state.chairPerson)
|
||||
require(!Map.mem(voter, state.voters))
|
||||
put(state{ voters = Map.add(voter, initVoter(), state.voters) })
|
||||
|
||||
function delegateChain(delegate: address) =
|
||||
require(delegate != caller()) /* Delegation loop! */
|
||||
let voter = Map.find(delegate, state.voters)
|
||||
switch(voter.vote)
|
||||
Delegated(d) => delegateChain(d)
|
||||
_ => delegate
|
||||
|
||||
function addVote(candidate, weight) =
|
||||
let proposal = List.nth(state.proposals, candidate)
|
||||
proposal{ voteCount = proposal.voteCount + weight }
|
||||
|
||||
function delegateVote(delegateTo: address, weight: uint) =
|
||||
let voter = Map.find(delegateTo, state.voters)
|
||||
switch(voter.vote)
|
||||
Voted(vote) => addVote(vote, weight)
|
||||
Delegated(_) => abort("impossible") // impossible
|
||||
NotVoted => voter{ weight = voter.weight + weight }
|
||||
|
||||
function delegate(delegateTo: address) =
|
||||
require(delegateTo != caller())
|
||||
let voter = Map.find(caller(), state.voters)
|
||||
require(voter.vote == NotVoted)
|
||||
let finalDelegate = delegateChain(delegateTo)
|
||||
let voter' = voter{ vote = Delegated(finalDelegate) }
|
||||
delegateVote(finalDelegate, voter.weight)
|
||||
|
||||
function vote(candidate: uint) =
|
||||
let voter = Map.find(caller(), state.voters)
|
||||
require(voter.vote == NotVoted)
|
||||
let voter' = voter{ vote = Voted(candidate) }
|
||||
addVote(candidate, voter.weight)
|
||||
|
||||
function winningProposal'(current, rest) =
|
||||
switch(rest)
|
||||
[] => current
|
||||
p :: ps => winningProposal'(if (p.voteCount > current.voteCount) p else current, ps)
|
||||
|
||||
// const
|
||||
function winningProposal() : proposal =
|
||||
switch(state.proposals)
|
||||
[] => abort("none")
|
||||
p :: ps => winningProposal'(p, ps)
|
||||
|
||||
// const
|
||||
function winnerName() = winningProposal().name
|
||||
|
||||
// const
|
||||
function currentTally() =
|
||||
List.map((p) => (p.name, p.voteCount), state.proposals)
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/* Example from Solidity by Example
|
||||
http://solidity.readthedocs.io/en/develop/common-patterns.html
|
||||
|
||||
contract WithdrawalContract {
|
||||
address public richest
|
||||
uint public mostSent
|
||||
|
||||
mapping (address => uint) pendingWithdrawals
|
||||
|
||||
function WithdrawalContract() payable {
|
||||
richest = msg.sender
|
||||
mostSent = msg.value
|
||||
}
|
||||
|
||||
function becomeRichest() payable returns (bool) {
|
||||
if (msg.value > mostSent) {
|
||||
pendingWithdrawals[richest] += msg.value
|
||||
richest = msg.sender
|
||||
mostSent = msg.value
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function withdraw() {
|
||||
uint amount = pendingWithdrawals[msg.sender]
|
||||
// Remember to zero the pending refund before
|
||||
// sending to prevent re-entrancy attacks
|
||||
pendingWithdrawals[msg.sender] = 0
|
||||
msg.sender.transfer(amount)
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
contract WithdrawalContract =
|
||||
|
||||
record state = { richest : address,
|
||||
mostSent : uint,
|
||||
pendingWithdrawals : map(address, uint) }
|
||||
|
||||
function becomeRichest() : result(bool) =
|
||||
if (call().value > state.mostSent)
|
||||
let totalAmount : uint = Map.get_(state.richest, pendingWithdrawals) + call().value
|
||||
{state = state{ pendingWithdrawals = Map.insert(state.richest, call().value, state.pendingWithdrawals),
|
||||
richest = call().sender,
|
||||
mostSent = call().value },
|
||||
result = true}
|
||||
else
|
||||
{result = false}
|
||||
|
||||
function withdraw() =
|
||||
let amount : uint = Map.get_(call().sender, state.pendingWithdrawals)
|
||||
{ state.pendingWithdrawals = Map.insert(call().sender, 0, state.pendingWithdrawals),
|
||||
transactions = spend_tx(amount, call().sender) }
|
||||
Reference in New Issue
Block a user