Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dcae96ed21 | |||
| 94689dd0e9 | |||
| be7c0e1bd4 | |||
| 8eaafa736c | |||
| cf5a8aeb5f | |||
| 9e555a3121 | |||
| d051fa6c89 | |||
| d4238c0bdc | |||
| 389072fb12 | |||
| 1760593170 | |||
| d8dd6b900f | |||
| 74d4048d9f | |||
| 6bd2b7c483 | |||
| 5aed8b3ef5 | |||
| e1a798aef4 | |||
| 23cc8e1132 | |||
| 691ae72fbb | |||
| 8830095c7e | |||
| 251b876495 | |||
| f8ee8f7129 | |||
| 192ec207a7 | |||
| 0aa1c89556 | |||
| 71b97cba62 | |||
| 8a381e5ef1 | |||
| 386419f112 | |||
| 45a62f0807 | |||
| 3255c62e0e | |||
| 51b63f9559 | |||
| 5e6af18c7b | |||
| 4324bfd49e | |||
| faa0ef9772 | |||
| f07954f62c | |||
| ef761a4c57 | |||
| 330d8929fd | |||
| 491b1211d1 | |||
| e460b84bd0 | |||
| 9109712826 | |||
| d6a55e144e | |||
| db64978d2e | |||
| 2ed9d17ce5 | |||
| 7bf7cb0b8f | |||
| 4a01c852c9 | |||
| df00c3958b | |||
| 12cb37245b | |||
| 562ad5ee87 | |||
| 4e78756b90 | |||
| 9f32fb1925 | |||
| 549a0c2201 | |||
| 9f5f8d4444 | |||
| fd0dbdf207 | |||
| 0d8b7c7c79 | |||
| 3271d6fba4 | |||
| 30fbcc50c5 | |||
| 27bc5474cb | |||
| efeb391805 | |||
| 15ca37342c | |||
| 8b7e4db490 | |||
| d89fd134b5 | |||
| 37dfbf78ac | |||
| 188916c61f | |||
| f395649419 | |||
| 36395b597a | |||
| 177e32c117 | |||
| 4d61ee65df | |||
| a089af555f | |||
| cd116b23d7 | |||
| fba6609c3a | |||
| e7c477d4de | |||
| a44b787735 | |||
| b6a789bbbc | |||
| ad34363673 | |||
| 257de08100 | |||
| 7ae4a98360 | |||
| 5e6e607fa4 | |||
| a69056c35e | |||
| 8cfa611b20 | |||
| e9bdd59def | |||
| 0da7376d11 | |||
| af4f2ad795 | |||
| abae4a7602 | |||
| 1cfd4c6f24 | |||
| f07a49c91d | |||
| e33e4cf2cd | |||
| 85a014958d | |||
| fadf3378b4 | |||
| 54edba3164 | |||
| fc64ca572d | |||
| 53b85ce6f4 | |||
| a7af62c089 | |||
| abc70ba288 | |||
| 6342cd6a08 | |||
| 123d1d2fa2 | |||
| b7b54b38a8 | |||
| ae3f292f03 | |||
| bcdf311096 |
@@ -19,16 +19,16 @@ jobs:
|
|||||||
- dialyzer-cache-v2-
|
- dialyzer-cache-v2-
|
||||||
- run:
|
- run:
|
||||||
name: Build
|
name: Build
|
||||||
command: rebar3 compile
|
command: ./rebar3 compile
|
||||||
- run:
|
- run:
|
||||||
name: Static Analysis
|
name: Static Analysis
|
||||||
command: rebar3 dialyzer
|
command: ./rebar3 dialyzer
|
||||||
- run:
|
- run:
|
||||||
name: Eunit
|
name: Eunit
|
||||||
command: rebar3 eunit
|
command: ./rebar3 eunit
|
||||||
- run:
|
- run:
|
||||||
name: Common Tests
|
name: Common Tests
|
||||||
command: rebar3 ct
|
command: ./rebar3 ct
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: dialyzer-cache-v2-{{ .Branch }}-{{ .Revision }}
|
key: dialyzer-cache-v2-{{ .Branch }}-{{ .Revision }}
|
||||||
paths:
|
paths:
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
### Changed
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
## [3.0.0] - 2019-05-21
|
||||||
|
### Added
|
||||||
|
- `stateful` annotations are now properly enforced. Functions must be marked stateful
|
||||||
|
in order to update the state or spend tokens.
|
||||||
|
- Primitives `Contract.creator`, `Address.is_contract`, `Address.is_oracle`,
|
||||||
|
`Oracle.check` and `Oracle.check_query` has been added to Sophia.
|
||||||
|
- A byte array type `bytes(N)` has been added to generalize `hash (== bytes(32))` and
|
||||||
|
`signature (== bytes(64))` and allow for byte arrays of arbitrary fixed length.
|
||||||
|
- `Crypto.ecverify_secp256k1` has been added.
|
||||||
|
### Changed
|
||||||
|
- Address literals (+ Oracle, Oracle query and remote contracts) have been changed
|
||||||
|
from `#<hex>` to address as `ak_<base58check>`, oracle `ok_<base58check>`,
|
||||||
|
oracle query `oq_<base58check>` and remote contract `ct_<base58check>`.
|
||||||
|
- The compilation and typechecking of `letfun` (e.g. `let m(f, xs) = map(f, xs)`) was
|
||||||
|
not working properly and has been fixed.
|
||||||
|
### Removed
|
||||||
|
- `let rec` has been removed from the language, it has never worked.
|
||||||
|
- The standalone CLI compiler is served in the repo `aeternity/aesophia_cli` and has
|
||||||
|
been completely removed from `aesophia`.
|
||||||
|
|
||||||
|
## [2.1.0] - 2019-04-11
|
||||||
|
### Added
|
||||||
|
- Stubs (not yet wired up) for compilation to FATE
|
||||||
|
- Add functions specific for Calldata decoding
|
||||||
|
- Support for `Auth.tx_hash`, not available in AEVM until Fortuna release
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Improvements to the ACI generator
|
||||||
|
|
||||||
|
## [2.0.0] - 2019-03-11
|
||||||
|
### Added
|
||||||
|
- Add `Crypto.ecverify` to the compiler.
|
||||||
|
- Add `Crypto.sha3`, `Crypto.blake2`, `Crypto.sha256`, `String.blake2` and
|
||||||
|
`String.sha256` to the compiler.
|
||||||
|
- Add the `bits` type for working with bit fields in Sophia.
|
||||||
|
- Add Namespaces to Sophia in order to simplify using library contracts, etc.
|
||||||
|
- Add a missig type check on the `init` function - detects programmer errors earlier.
|
||||||
|
- Add the ACI (Aeternity Contract Interface) generator.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Use native bit shift operations in builtin functions, reducing gas cost.
|
||||||
|
- Improve type checking of `record` fields - generates more understandable error messages.
|
||||||
|
- Improved, more coherent, error messages.
|
||||||
|
- Simplify calldata creation - instead of passing a compiled contract, simply
|
||||||
|
pass a (stubbed) contract string.
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/aeternity/aesophia/compare/v3.0.0...HEAD
|
||||||
|
[3.0.0]: https://github.com/aeternity/aesophia/compare/v2.1.0...v3.0.0
|
||||||
|
[2.1.0]: https://github.com/aeternity/aesophia/compare/v2.0.0...v2.1.0
|
||||||
|
[2.0.0]: https://github.com/aeternity/aesophia/tag/v2.0.0
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# About this release
|
|
||||||
|
|
||||||
This is the `aesophia` compiler version 2.0.0. The main changes compared to version 1.2.0 are:
|
|
||||||
|
|
||||||
* Add `Crypto.ecverify` to the compiler.
|
|
||||||
* Add `Crypto.sha3`, `Crypto.blake2`, `Crypto.sha256`, `String.blake2` and
|
|
||||||
`String.sha256` to the compiler.
|
|
||||||
* Add the `bits` type for working with bit fields in Sophia.
|
|
||||||
* Use native bit shift operations in builtin functions, reducing gas cost.
|
|
||||||
* Add Namespaces to Sophia in order to simplify using library contracts, etc.
|
|
||||||
* Simplify calldata creation - instead of passing a compiled contract, simply
|
|
||||||
pass a (stubbed) contract string.
|
|
||||||
* Add a missig type check on the `init` function - detects programmer errors earlier.
|
|
||||||
* Improve type checking of `record` fields - generates more understandable error messages.
|
|
||||||
* Improved, more coherent, error messages.
|
|
||||||
* Add the ACI (Aeternity Contract Interface) generator.
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
|
|
||||||
-record(pmap, {key_t :: aeso_sophia:type(),
|
|
||||||
val_t :: aeso_sophia:type(),
|
|
||||||
parent :: none | non_neg_integer(),
|
|
||||||
size = 0 :: non_neg_integer(),
|
|
||||||
data :: #{aeso_heap:binary_value() => aeso_heap:binary_value() | tombstone}
|
|
||||||
| stored}).
|
|
||||||
|
|
||||||
-record(maps, { maps = #{} :: #{ non_neg_integer() => #pmap{} }
|
|
||||||
, next_id = 0 :: non_neg_integer() }).
|
|
||||||
|
|
||||||
-record(heap, { maps :: #maps{},
|
|
||||||
offset :: aeso_heap:offset(),
|
|
||||||
heap :: binary() | #{non_neg_integer() => non_neg_integer()} }).
|
|
||||||
|
|
||||||
+9
-16
@@ -3,29 +3,22 @@
|
|||||||
{erl_opts, [debug_info]}.
|
{erl_opts, [debug_info]}.
|
||||||
|
|
||||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
|
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
|
||||||
{ref,"720510a"}}}
|
{ref, "2f4e188"}}}
|
||||||
, {getopt, "1.0.1"}
|
, {getopt, "1.0.1"}
|
||||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
||||||
{tag, "2.8.0"}}}
|
{tag, "2.8.0"}}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{escript_incl_apps, [aesophia, aebytecode, getopt]}.
|
|
||||||
{escript_main_app, aesophia}.
|
|
||||||
{escript_name, aesophia}.
|
|
||||||
{escript_emu_args, "%%! +sbtu +A0\n"}.
|
|
||||||
{provider_hooks, [{post, [{compile, escriptize}]}]}.
|
|
||||||
|
|
||||||
{post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)",
|
|
||||||
escriptize,
|
|
||||||
"cp \"$REBAR_BUILD_DIR/bin/aesophia\" ./aesophia"},
|
|
||||||
{"win32",
|
|
||||||
escriptize,
|
|
||||||
"robocopy \"%REBAR_BUILD_DIR%/bin/\" ./ aesophia* "
|
|
||||||
"/njs /njh /nfl /ndl & exit /b 0"} % silence things
|
|
||||||
]}.
|
|
||||||
|
|
||||||
{dialyzer, [
|
{dialyzer, [
|
||||||
{warnings, [unknown]},
|
{warnings, [unknown]},
|
||||||
{plt_apps, all_deps},
|
{plt_apps, all_deps},
|
||||||
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
{relx, [{release, {aesophia, "3.0.0"},
|
||||||
|
[aesophia, aebytecode, getopt]},
|
||||||
|
|
||||||
|
{dev_mode, true},
|
||||||
|
{include_erts, false},
|
||||||
|
|
||||||
|
{extended_start_script, true}]}.
|
||||||
|
|||||||
+11
-1
@@ -1,8 +1,17 @@
|
|||||||
{"1.1.0",
|
{"1.1.0",
|
||||||
[{<<"aebytecode">>,
|
[{<<"aebytecode">>,
|
||||||
{git,"https://github.com/aeternity/aebytecode.git",
|
{git,"https://github.com/aeternity/aebytecode.git",
|
||||||
{ref,"720510a24de32c9bad6486f34ca7babde124bf1e"}},
|
{ref,"2f4e1888c241a7347ffec855ab6761c2c2972f37"}},
|
||||||
0},
|
0},
|
||||||
|
{<<"aeserialization">>,
|
||||||
|
{git,"https://github.com/aeternity/aeserialization.git",
|
||||||
|
{ref,"816bf994ffb5cee218c3f22dc5fea296c9e0882e"}},
|
||||||
|
1},
|
||||||
|
{<<"base58">>,
|
||||||
|
{git,"https://github.com/aeternity/erl-base58.git",
|
||||||
|
{ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}},
|
||||||
|
2},
|
||||||
|
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},1},
|
||||||
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
|
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
|
||||||
{<<"jsx">>,
|
{<<"jsx">>,
|
||||||
{git,"https://github.com/talentdeficit/jsx.git",
|
{git,"https://github.com/talentdeficit/jsx.git",
|
||||||
@@ -10,5 +19,6 @@
|
|||||||
0}]}.
|
0}]}.
|
||||||
[
|
[
|
||||||
{pkg_hash,[
|
{pkg_hash,[
|
||||||
|
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
|
||||||
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
|
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
|
||||||
].
|
].
|
||||||
|
|||||||
@@ -1,209 +0,0 @@
|
|||||||
%%%-------------------------------------------------------------------
|
|
||||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
|
||||||
%%% @doc
|
|
||||||
%%% Encode and decode data and function calls according to
|
|
||||||
%%% Sophia-AEVM-ABI.
|
|
||||||
%%% @end
|
|
||||||
%%% Created : 25 Jan 2018
|
|
||||||
%%%
|
|
||||||
%%%-------------------------------------------------------------------
|
|
||||||
-module(aeso_abi).
|
|
||||||
-define(HASH_SIZE, 32).
|
|
||||||
|
|
||||||
-export([ old_create_calldata/3
|
|
||||||
, create_calldata/4
|
|
||||||
, check_calldata/2
|
|
||||||
, function_type_info/3
|
|
||||||
, function_type_hash/3
|
|
||||||
, arg_typerep_from_function/2
|
|
||||||
, type_hash_from_function_name/2
|
|
||||||
, typereps_from_type_hash/2
|
|
||||||
, function_name_from_type_hash/2
|
|
||||||
, get_function_hash_from_calldata/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
-type hash() :: <<_:256>>. %% 256 = ?HASH_SIZE * 8.
|
|
||||||
-type function_name() :: binary(). %% String
|
|
||||||
-type typerep() :: aeso_sophia:type().
|
|
||||||
-type function_type_info() :: { FunctionHash :: hash()
|
|
||||||
, FunctionName :: function_name()
|
|
||||||
, ArgType :: binary() %% binary typerep
|
|
||||||
, OutType :: binary() %% binary typerep
|
|
||||||
}.
|
|
||||||
-type type_info() :: [function_type_info()].
|
|
||||||
|
|
||||||
%%%===================================================================
|
|
||||||
%%% API
|
|
||||||
%%%===================================================================
|
|
||||||
|
|
||||||
%%%===================================================================
|
|
||||||
%%% Handle calldata
|
|
||||||
|
|
||||||
create_calldata(FunName, Args, ArgTypes0, RetType) ->
|
|
||||||
ArgTypes = {tuple, ArgTypes0},
|
|
||||||
<<TypeHashInt:?HASH_SIZE/unit:8>> =
|
|
||||||
function_type_hash(list_to_binary(FunName), ArgTypes, RetType),
|
|
||||||
Data = aeso_heap:to_binary({TypeHashInt, list_to_tuple(Args)}),
|
|
||||||
{ok, Data, {tuple, [word, ArgTypes]}, RetType}.
|
|
||||||
|
|
||||||
get_type_info_and_hash(#{type_info := TypeInfo}, FunName) ->
|
|
||||||
FunBin = list_to_binary(FunName),
|
|
||||||
case type_hash_from_function_name(FunBin, TypeInfo) of
|
|
||||||
{ok, <<TypeHashInt:?HASH_SIZE/unit:8>>} -> {ok, TypeInfo, TypeHashInt};
|
|
||||||
{ok, _} -> {error, bad_type_hash};
|
|
||||||
{error, _} = Err -> Err
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec check_calldata(binary(), type_info()) ->
|
|
||||||
{'ok', typerep(), typerep()} | {'error', atom()}.
|
|
||||||
check_calldata(CallData, TypeInfo) ->
|
|
||||||
%% The first element of the CallData should be the function name
|
|
||||||
case get_function_hash_from_calldata(CallData) of
|
|
||||||
{ok, Hash} ->
|
|
||||||
case typereps_from_type_hash(Hash, TypeInfo) of
|
|
||||||
{ok, ArgType, OutType} ->
|
|
||||||
try aeso_heap:from_binary({tuple, [word, ArgType]}, CallData) of
|
|
||||||
{ok, _Something} ->
|
|
||||||
{ok, {tuple, [word, ArgType]}, OutType};
|
|
||||||
{error, _} ->
|
|
||||||
{error, bad_call_data}
|
|
||||||
catch
|
|
||||||
_T:_E ->
|
|
||||||
{error, bad_call_data}
|
|
||||||
end;
|
|
||||||
{error, _} ->
|
|
||||||
{error, unknown_function}
|
|
||||||
end;
|
|
||||||
{error, _What} ->
|
|
||||||
{error, bad_call_data}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec get_function_hash_from_calldata(CallData::binary()) ->
|
|
||||||
{ok, binary()} | {error, term()}.
|
|
||||||
get_function_hash_from_calldata(CallData) ->
|
|
||||||
case aeso_heap:from_binary({tuple, [word]}, CallData) of
|
|
||||||
{ok, {HashInt}} -> {ok, <<HashInt:?HASH_SIZE/unit:8>>};
|
|
||||||
{error, _} = Error -> Error
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%%===================================================================
|
|
||||||
%%% Handle type info from contract meta data
|
|
||||||
|
|
||||||
-spec function_type_info(function_name(), [typerep()], typerep()) ->
|
|
||||||
function_type_info().
|
|
||||||
function_type_info(Name, ArgTypes, OutType) ->
|
|
||||||
ArgType = {tuple, ArgTypes},
|
|
||||||
{ function_type_hash(Name, ArgType, OutType)
|
|
||||||
, Name
|
|
||||||
, aeso_heap:to_binary(ArgType)
|
|
||||||
, aeso_heap:to_binary(OutType)
|
|
||||||
}.
|
|
||||||
|
|
||||||
-spec function_type_hash(function_name(), typerep(), typerep()) -> hash().
|
|
||||||
function_type_hash(Name, ArgType, OutType) when is_binary(Name) ->
|
|
||||||
Bin = iolist_to_binary([ Name
|
|
||||||
, aeso_heap:to_binary(ArgType)
|
|
||||||
, aeso_heap:to_binary(OutType)
|
|
||||||
]),
|
|
||||||
%% Calculate a 256 bit digest BLAKE2b hash value of a binary
|
|
||||||
{ok, Hash} = aeso_blake2:blake2b(?HASH_SIZE, Bin),
|
|
||||||
Hash.
|
|
||||||
|
|
||||||
-spec arg_typerep_from_function(function_name(), type_info()) ->
|
|
||||||
{'ok', typerep()} | {'error', 'bad_type_data' | 'unknown_function'}.
|
|
||||||
arg_typerep_from_function(Function, TypeInfo) ->
|
|
||||||
case lists:keyfind(Function, 2, TypeInfo) of
|
|
||||||
{_TypeHash, Function, ArgTypeBin,_OutTypeBin} ->
|
|
||||||
case aeso_heap:from_binary(typerep, ArgTypeBin) of
|
|
||||||
{ok, ArgType} -> {ok, ArgType};
|
|
||||||
{error,_} -> {error, bad_type_data}
|
|
||||||
end;
|
|
||||||
false ->
|
|
||||||
{error, unknown_function}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec typereps_from_type_hash(hash(), type_info()) ->
|
|
||||||
{'ok', typerep(), typerep()} | {'error', 'bad_type_data' | 'unknown_function'}.
|
|
||||||
typereps_from_type_hash(TypeHash, TypeInfo) ->
|
|
||||||
case lists:keyfind(TypeHash, 1, TypeInfo) of
|
|
||||||
{TypeHash,_Function, ArgTypeBin, OutTypeBin} ->
|
|
||||||
case {aeso_heap:from_binary(typerep, ArgTypeBin),
|
|
||||||
aeso_heap:from_binary(typerep, OutTypeBin)} of
|
|
||||||
{{ok, ArgType}, {ok, OutType}} -> {ok, ArgType, OutType};
|
|
||||||
{_, _} -> {error, bad_type_data}
|
|
||||||
end;
|
|
||||||
false ->
|
|
||||||
{error, unknown_function}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec function_name_from_type_hash(hash(), type_info()) ->
|
|
||||||
{'ok', function_name()}
|
|
||||||
| {'error', 'unknown_function'}.
|
|
||||||
function_name_from_type_hash(TypeHash, TypeInfo) ->
|
|
||||||
case lists:keyfind(TypeHash, 1, TypeInfo) of
|
|
||||||
{TypeHash, Function,_ArgTypeBin,_OutTypeBin} ->
|
|
||||||
{ok, Function};
|
|
||||||
false ->
|
|
||||||
{error, unknown_function}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec type_hash_from_function_name(function_name(), type_info()) ->
|
|
||||||
{'ok', hash()}
|
|
||||||
| {'error', 'unknown_function'}.
|
|
||||||
type_hash_from_function_name(Name, TypeInfo) ->
|
|
||||||
case lists:keyfind(Name, 2, TypeInfo) of
|
|
||||||
{TypeHash, Name,_ArgTypeBin,_OutTypeBin} ->
|
|
||||||
{ok, TypeHash};
|
|
||||||
false ->
|
|
||||||
{error, unknown_function}
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% -- Old calldata creation. Kept for backwards compatibility. ---------------
|
|
||||||
|
|
||||||
old_create_calldata(Contract, Function, Argument) when is_map(Contract) ->
|
|
||||||
case aeso_constants:string(Argument) of
|
|
||||||
{ok, {tuple, _, _} = Tuple} ->
|
|
||||||
old_encode_call(Contract, Function, Tuple);
|
|
||||||
{ok, {unit, _} = Tuple} ->
|
|
||||||
old_encode_call(Contract, Function, Tuple);
|
|
||||||
{ok, ParsedArgument} ->
|
|
||||||
%% The Sophia compiler does not parse a singleton tuple (42) as a tuple,
|
|
||||||
%% Wrap it in a tuple.
|
|
||||||
old_encode_call(Contract, Function, {tuple, [], [ParsedArgument]});
|
|
||||||
{error, _} ->
|
|
||||||
{error, argument_syntax_error}
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% Call takes one arument.
|
|
||||||
%% Use a tuple to pass multiple arguments.
|
|
||||||
old_encode_call(Contract, Function, ArgumentAst) ->
|
|
||||||
Argument = old_ast_to_erlang(ArgumentAst),
|
|
||||||
case get_type_info_and_hash(Contract, Function) of
|
|
||||||
{ok, TypeInfo, TypeHashInt} ->
|
|
||||||
Data = aeso_heap:to_binary({TypeHashInt, Argument}),
|
|
||||||
case check_calldata(Data, TypeInfo) of
|
|
||||||
{ok, CallDataType, OutType} ->
|
|
||||||
{ok, Data, CallDataType, OutType};
|
|
||||||
{error, _} = Err ->
|
|
||||||
Err
|
|
||||||
end;
|
|
||||||
{error, _} = Err -> Err
|
|
||||||
end.
|
|
||||||
|
|
||||||
old_ast_to_erlang({int, _, N}) -> N;
|
|
||||||
old_ast_to_erlang({hash, _, <<N:?HASH_SIZE/unit:8>>}) -> N;
|
|
||||||
old_ast_to_erlang({hash, _, <<Hi:256, Lo:256>>}) -> {Hi, Lo}; %% signature
|
|
||||||
old_ast_to_erlang({bool, _, true}) -> 1;
|
|
||||||
old_ast_to_erlang({bool, _, false}) -> 0;
|
|
||||||
old_ast_to_erlang({string, _, Bin}) -> Bin;
|
|
||||||
old_ast_to_erlang({unit, _}) -> {};
|
|
||||||
old_ast_to_erlang({con, _, "None"}) -> none;
|
|
||||||
old_ast_to_erlang({app, _, {con, _, "Some"}, [A]}) -> {some, old_ast_to_erlang(A)};
|
|
||||||
old_ast_to_erlang({tuple, _, Elems}) ->
|
|
||||||
list_to_tuple(lists:map(fun old_ast_to_erlang/1, Elems));
|
|
||||||
old_ast_to_erlang({list, _, Elems}) ->
|
|
||||||
lists:map(fun old_ast_to_erlang/1, Elems);
|
|
||||||
old_ast_to_erlang({map, _, Elems}) ->
|
|
||||||
maps:from_list([ {old_ast_to_erlang(element(1, Elem)), old_ast_to_erlang(element(2, Elem))}
|
|
||||||
|| Elem <- Elems ]).
|
|
||||||
|
|
||||||
+168
-48
@@ -1,26 +1,30 @@
|
|||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
%%% @author Robert Virding
|
%%% @author Robert Virding
|
||||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||||
%%% @doc
|
%%% @doc
|
||||||
%%% ACI interface
|
%%% ACI interface
|
||||||
%%% @end
|
%%% @end
|
||||||
%%% Created : 12 Dec 2017
|
%%% Created : 12 Jan 2019
|
||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
|
|
||||||
-module(aeso_aci).
|
-module(aeso_aci).
|
||||||
|
|
||||||
-export([encode/1,encode/2,decode/1]).
|
-export([encode/1,encode/2,decode/1]).
|
||||||
|
-export([encode_type/1,encode_stmt/1,encode_expr/1]).
|
||||||
|
|
||||||
%% Define records for the various typed syntactic forms. These make
|
%% Define records for the various typed syntactic forms. These make
|
||||||
%% the code easier but don't seem to exist elsewhere.
|
%% the code easier but don't seem to exist elsewhere.
|
||||||
|
|
||||||
|
%% Top-level
|
||||||
-record(contract, {ann,con,decls}).
|
-record(contract, {ann,con,decls}).
|
||||||
-record(namespace, {ann,con,decls}).
|
%% -record(namespace, {ann,con,decls}).
|
||||||
-record(letfun, {ann,id,args,type,body}).
|
-record(letfun, {ann,id,args,type,body}).
|
||||||
-record(type_def, {ann,id,vars,typedef}).
|
-record(type_def, {ann,id,vars,typedef}).
|
||||||
|
|
||||||
|
%% Types
|
||||||
-record(app_t, {ann,id,fields}).
|
-record(app_t, {ann,id,fields}).
|
||||||
-record(tuple_t, {ann,args}).
|
-record(tuple_t, {ann,args}).
|
||||||
|
-record(bytes_t, {ann,len}).
|
||||||
-record(record_t, {fields}).
|
-record(record_t, {fields}).
|
||||||
-record(field_t, {ann,id,type}).
|
-record(field_t, {ann,id,type}).
|
||||||
-record(alias_t, {type}).
|
-record(alias_t, {type}).
|
||||||
@@ -28,6 +32,7 @@
|
|||||||
-record(constr_t, {ann,con,args}).
|
-record(constr_t, {ann,con,args}).
|
||||||
-record(fun_t, {ann,named,args,type}).
|
-record(fun_t, {ann,named,args,type}).
|
||||||
|
|
||||||
|
%% Tokens
|
||||||
-record(arg, {ann,id,type}).
|
-record(arg, {ann,id,type}).
|
||||||
-record(id, {ann,name}).
|
-record(id, {ann,name}).
|
||||||
-record(con, {ann,name}).
|
-record(con, {ann,name}).
|
||||||
@@ -35,6 +40,16 @@
|
|||||||
-record(qcon, {ann,names}).
|
-record(qcon, {ann,names}).
|
||||||
-record(tvar, {ann,name}).
|
-record(tvar, {ann,name}).
|
||||||
|
|
||||||
|
%% Expressions
|
||||||
|
-record(bool, {ann,bool}).
|
||||||
|
-record(int, {ann,value}).
|
||||||
|
-record(string, {ann,bin}).
|
||||||
|
-record(bytes, {ann,bin}).
|
||||||
|
-record(tuple, {ann,args}).
|
||||||
|
-record(list, {ann,args}).
|
||||||
|
-record(app, {ann,func,args}).
|
||||||
|
-record(typed, {ann,expr,type}).
|
||||||
|
|
||||||
%% encode(ContractString) -> {ok,JSON} | {error,String}.
|
%% encode(ContractString) -> {ok,JSON} | {error,String}.
|
||||||
%% encode(ContractString, Options) -> {ok,JSON} | {error,String}.
|
%% encode(ContractString, Options) -> {ok,JSON} | {error,String}.
|
||||||
%% Build a JSON structure with lists and tuples, not maps, as this
|
%% Build a JSON structure with lists and tuples, not maps, as this
|
||||||
@@ -47,7 +62,7 @@ encode(ContractString, Options) when is_binary(ContractString) ->
|
|||||||
encode(ContractString, Options) ->
|
encode(ContractString, Options) ->
|
||||||
try
|
try
|
||||||
Ast = parse(ContractString, Options),
|
Ast = parse(ContractString, Options),
|
||||||
%%io:format("~p\n", [Ast]),
|
%% io:format("~p\n", [Ast]),
|
||||||
%% aeso_ast:pp(Ast),
|
%% aeso_ast:pp(Ast),
|
||||||
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
|
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
|
||||||
%% io:format("~p\n", [TypedAst]),
|
%% io:format("~p\n", [TypedAst]),
|
||||||
@@ -59,7 +74,7 @@ encode(ContractString, Options) ->
|
|||||||
T <- sort_decls(contract_types(Contract)) ],
|
T <- sort_decls(contract_types(Contract)) ],
|
||||||
Fdefs = [ encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
|
Fdefs = [ encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
|
||||||
not is_private_func(F) ],
|
not is_private_func(F) ],
|
||||||
Jmap = [{<<"contract">>, [{<<"name">>, list_to_binary(Cname)},
|
Jmap = [{<<"contract">>, [{<<"name">>, encode_name(Cname)},
|
||||||
{<<"type_defs">>, Tdefs},
|
{<<"type_defs">>, Tdefs},
|
||||||
{<<"functions">>, Fdefs}]}],
|
{<<"functions">>, Fdefs}]}],
|
||||||
%% io:format("~p\n", [Jmap]),
|
%% io:format("~p\n", [Jmap]),
|
||||||
@@ -80,74 +95,137 @@ join_errors(Prefix, Errors, Pfun) ->
|
|||||||
Ess = [ Pfun(E) || E <- Errors ],
|
Ess = [ Pfun(E) || E <- Errors ],
|
||||||
list_to_binary(string:join([Prefix|Ess], "\n")).
|
list_to_binary(string:join([Prefix|Ess], "\n")).
|
||||||
|
|
||||||
|
%% encode_func(Function) -> JSON
|
||||||
|
%% Encode a function definition. Currently we are only interested in
|
||||||
|
%% the interface and type.
|
||||||
|
|
||||||
encode_func(Fdef) ->
|
encode_func(Fdef) ->
|
||||||
Name = function_name(Fdef),
|
Name = function_name(Fdef),
|
||||||
Args = function_args(Fdef),
|
Args = function_args(Fdef),
|
||||||
Type = function_type(Fdef),
|
Type = function_type(Fdef),
|
||||||
[{<<"name">>, list_to_binary(Name)},
|
[{<<"name">>, encode_name(Name)},
|
||||||
{<<"arguments">>, encode_args(Args)},
|
{<<"arguments">>, encode_args(Args)},
|
||||||
{<<"type">>, list_to_binary(encode_type(Type))},
|
{<<"returns">>, encode_type(Type)},
|
||||||
{<<"stateful">>, is_stateful_func(Fdef)}].
|
{<<"stateful">>, is_stateful_func(Fdef)}].
|
||||||
|
|
||||||
|
%% encode_args(Args) -> [JSON].
|
||||||
|
%% encode_arg(Args) -> JSON.
|
||||||
|
|
||||||
encode_args(Args) ->
|
encode_args(Args) ->
|
||||||
[ encode_arg(A) || A <- Args ].
|
[ encode_arg(A) || A <- Args ].
|
||||||
|
|
||||||
encode_arg(#arg{id=Id,type=T}) ->
|
encode_arg(#arg{id=Id,type=T}) ->
|
||||||
[{<<"name">>,list_to_binary(encode_type(Id))},
|
[{<<"name">>,encode_type(Id)},
|
||||||
{<<"type">>,list_to_binary(encode_type(T))}].
|
{<<"type">>,[encode_type(T)]}].
|
||||||
|
|
||||||
|
%% encode_types(Types) -> [JSON].
|
||||||
|
%% encode_type(Type) -> JSON.
|
||||||
|
|
||||||
encode_types(Types) ->
|
encode_types(Types) ->
|
||||||
[ encode_type(T) || T <- Types ].
|
[ encode_type(T) || T <- Types ].
|
||||||
|
|
||||||
encode_type(#tvar{name=N}) -> N;
|
encode_type(#tvar{name=N}) -> encode_name(N);
|
||||||
encode_type(#id{name=N}) -> N;
|
encode_type(#id{name=N}) -> encode_name(N);
|
||||||
encode_type(#con{name=N}) -> N;
|
encode_type(#con{name=N}) -> encode_name(N);
|
||||||
encode_type(#qid{names=Ns}) ->
|
encode_type(#qid{names=Ns}) ->
|
||||||
lists:join(".", Ns);
|
encode_name(lists:join(".", Ns));
|
||||||
encode_type(#qcon{names=Ns}) ->
|
encode_type(#qcon{names=Ns}) ->
|
||||||
lists:join(".", Ns); %?
|
encode_name(lists:join(".", Ns)); %?
|
||||||
encode_type(#tuple_t{args=As}) ->
|
encode_type(#tuple_t{args=As}) ->
|
||||||
Eas = encode_types(As),
|
Eas = encode_types(As),
|
||||||
[$(,lists:join(",", Eas),$)];
|
[{<<"tuple">>,Eas}];
|
||||||
|
encode_type(#bytes_t{len=Len}) ->
|
||||||
|
{<<"bytes">>, Len};
|
||||||
encode_type(#record_t{fields=Fs}) ->
|
encode_type(#record_t{fields=Fs}) ->
|
||||||
Efs = encode_types(Fs),
|
Efs = encode_fields(Fs),
|
||||||
[${,lists:join(",", Efs),$}];
|
[{<<"record">>,Efs}];
|
||||||
encode_type(#app_t{id=Id,fields=Fs}) ->
|
encode_type(#app_t{id=Id,fields=Fs}) ->
|
||||||
Name = encode_type(Id),
|
Name = encode_type(Id),
|
||||||
Efs = encode_types(Fs),
|
Efs = encode_types(Fs),
|
||||||
[Name,"(",lists:join(",", Efs),")"];
|
[{Name,Efs}];
|
||||||
encode_type(#field_t{id=Id,type=T}) ->
|
|
||||||
[encode_type(Id)," : ",encode_type(T)];
|
|
||||||
encode_type(#variant_t{cons=Cs}) ->
|
encode_type(#variant_t{cons=Cs}) ->
|
||||||
Ecs = encode_types(Cs),
|
Ecs = encode_types(Cs),
|
||||||
lists:join(" | ", Ecs);
|
[{<<"variant">>,Ecs}];
|
||||||
encode_type(#constr_t{con=C,args=As}) ->
|
encode_type(#constr_t{con=C,args=As}) ->
|
||||||
Ec = encode_type(C),
|
Ec = encode_type(C),
|
||||||
Eas = encode_types(As),
|
Eas = encode_types(As),
|
||||||
[Ec,$(,lists:join(", ", Eas),$)];
|
[{Ec,Eas}];
|
||||||
encode_type(#fun_t{args=As,type=T}) ->
|
encode_type(#fun_t{args=As,type=T}) ->
|
||||||
Eas = encode_types(As),
|
Eas = encode_types(As),
|
||||||
Et = encode_type(T),
|
Et = encode_type(T),
|
||||||
[$(,lists:join(", ", Eas),") => ",Et].
|
[{<<"function">>,[{<<"arguments">>,Eas},{<<"returns">>,Et}]}].
|
||||||
|
|
||||||
|
encode_name(Name) ->
|
||||||
|
list_to_binary(Name).
|
||||||
|
|
||||||
|
%% encode_fields(Fields) -> [JSON].
|
||||||
|
%% encode_field(Field) -> JSON.
|
||||||
|
%% Encode a record field.
|
||||||
|
|
||||||
|
encode_fields(Fs) ->
|
||||||
|
[ encode_field(F) || F <- Fs ].
|
||||||
|
|
||||||
|
encode_field(#field_t{id=Id,type=T}) ->
|
||||||
|
[{<<"name">>,encode_type(Id)},
|
||||||
|
{<<"type">>,[encode_type(T)]}].
|
||||||
|
|
||||||
|
%% encode_typedef(TypeDef) -> JSON.
|
||||||
|
|
||||||
encode_typedef(Type) ->
|
encode_typedef(Type) ->
|
||||||
Name = typedef_name(Type),
|
Name = typedef_name(Type),
|
||||||
Vars = typedef_vars(Type),
|
Vars = typedef_vars(Type),
|
||||||
Def = typedef_def(Type),
|
Def = typedef_def(Type),
|
||||||
[{<<"name">>, list_to_binary(Name)},
|
[{<<"name">>, encode_name(Name)},
|
||||||
{<<"vars">>, encode_tvars(Vars)},
|
{<<"vars">>, encode_tvars(Vars)},
|
||||||
{<<"typedef">>, list_to_binary(encode_alias(Def))}].
|
{<<"typedef">>, encode_alias(Def)}].
|
||||||
|
|
||||||
encode_tvars(Vars) ->
|
encode_tvars(Vars) ->
|
||||||
[ encode_tvar(V) || V <- Vars ].
|
[ encode_tvar(V) || V <- Vars ].
|
||||||
|
|
||||||
encode_tvar(#tvar{name=N}) ->
|
encode_tvar(#tvar{name=N}) ->
|
||||||
[{<<"name">>, list_to_binary(N)}].
|
[{<<"name">>, encode_name(N)}].
|
||||||
|
|
||||||
encode_alias(#alias_t{type=T}) ->
|
encode_alias(#alias_t{type=T}) ->
|
||||||
encode_type(T);
|
encode_type(T);
|
||||||
encode_alias(A) -> encode_type(A).
|
encode_alias(A) -> encode_type(A).
|
||||||
|
|
||||||
|
%% encode_stmt(Stmt) -> JSON.
|
||||||
|
|
||||||
|
encode_stmt(E) ->
|
||||||
|
encode_expr(E).
|
||||||
|
|
||||||
|
%% encode_exprs(Exprs) -> [JSON].
|
||||||
|
%% encode_expr(Expr) -> JSON.
|
||||||
|
|
||||||
|
encode_exprs(Es) ->
|
||||||
|
[ encode_expr(E) || E <- Es ].
|
||||||
|
|
||||||
|
encode_expr(#id{name=N}) -> encode_name(N);
|
||||||
|
encode_expr(#con{name=N}) -> encode_name(N);
|
||||||
|
encode_expr(#qid{names=Ns}) ->
|
||||||
|
encode_name(lists:join(".", Ns));
|
||||||
|
encode_expr(#qcon{names=Ns}) ->
|
||||||
|
encode_name(lists:join(".", Ns)); %?
|
||||||
|
encode_expr(#typed{expr=E}) ->
|
||||||
|
encode_expr(E);
|
||||||
|
encode_expr(#bool{bool=B}) -> B;
|
||||||
|
encode_expr(#int{value=V}) -> V;
|
||||||
|
encode_expr(#string{bin=B}) -> B;
|
||||||
|
encode_expr(#bytes{bin=B}) -> B;
|
||||||
|
encode_expr(#tuple{args=As}) ->
|
||||||
|
Eas = encode_exprs(As),
|
||||||
|
[{<<"tuple">>,Eas}];
|
||||||
|
encode_expr(#list{args=As}) ->
|
||||||
|
Eas = encode_exprs(As),
|
||||||
|
[{<<"list">>,Eas}];
|
||||||
|
encode_expr(#app{func=F,args=As}) ->
|
||||||
|
Ef = encode_expr(F),
|
||||||
|
Eas = encode_exprs(As),
|
||||||
|
[{<<"apply">>,[{<<"function">>,Ef},
|
||||||
|
{<<"arguments">>,Eas}]}];
|
||||||
|
encode_expr({Op,_Ann}) ->
|
||||||
|
list_to_binary(atom_to_list(Op)).
|
||||||
|
|
||||||
%% decode(JSON) -> ContractString.
|
%% decode(JSON) -> ContractString.
|
||||||
%% Decode a JSON string and generate a suitable contract string which
|
%% Decode a JSON string and generate a suitable contract string which
|
||||||
%% can be included in a contract definition. We decode into a map
|
%% can be included in a contract definition. We decode into a map
|
||||||
@@ -160,42 +238,81 @@ decode(Json) ->
|
|||||||
list_to_binary(decode_contract(C)).
|
list_to_binary(decode_contract(C)).
|
||||||
|
|
||||||
decode_contract(#{<<"name">> := Name,
|
decode_contract(#{<<"name">> := Name,
|
||||||
<<"type_defs">> := _Ts,
|
<<"type_defs">> := Ts,
|
||||||
<<"functions">> := Fs}) ->
|
<<"functions">> := Fs}) ->
|
||||||
["contract"," ",io_lib:format("~s", [Name])," =\n",
|
["contract"," ",io_lib:format("~s", [Name])," =\n",
|
||||||
[], %Don't include types yet.
|
decode_tdefs(Ts),
|
||||||
%% decode_tdefs(Ts),
|
|
||||||
decode_funcs(Fs)].
|
decode_funcs(Fs)].
|
||||||
|
|
||||||
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
|
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
|
||||||
|
|
||||||
decode_func(#{<<"name">> := <<"init">>}) -> [];
|
decode_func(#{<<"name">> := <<"init">>}) -> [];
|
||||||
decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"type">> := T}) ->
|
decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
|
||||||
[" function"," ",io_lib:format("~s", [Name])," : ",
|
[" function"," ",io_lib:format("~s", [Name])," : ",
|
||||||
decode_args(As)," => ",decode_type(T),$\n].
|
decode_args(As)," => ",decode_type(T),$\n].
|
||||||
|
|
||||||
decode_type(T) -> io_lib:format("~s", [T]).
|
|
||||||
|
|
||||||
decode_args(As) ->
|
decode_args(As) ->
|
||||||
Das = [ decode_arg(A) || A <- As ],
|
Das = [ decode_arg(A) || A <- As ],
|
||||||
[$(,lists:join(", ", Das),$)].
|
[$(,lists:join(", ", Das),$)].
|
||||||
|
|
||||||
decode_arg(#{<<"type">> := T}) -> decode_type(T).
|
decode_arg(#{<<"type">> := [T]}) -> decode_type(T).
|
||||||
|
|
||||||
|
decode_types(Ets) ->
|
||||||
|
[ decode_type(Et) || Et <- Ets ].
|
||||||
|
|
||||||
|
decode_type(#{<<"tuple">> := Ets}) ->
|
||||||
|
Ts = decode_types(Ets),
|
||||||
|
[$(,lists:join(",", Ts),$)];
|
||||||
|
decode_type(#{<<"record">> := Efs}) ->
|
||||||
|
Fs = decode_fields(Efs),
|
||||||
|
[${,lists:join(",", Fs),$}];
|
||||||
|
decode_type(#{<<"list">> := [Et]}) ->
|
||||||
|
T = decode_type(Et),
|
||||||
|
["list",$(,T,$)];
|
||||||
|
decode_type(#{<<"map">> := Ets}) ->
|
||||||
|
Ts = decode_types(Ets),
|
||||||
|
["map",$(,lists:join(",", Ts),$)];
|
||||||
|
decode_type(#{<<"variant">> := Ets}) ->
|
||||||
|
Ts = decode_types(Ets),
|
||||||
|
lists:join(" | ", Ts);
|
||||||
|
decode_type(Econs) when is_map(Econs) -> %General constructor
|
||||||
|
[{Ec,Ets}] = maps:to_list(Econs),
|
||||||
|
C = decode_name(Ec),
|
||||||
|
Ts = decode_types(Ets),
|
||||||
|
[C,$(,lists:join(",", Ts),$)];
|
||||||
|
decode_type(T) -> %Just raw names.
|
||||||
|
decode_name(T).
|
||||||
|
|
||||||
|
decode_name(En) ->
|
||||||
|
binary_to_list(En).
|
||||||
|
|
||||||
|
decode_fields(Efs) ->
|
||||||
|
[ decode_field(Ef) || Ef <- Efs ].
|
||||||
|
|
||||||
|
decode_field(#{<<"name">> := En,<<"type">> := [Et]}) ->
|
||||||
|
Name = decode_name(En),
|
||||||
|
Type = decode_type(Et),
|
||||||
|
[Name," : ",Type].
|
||||||
|
|
||||||
|
%% decode_tdefs(Json) -> [TypeString].
|
||||||
|
%% Here we are only interested in the type definitions and ignore the
|
||||||
|
%% aliases. We find them as they always have variants.
|
||||||
|
|
||||||
|
decode_tdefs(Ts) -> [ decode_tdef(T) ||
|
||||||
|
#{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts
|
||||||
|
].
|
||||||
|
|
||||||
|
decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
|
||||||
|
[" datatype"," ",decode_name(Name),decode_tvars(Vs),
|
||||||
|
" = ",decode_type(T),$\n].
|
||||||
|
|
||||||
|
decode_tvars([]) -> []; %No tvars, no parentheses
|
||||||
|
decode_tvars(Vs) ->
|
||||||
|
Dvs = [ decode_tvar(V) || V <- Vs ],
|
||||||
|
[$(,lists:join(", ", Dvs),$)].
|
||||||
|
|
||||||
|
decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
|
||||||
|
|
||||||
%% To keep dialyzer happy and quiet.
|
|
||||||
%% decode_tdefs(Ts) -> [ decode_tdef(T) || T <- Ts ].
|
|
||||||
%%
|
|
||||||
%% decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
|
|
||||||
%% [" type"," ",io_lib:format("~s", [Name]),decode_tvars(Vs),
|
|
||||||
%% " = ",decode_type(T),$\n].
|
|
||||||
%%
|
|
||||||
%% decode_tvars([]) -> []; %No tvars, no parentheses
|
|
||||||
%% decode_tvars(Vs) ->
|
|
||||||
%% Dvs = [ decode_tvar(V) || V <- Vs ],
|
|
||||||
%% [$(,lists:join(", ", Dvs),$)].
|
|
||||||
%%
|
|
||||||
%% decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
|
|
||||||
%%
|
|
||||||
%% #contract{Ann, Con, [Declarations]}.
|
%% #contract{Ann, Con, [Declarations]}.
|
||||||
|
|
||||||
contract_name(#contract{con=#con{name=N}}) -> N.
|
contract_name(#contract{con=#con{name=N}}) -> N.
|
||||||
@@ -242,6 +359,9 @@ typedef_vars(#type_def{vars=Vars}) -> Vars.
|
|||||||
|
|
||||||
typedef_def(#type_def{typedef=Def}) -> Def.
|
typedef_def(#type_def{typedef=Def}) -> Def.
|
||||||
|
|
||||||
|
%% parse(ContractString, Options) -> {ok,AST}.
|
||||||
|
%% Signal errors, the sophia compiler way. Sigh!
|
||||||
|
|
||||||
parse(Text, Options) ->
|
parse(Text, Options) ->
|
||||||
%% Try and return something sensible here!
|
%% Try and return something sensible here!
|
||||||
case aeso_parser:string(Text, Options) of
|
case aeso_parser:string(Text, Options) of
|
||||||
@@ -264,7 +384,7 @@ parse(Text, Options) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
parse_error(Pos, ErrorString) ->
|
parse_error(Pos, ErrorString) ->
|
||||||
io:format("Error ~p ~p\n", [Pos,ErrorString]),
|
%% io:format("Error ~p ~p\n", [Pos,ErrorString]),
|
||||||
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
|
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
|
||||||
error({parse_errors, [Error]}).
|
error({parse_errors, [Error]}).
|
||||||
|
|
||||||
|
|||||||
+188
-58
@@ -56,7 +56,12 @@
|
|||||||
, fields :: [aeso_syntax:id()]
|
, fields :: [aeso_syntax:id()]
|
||||||
, context :: why_record() }).
|
, context :: why_record() }).
|
||||||
|
|
||||||
-type field_constraint() :: #field_constraint{} | #record_create_constraint{}.
|
-record(is_contract_constraint,
|
||||||
|
{ contract_t :: utype(),
|
||||||
|
context :: aeso_syntax:expr() %% The address literal
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}.
|
||||||
|
|
||||||
-record(field_info,
|
-record(field_info,
|
||||||
{ ann :: aeso_syntax:ann()
|
{ ann :: aeso_syntax:ann()
|
||||||
@@ -98,6 +103,9 @@
|
|||||||
, typevars = unrestricted :: unrestricted | [name()]
|
, typevars = unrestricted :: unrestricted | [name()]
|
||||||
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
|
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
|
||||||
, namespace = [] :: qname()
|
, namespace = [] :: qname()
|
||||||
|
, in_pattern = false :: boolean()
|
||||||
|
, stateful = false :: boolean()
|
||||||
|
, current_function = none :: none | aeso_syntax:id()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type env() :: #env{}.
|
-type env() :: #env{}.
|
||||||
@@ -191,7 +199,7 @@ bind_state(Env) ->
|
|||||||
false -> {id, Ann, "event"} %% will cause type error if used(?)
|
false -> {id, Ann, "event"} %% will cause type error if used(?)
|
||||||
end,
|
end,
|
||||||
Env1 = bind_funs([{"state", State},
|
Env1 = bind_funs([{"state", State},
|
||||||
{"put", {fun_t, Ann, [], [State], Unit}}], Env),
|
{"put", {type_sig, [stateful | Ann], [], [State], Unit}}], Env),
|
||||||
|
|
||||||
%% We bind Chain.event in a local 'Chain' namespace.
|
%% We bind Chain.event in a local 'Chain' namespace.
|
||||||
pop_scope(
|
pop_scope(
|
||||||
@@ -341,6 +349,7 @@ global_env() ->
|
|||||||
Address = {id, Ann, "address"},
|
Address = {id, Ann, "address"},
|
||||||
Hash = {id, Ann, "hash"},
|
Hash = {id, Ann, "hash"},
|
||||||
Bits = {id, Ann, "bits"},
|
Bits = {id, Ann, "bits"},
|
||||||
|
Bytes = fun(Len) -> {bytes_t, Ann, Len} end,
|
||||||
Oracle = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle"}, [Q, R]} end,
|
Oracle = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle"}, [Q, R]} end,
|
||||||
Query = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle_query"}, [Q, R]} end,
|
Query = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle_query"}, [Q, R]} end,
|
||||||
Unit = {tuple_t, Ann, []},
|
Unit = {tuple_t, Ann, []},
|
||||||
@@ -350,11 +359,12 @@ global_env() ->
|
|||||||
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
|
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
|
||||||
Fun = fun(Ts, T) -> {type_sig, Ann, [], Ts, T} end,
|
Fun = fun(Ts, T) -> {type_sig, Ann, [], Ts, T} end,
|
||||||
Fun1 = fun(S, T) -> Fun([S], T) end,
|
Fun1 = fun(S, T) -> Fun([S], T) end,
|
||||||
|
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [], Ts, T} end,
|
||||||
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
|
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
|
||||||
SignId = {id, Ann, "signature"},
|
SignId = {id, Ann, "signature"},
|
||||||
SignDef = {tuple, Ann, [{int, Ann, 0}, {int, Ann, 0}]},
|
SignDef = {bytes, Ann, <<0:64/unit:8>>},
|
||||||
Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}},
|
Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}},
|
||||||
SignFun = fun(Ts, T) -> {type_sig, Ann, [Signature], Ts, T} end,
|
SignFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [Signature], Ts, T} end,
|
||||||
TTL = {qid, Ann, ["Chain", "ttl"]},
|
TTL = {qid, Ann, ["Chain", "ttl"]},
|
||||||
Fee = Int,
|
Fee = Int,
|
||||||
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
|
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
|
||||||
@@ -372,8 +382,10 @@ global_env() ->
|
|||||||
%% Abort
|
%% Abort
|
||||||
{"abort", Fun1(String, A)}])
|
{"abort", Fun1(String, A)}])
|
||||||
, types = MkDefs(
|
, types = MkDefs(
|
||||||
[{"int", 0}, {"bool", 0}, {"string", 0}, {"address", 0},
|
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
|
||||||
{"hash", 0}, {"signature", 0}, {"bits", 0},
|
{"hash", {[], {alias_t, Bytes(32)}}},
|
||||||
|
{"signature", {[], {alias_t, Bytes(64)}}},
|
||||||
|
{"bits", 0},
|
||||||
{"option", 1}, {"list", 1}, {"map", 2},
|
{"option", 1}, {"list", 1}, {"map", 2},
|
||||||
{"oracle", 2}, {"oracle_query", 2}
|
{"oracle", 2}, {"oracle_query", 2}
|
||||||
]) },
|
]) },
|
||||||
@@ -381,7 +393,7 @@ global_env() ->
|
|||||||
ChainScope = #scope
|
ChainScope = #scope
|
||||||
{ funs = MkDefs(
|
{ funs = MkDefs(
|
||||||
%% Spend transaction.
|
%% Spend transaction.
|
||||||
[{"spend", Fun([Address, Int], Unit)},
|
[{"spend", StateFun([Address, Int], Unit)},
|
||||||
%% Chain environment
|
%% Chain environment
|
||||||
{"balance", Fun1(Address, Int)},
|
{"balance", Fun1(Address, Int)},
|
||||||
{"block_hash", Fun1(Int, Int)},
|
{"block_hash", Fun1(Int, Int)},
|
||||||
@@ -395,7 +407,7 @@ global_env() ->
|
|||||||
ContractScope = #scope
|
ContractScope = #scope
|
||||||
{ funs = MkDefs(
|
{ funs = MkDefs(
|
||||||
[{"address", Address},
|
[{"address", Address},
|
||||||
%% {"owner", Int}, %% Not in EVM
|
{"creator", Address},
|
||||||
{"balance", Int}]) },
|
{"balance", Int}]) },
|
||||||
|
|
||||||
CallScope = #scope
|
CallScope = #scope
|
||||||
@@ -411,11 +423,13 @@ global_env() ->
|
|||||||
{ funs = MkDefs(
|
{ funs = MkDefs(
|
||||||
[{"register", SignFun([Address, Fee, TTL], Oracle(Q, R))},
|
[{"register", SignFun([Address, Fee, TTL], Oracle(Q, R))},
|
||||||
{"query_fee", Fun([Oracle(Q, R)], Fee)},
|
{"query_fee", Fun([Oracle(Q, R)], Fee)},
|
||||||
{"query", Fun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))},
|
{"query", StateFun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))},
|
||||||
{"get_question", Fun([Oracle(Q, R), Query(Q, R)], Q)},
|
{"get_question", Fun([Oracle(Q, R), Query(Q, R)], Q)},
|
||||||
{"respond", SignFun([Oracle(Q, R), Query(Q, R), R], Unit)},
|
{"respond", SignFun([Oracle(Q, R), Query(Q, R), R], Unit)},
|
||||||
{"extend", SignFun([Oracle(Q, R), TTL], Unit)},
|
{"extend", SignFun([Oracle(Q, R), TTL], Unit)},
|
||||||
{"get_answer", Fun([Oracle(Q, R), Query(Q, R)], option_t(Ann, R))}]) },
|
{"get_answer", Fun([Oracle(Q, R), Query(Q, R)], option_t(Ann, R))},
|
||||||
|
{"check", Fun([Oracle(Q, R)], Bool)},
|
||||||
|
{"check_query", Fun([Oracle(Q,R), Query(Q, R)], Bool)}]) },
|
||||||
|
|
||||||
AENSScope = #scope
|
AENSScope = #scope
|
||||||
{ funs = MkDefs(
|
{ funs = MkDefs(
|
||||||
@@ -439,10 +453,16 @@ global_env() ->
|
|||||||
CryptoScope = #scope
|
CryptoScope = #scope
|
||||||
{ funs = MkDefs(
|
{ funs = MkDefs(
|
||||||
[{"ecverify", Fun([Hash, Address, SignId], Bool)},
|
[{"ecverify", Fun([Hash, Address, SignId], Bool)},
|
||||||
|
{"ecverify_secp256k1", Fun([Hash, Bytes(64), Bytes(64)], Bool)},
|
||||||
{"sha3", Fun1(A, Hash)},
|
{"sha3", Fun1(A, Hash)},
|
||||||
{"sha256", Fun1(A, Hash)},
|
{"sha256", Fun1(A, Hash)},
|
||||||
{"blake2b", Fun1(A, Hash)}]) },
|
{"blake2b", Fun1(A, Hash)}]) },
|
||||||
|
|
||||||
|
%% Authentication
|
||||||
|
AuthScope = #scope
|
||||||
|
{ funs = MkDefs(
|
||||||
|
[{"tx_hash", Option(Hash)}]) },
|
||||||
|
|
||||||
%% Strings
|
%% Strings
|
||||||
StringScope = #scope
|
StringScope = #scope
|
||||||
{ funs = MkDefs(
|
{ funs = MkDefs(
|
||||||
@@ -467,7 +487,9 @@ global_env() ->
|
|||||||
|
|
||||||
%% Conversion
|
%% Conversion
|
||||||
IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) },
|
IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) },
|
||||||
AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}]) },
|
AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)},
|
||||||
|
{"is_oracle", Fun1(Address, Bool)},
|
||||||
|
{"is_contract", Fun1(Address, Bool)}]) },
|
||||||
|
|
||||||
#env{ scopes =
|
#env{ scopes =
|
||||||
#{ [] => TopScope
|
#{ [] => TopScope
|
||||||
@@ -477,6 +499,7 @@ global_env() ->
|
|||||||
, ["Oracle"] => OracleScope
|
, ["Oracle"] => OracleScope
|
||||||
, ["AENS"] => AENSScope
|
, ["AENS"] => AENSScope
|
||||||
, ["Map"] => MapScope
|
, ["Map"] => MapScope
|
||||||
|
, ["Auth"] => AuthScope
|
||||||
, ["Crypto"] => CryptoScope
|
, ["Crypto"] => CryptoScope
|
||||||
, ["String"] => StringScope
|
, ["String"] => StringScope
|
||||||
, ["Bits"] => BitsScope
|
, ["Bits"] => BitsScope
|
||||||
@@ -491,7 +514,7 @@ map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
|
|||||||
infer(Contracts) ->
|
infer(Contracts) ->
|
||||||
infer(Contracts, []).
|
infer(Contracts, []).
|
||||||
|
|
||||||
-type option() :: permissive_address_literals | return_env.
|
-type option() :: return_env.
|
||||||
|
|
||||||
-spec init_env(list(option())) -> env().
|
-spec init_env(list(option())) -> env().
|
||||||
init_env(_Options) -> global_env().
|
init_env(_Options) -> global_env().
|
||||||
@@ -582,6 +605,8 @@ infer_contract(Env, What, Defs) ->
|
|||||||
SCCs = aeso_utils:scc(DepGraph),
|
SCCs = aeso_utils:scc(DepGraph),
|
||||||
%% io:format("Dependency sorted functions:\n ~p\n", [SCCs]),
|
%% io:format("Dependency sorted functions:\n ~p\n", [SCCs]),
|
||||||
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
|
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
|
||||||
|
%% Check that `init` doesn't read or write the state
|
||||||
|
check_state_dependencies(Env4, Defs1),
|
||||||
destroy_and_report_type_errors(Env4),
|
destroy_and_report_type_errors(Env4),
|
||||||
{Env4, TypeDefs ++ Decls ++ Defs1}.
|
{Env4, TypeDefs ++ Decls ++ Defs1}.
|
||||||
|
|
||||||
@@ -669,6 +694,9 @@ check_type(Env, X = {Tag, _, _}, Arity) when Tag == con; Tag == qcon; Tag == id;
|
|||||||
check_type(Env, Type = {tuple_t, Ann, Types}, Arity) ->
|
check_type(Env, Type = {tuple_t, Ann, Types}, Arity) ->
|
||||||
ensure_base_type(Type, Arity),
|
ensure_base_type(Type, Arity),
|
||||||
{tuple_t, Ann, [ check_type(Env, T, 0) || T <- Types ]};
|
{tuple_t, Ann, [ check_type(Env, T, 0) || T <- Types ]};
|
||||||
|
check_type(_Env, Type = {bytes_t, _Ann, _Len}, Arity) ->
|
||||||
|
ensure_base_type(Type, Arity),
|
||||||
|
Type;
|
||||||
check_type(Env, {app_t, Ann, Type, Types}, Arity) ->
|
check_type(Env, {app_t, Ann, Type, Types}, Arity) ->
|
||||||
Types1 = [ check_type(Env, T, 0) || T <- Types ],
|
Types1 = [ check_type(Env, T, 0) || T <- Types ],
|
||||||
Type1 = check_type(Env, Type, Arity + length(Types)),
|
Type1 = check_type(Env, Type, Arity + length(Types)),
|
||||||
@@ -775,7 +803,7 @@ check_sccs(Env = #env{}, Funs, [{acyclic, X} | SCCs], Acc) ->
|
|||||||
end;
|
end;
|
||||||
check_sccs(Env = #env{}, Funs, [{cyclic, Xs} | SCCs], Acc) ->
|
check_sccs(Env = #env{}, Funs, [{cyclic, Xs} | SCCs], Acc) ->
|
||||||
Defs = [ maps:get(X, Funs) || X <- Xs ],
|
Defs = [ maps:get(X, Funs) || X <- Xs ],
|
||||||
{TypeSigs, {letrec, _, Defs1}} = infer_letrec(Env, {letrec, [], Defs}),
|
{TypeSigs, Defs1} = infer_letrec(Env, Defs),
|
||||||
Env1 = bind_funs(TypeSigs, Env),
|
Env1 = bind_funs(TypeSigs, Env),
|
||||||
check_sccs(Env1, Funs, SCCs, Defs1 ++ Acc).
|
check_sccs(Env1, Funs, SCCs, Defs1 ++ Acc).
|
||||||
|
|
||||||
@@ -817,7 +845,7 @@ check_special_funs(_, _) -> ok.
|
|||||||
|
|
||||||
typesig_to_fun_t({type_sig, Ann, Named, Args, Res}) -> {fun_t, Ann, Named, Args, Res}.
|
typesig_to_fun_t({type_sig, Ann, Named, Args, Res}) -> {fun_t, Ann, Named, Args, Res}.
|
||||||
|
|
||||||
infer_letrec(Env, {letrec, Attrs, Defs}) ->
|
infer_letrec(Env, Defs) ->
|
||||||
create_constraints(),
|
create_constraints(),
|
||||||
Funs = [{Name, fresh_uvar(A)}
|
Funs = [{Name, fresh_uvar(A)}
|
||||||
|| {letfun, _, {id, A, Name}, _, _, _} <- Defs],
|
|| {letfun, _, {id, A, Name}, _, _, _} <- Defs],
|
||||||
@@ -837,9 +865,12 @@ infer_letrec(Env, {letrec, Attrs, Defs}) ->
|
|||||||
TypeSigs = instantiate([Sig || {Sig, _} <- Inferred]),
|
TypeSigs = instantiate([Sig || {Sig, _} <- Inferred]),
|
||||||
NewDefs = instantiate([D || {_, D} <- Inferred]),
|
NewDefs = instantiate([D || {_, D} <- Inferred]),
|
||||||
[print_typesig(S) || S <- TypeSigs],
|
[print_typesig(S) || S <- TypeSigs],
|
||||||
{TypeSigs, {letrec, Attrs, NewDefs}}.
|
{TypeSigs, NewDefs}.
|
||||||
|
|
||||||
infer_letfun(Env, {letfun, Attrib, {id, NameAttrib, Name}, Args, What, Body}) ->
|
infer_letfun(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Body}) ->
|
||||||
|
Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false),
|
||||||
|
current_function = Fun },
|
||||||
|
check_unique_arg_names(Fun, Args),
|
||||||
ArgTypes = [{ArgName, check_type(Env, arg_type(T))} || {arg, _, ArgName, T} <- Args],
|
ArgTypes = [{ArgName, check_type(Env, arg_type(T))} || {arg, _, ArgName, T} <- Args],
|
||||||
ExpectedType = check_type(Env, arg_type(What)),
|
ExpectedType = check_type(Env, arg_type(What)),
|
||||||
NewBody={typed, _, _, ResultType} = check_expr(bind_vars(ArgTypes, Env), Body, ExpectedType),
|
NewBody={typed, _, _, ResultType} = check_expr(bind_vars(ArgTypes, Env), Body, ExpectedType),
|
||||||
@@ -850,6 +881,13 @@ infer_letfun(Env, {letfun, Attrib, {id, NameAttrib, Name}, Args, What, Body}) ->
|
|||||||
{{Name, TypeSig},
|
{{Name, TypeSig},
|
||||||
{letfun, Attrib, {id, NameAttrib, Name}, NewArgs, ResultType, NewBody}}.
|
{letfun, Attrib, {id, NameAttrib, Name}, NewArgs, ResultType, NewBody}}.
|
||||||
|
|
||||||
|
check_unique_arg_names(Fun, Args) ->
|
||||||
|
Name = fun({arg, _, {id, _, X}, _}) -> X end,
|
||||||
|
Names = lists:map(Name, Args),
|
||||||
|
Dups = lists:usort(Names -- lists:usort(Names)),
|
||||||
|
[ type_error({repeated_arg, Fun, Arg}) || Arg <- Dups ],
|
||||||
|
ok.
|
||||||
|
|
||||||
print_typesig({Name, TypeSig}) ->
|
print_typesig({Name, TypeSig}) ->
|
||||||
?PRINT_TYPES("Inferred ~s : ~s\n", [Name, pp(TypeSig)]).
|
?PRINT_TYPES("Inferred ~s : ~s\n", [Name, pp(TypeSig)]).
|
||||||
|
|
||||||
@@ -873,6 +911,7 @@ lookup_name(Env, As, Id, Options) ->
|
|||||||
{Id, fresh_uvar(As)};
|
{Id, fresh_uvar(As)};
|
||||||
{QId, {_, Ty}} ->
|
{QId, {_, Ty}} ->
|
||||||
Freshen = proplists:get_value(freshen, Options, false),
|
Freshen = proplists:get_value(freshen, Options, false),
|
||||||
|
check_stateful(Env, Id, Ty),
|
||||||
Ty1 = case Ty of
|
Ty1 = case Ty of
|
||||||
{type_sig, _, _, _, _} -> freshen_type(typesig_to_fun_t(Ty));
|
{type_sig, _, _, _, _} -> freshen_type(typesig_to_fun_t(Ty));
|
||||||
_ when Freshen -> freshen_type(Ty);
|
_ when Freshen -> freshen_type(Ty);
|
||||||
@@ -881,6 +920,61 @@ lookup_name(Env, As, Id, Options) ->
|
|||||||
{set_qname(QId, Id), Ty1}
|
{set_qname(QId, Id), Ty1}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
check_stateful(#env{ stateful = false, current_function = Fun }, Id, Type = {type_sig, _, _, _, _}) ->
|
||||||
|
case aeso_syntax:get_ann(stateful, Type, false) of
|
||||||
|
false -> ok;
|
||||||
|
true ->
|
||||||
|
type_error({stateful_not_allowed, Id, Fun})
|
||||||
|
end;
|
||||||
|
check_stateful(_Env, _Id, _Type) -> ok.
|
||||||
|
|
||||||
|
%% Hack: don't allow passing the 'value' named arg if not stateful. This only
|
||||||
|
%% works since the user can't create functions with named arguments.
|
||||||
|
check_stateful_named_arg(#env{ stateful = false, current_function = Fun }, {id, _, "value"}, Default) ->
|
||||||
|
case Default of
|
||||||
|
{int, _, 0} -> ok;
|
||||||
|
_ -> type_error({value_arg_not_allowed, Default, Fun})
|
||||||
|
end;
|
||||||
|
check_stateful_named_arg(_, _, _) -> ok.
|
||||||
|
|
||||||
|
%% Check that `init` doesn't read or write the state
|
||||||
|
check_state_dependencies(Env, Defs) ->
|
||||||
|
Top = Env#env.namespace,
|
||||||
|
GetState = Top ++ ["state"],
|
||||||
|
SetState = Top ++ ["put"],
|
||||||
|
Init = Top ++ ["init"],
|
||||||
|
UsedNames = fun(X) -> [{Xs, Ann} || {{term, Xs}, Ann} <- aeso_syntax_utils:used(X)] end,
|
||||||
|
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _Body} <- Defs ],
|
||||||
|
Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]),
|
||||||
|
case maps:get(Init, Deps, false) of
|
||||||
|
false -> ok; %% No init, so nothing to check
|
||||||
|
_ ->
|
||||||
|
[ type_error({init_depends_on_state, state, Chain})
|
||||||
|
|| Chain <- get_call_chains(Deps, Init, GetState) ],
|
||||||
|
[ type_error({init_depends_on_state, put, Chain})
|
||||||
|
|| Chain <- get_call_chains(Deps, Init, SetState) ],
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% Compute all paths (not sharing intermediate nodes) from Start to Stop in Graph.
|
||||||
|
get_call_chains(Graph, Start, Stop) ->
|
||||||
|
get_call_chains(Graph, #{}, queue:from_list([{Start, [], []}]), Stop, []).
|
||||||
|
|
||||||
|
get_call_chains(Graph, Visited, Queue, Stop, Acc) ->
|
||||||
|
case queue:out(Queue) of
|
||||||
|
{empty, _} -> lists:reverse(Acc);
|
||||||
|
{{value, {Stop, Ann, Path}}, Queue1} ->
|
||||||
|
get_call_chains(Graph, Visited, Queue1, Stop, [lists:reverse([{Stop, Ann} | Path]) | Acc]);
|
||||||
|
{{value, {Node, Ann, Path}}, Queue1} ->
|
||||||
|
case maps:is_key(Node, Visited) of
|
||||||
|
true -> get_call_chains(Graph, Visited, Queue1, Stop, Acc);
|
||||||
|
false ->
|
||||||
|
Calls = maps:get(Node, Graph, []),
|
||||||
|
NewQ = queue:from_list([{New, Ann1, [{Node, Ann} | Path]} || {New, Ann1} <- Calls]),
|
||||||
|
get_call_chains(Graph, Visited#{Node => true}, queue:join(Queue1, NewQ), Stop, Acc)
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
check_expr(Env, Expr, Type) ->
|
check_expr(Env, Expr, Type) ->
|
||||||
E = {typed, _, _, Type1} = infer_expr(Env, Expr),
|
E = {typed, _, _, Type1} = infer_expr(Env, Expr),
|
||||||
unify(Env, Type1, Type, {check_expr, Expr, Type1, Type}),
|
unify(Env, Type1, Type, {check_expr, Expr, Type1, Type}),
|
||||||
@@ -890,26 +984,35 @@ infer_expr(_Env, Body={bool, As, _}) ->
|
|||||||
{typed, As, Body, {id, As, "bool"}};
|
{typed, As, Body, {id, As, "bool"}};
|
||||||
infer_expr(_Env, Body={int, As, _}) ->
|
infer_expr(_Env, Body={int, As, _}) ->
|
||||||
{typed, As, Body, {id, As, "int"}};
|
{typed, As, Body, {id, As, "int"}};
|
||||||
|
infer_expr(_Env, Body={char, As, _}) ->
|
||||||
|
{typed, As, Body, {id, As, "char"}};
|
||||||
infer_expr(_Env, Body={string, As, _}) ->
|
infer_expr(_Env, Body={string, As, _}) ->
|
||||||
{typed, As, Body, {id, As, "string"}};
|
{typed, As, Body, {id, As, "string"}};
|
||||||
infer_expr(_Env, Body={hash, As, Hash}) ->
|
infer_expr(_Env, Body={bytes, As, Bin}) ->
|
||||||
case byte_size(Hash) of
|
{typed, As, Body, {bytes_t, As, byte_size(Bin)}};
|
||||||
32 -> {typed, As, Body, {id, As, "address"}};
|
infer_expr(_Env, Body={account_pubkey, As, _}) ->
|
||||||
64 -> {typed, As, Body, {id, As, "signature"}}
|
{typed, As, Body, {id, As, "address"}};
|
||||||
end;
|
infer_expr(_Env, Body={oracle_pubkey, As, _}) ->
|
||||||
|
Q = fresh_uvar(As),
|
||||||
|
R = fresh_uvar(As),
|
||||||
|
{typed, As, Body, {app_t, As, {id, As, "oracle"}, [Q, R]}};
|
||||||
|
infer_expr(_Env, Body={oracle_query_id, As, _}) ->
|
||||||
|
Q = fresh_uvar(As),
|
||||||
|
R = fresh_uvar(As),
|
||||||
|
{typed, As, Body, {app_t, As, {id, As, "oracle_query"}, [Q, R]}};
|
||||||
|
infer_expr(_Env, Body={contract_pubkey, As, _}) ->
|
||||||
|
Con = fresh_uvar(As),
|
||||||
|
constrain([#is_contract_constraint{ contract_t = Con,
|
||||||
|
context = Body }]),
|
||||||
|
{typed, As, Body, Con};
|
||||||
infer_expr(_Env, Body={id, As, "_"}) ->
|
infer_expr(_Env, Body={id, As, "_"}) ->
|
||||||
{typed, As, Body, fresh_uvar(As)};
|
{typed, As, Body, fresh_uvar(As)};
|
||||||
infer_expr(Env, Id = {id, As, _}) ->
|
infer_expr(Env, Id = {Tag, As, _}) when Tag == id; Tag == qid ->
|
||||||
{QName, Type} = lookup_name(Env, As, Id),
|
{QName, Type} = lookup_name(Env, As, Id),
|
||||||
{typed, As, QName, Type};
|
{typed, As, QName, Type};
|
||||||
infer_expr(Env, Id = {qid, As, _}) ->
|
infer_expr(Env, Id = {Tag, As, _}) when Tag == con; Tag == qcon ->
|
||||||
{QName, Type} = lookup_name(Env, As, Id),
|
|
||||||
{typed, As, QName, Type};
|
|
||||||
infer_expr(Env, Id = {con, As, _}) ->
|
|
||||||
{QName, Type} = lookup_name(Env, As, Id, [freshen]),
|
{QName, Type} = lookup_name(Env, As, Id, [freshen]),
|
||||||
{typed, As, QName, Type};
|
{typed, As, QName, Type};
|
||||||
infer_expr(Env, {unit, As}) ->
|
|
||||||
infer_expr(Env, {tuple, As, []});
|
|
||||||
infer_expr(Env, {tuple, As, Cpts}) ->
|
infer_expr(Env, {tuple, As, Cpts}) ->
|
||||||
NewCpts = [infer_expr(Env, C) || C <- Cpts],
|
NewCpts = [infer_expr(Env, C) || C <- Cpts],
|
||||||
CptTypes = [T || {typed, _, _, T} <- NewCpts],
|
CptTypes = [T || {typed, _, _, T} <- NewCpts],
|
||||||
@@ -963,7 +1066,7 @@ infer_expr(Env, {record, Attrs, Fields}) ->
|
|||||||
constrain([ #record_create_constraint{
|
constrain([ #record_create_constraint{
|
||||||
record_t = RecordType1,
|
record_t = RecordType1,
|
||||||
fields = [ FieldName || {field, _, [{proj, _, FieldName}], _} <- Fields ],
|
fields = [ FieldName || {field, _, [{proj, _, FieldName}], _} <- Fields ],
|
||||||
context = Attrs } ] ++
|
context = Attrs } || not Env#env.in_pattern ] ++
|
||||||
[begin
|
[begin
|
||||||
[{proj, _, FieldName}] = LV,
|
[{proj, _, FieldName}] = LV,
|
||||||
#field_constraint{
|
#field_constraint{
|
||||||
@@ -1032,6 +1135,7 @@ infer_expr(Env, {lam, Attrs, Args, Body}) ->
|
|||||||
|
|
||||||
infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) ->
|
infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) ->
|
||||||
CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E),
|
CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E),
|
||||||
|
check_stateful_named_arg(Env, Id, E),
|
||||||
add_named_argument_constraint(
|
add_named_argument_constraint(
|
||||||
#named_argument_constraint{
|
#named_argument_constraint{
|
||||||
args = NamedArgs,
|
args = NamedArgs,
|
||||||
@@ -1092,7 +1196,7 @@ infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
|
|||||||
[] -> ok;
|
[] -> ok;
|
||||||
Nonlinear -> type_error({non_linear_pattern, Pattern, lists:usort(Nonlinear)})
|
Nonlinear -> type_error({non_linear_pattern, Pattern, lists:usort(Nonlinear)})
|
||||||
end,
|
end,
|
||||||
NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env),
|
NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env#env{ in_pattern = true }),
|
||||||
NewPattern = {typed, _, _, PatType} = infer_expr(NewEnv, Pattern),
|
NewPattern = {typed, _, _, PatType} = infer_expr(NewEnv, Pattern),
|
||||||
NewBranch = check_expr(NewEnv, Branch, SwitchType),
|
NewBranch = check_expr(NewEnv, Branch, SwitchType),
|
||||||
unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}),
|
unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}),
|
||||||
@@ -1101,12 +1205,11 @@ infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
|
|||||||
%% NewStmts = infer_block(Env, Attrs, Stmts, BlockType)
|
%% NewStmts = infer_block(Env, Attrs, Stmts, BlockType)
|
||||||
infer_block(_Env, Attrs, [], BlockType) ->
|
infer_block(_Env, Attrs, [], BlockType) ->
|
||||||
error({impossible, empty_block, Attrs, BlockType});
|
error({impossible, empty_block, Attrs, BlockType});
|
||||||
infer_block(Env, Attrs, [Def={letfun, _, _, _, _, _}|Rest], BlockType) ->
|
infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) ->
|
||||||
NewDef = infer_letfun(Env, Def),
|
{{Name, TypeSig}, LetFun} = infer_letfun(Env, Def),
|
||||||
[NewDef|infer_block(Env, Attrs, Rest, BlockType)];
|
FunT = freshen_type(typesig_to_fun_t(TypeSig)),
|
||||||
infer_block(Env, Attrs, [Def={letrec, _, _}|Rest], BlockType) ->
|
NewE = bind_var({id, Ann, Name}, FunT, Env),
|
||||||
NewDef = infer_letrec(Env, Def),
|
[LetFun|infer_block(NewE, Attrs, Rest, BlockType)];
|
||||||
[NewDef|infer_block(Env, Attrs, Rest, BlockType)];
|
|
||||||
infer_block(Env, _, [{letval, Attrs, Pattern, Type, E}|Rest], BlockType) ->
|
infer_block(Env, _, [{letval, Attrs, Pattern, Type, E}|Rest], BlockType) ->
|
||||||
NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, Attrs, E, arg_type(Type)}),
|
NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, Attrs, E, arg_type(Type)}),
|
||||||
{'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} =
|
{'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} =
|
||||||
@@ -1151,6 +1254,8 @@ infer_prefix({IntOp,As}) when IntOp =:= '-' ->
|
|||||||
|
|
||||||
free_vars({int, _, _}) ->
|
free_vars({int, _, _}) ->
|
||||||
[];
|
[];
|
||||||
|
free_vars({char, _, _}) ->
|
||||||
|
[];
|
||||||
free_vars({string, _, _}) ->
|
free_vars({string, _, _}) ->
|
||||||
[];
|
[];
|
||||||
free_vars({bool, _, _}) ->
|
free_vars({bool, _, _}) ->
|
||||||
@@ -1342,6 +1447,16 @@ check_record_create_constraints(Env, [C | Cs]) ->
|
|||||||
end,
|
end,
|
||||||
check_record_create_constraints(Env, Cs).
|
check_record_create_constraints(Env, Cs).
|
||||||
|
|
||||||
|
check_is_contract_constraints(_Env, []) -> ok;
|
||||||
|
check_is_contract_constraints(Env, [C | Cs]) ->
|
||||||
|
#is_contract_constraint{ contract_t = Type, context = Lit } = C,
|
||||||
|
Type1 = unfold_types_in_type(Env, instantiate(Type)),
|
||||||
|
case lookup_type(Env, record_type_name(Type1)) of
|
||||||
|
{_, {_Ann, {[], {contract_t, _}}}} -> ok;
|
||||||
|
_ -> type_error({not_a_contract_type, Type1, Lit})
|
||||||
|
end,
|
||||||
|
check_is_contract_constraints(Env, Cs).
|
||||||
|
|
||||||
-spec solve_field_constraints(env(), [field_constraint()]) -> ok.
|
-spec solve_field_constraints(env(), [field_constraint()]) -> ok.
|
||||||
solve_field_constraints(Env, Constraints) ->
|
solve_field_constraints(Env, Constraints) ->
|
||||||
%% First look for record fields that appear in only one type definition
|
%% First look for record fields that appear in only one type definition
|
||||||
@@ -1440,9 +1555,12 @@ solve_known_record_types(Env, Constraints) ->
|
|||||||
DerefConstraints--SolvedConstraints.
|
DerefConstraints--SolvedConstraints.
|
||||||
|
|
||||||
destroy_and_report_unsolved_field_constraints(Env) ->
|
destroy_and_report_unsolved_field_constraints(Env) ->
|
||||||
{FieldCs, CreateCs} =
|
{FieldCs, OtherCs} =
|
||||||
lists:partition(fun(#field_constraint{}) -> true; (_) -> false end,
|
lists:partition(fun(#field_constraint{}) -> true; (_) -> false end,
|
||||||
get_field_constraints()),
|
get_field_constraints()),
|
||||||
|
{CreateCs, ContractCs} =
|
||||||
|
lists:partition(fun(#record_create_constraint{}) -> true; (_) -> false end,
|
||||||
|
OtherCs),
|
||||||
Unknown = solve_known_record_types(Env, FieldCs),
|
Unknown = solve_known_record_types(Env, FieldCs),
|
||||||
if Unknown == [] -> ok;
|
if Unknown == [] -> ok;
|
||||||
true ->
|
true ->
|
||||||
@@ -1452,6 +1570,7 @@ destroy_and_report_unsolved_field_constraints(Env) ->
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
check_record_create_constraints(Env, CreateCs),
|
check_record_create_constraints(Env, CreateCs),
|
||||||
|
check_is_contract_constraints(Env, ContractCs),
|
||||||
destroy_field_constraints(),
|
destroy_field_constraints(),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
@@ -1577,6 +1696,8 @@ unfold_types_in_type(Env, {field_t, Attr, Name, Type}, Options) ->
|
|||||||
{field_t, Attr, Name, unfold_types_in_type(Env, Type, Options)};
|
{field_t, Attr, Name, unfold_types_in_type(Env, Type, Options)};
|
||||||
unfold_types_in_type(Env, {constr_t, Ann, Con, Types}, Options) ->
|
unfold_types_in_type(Env, {constr_t, Ann, Con, Types}, Options) ->
|
||||||
{constr_t, Ann, Con, unfold_types_in_type(Env, Types, Options)};
|
{constr_t, Ann, Con, unfold_types_in_type(Env, Types, Options)};
|
||||||
|
unfold_types_in_type(Env, {named_arg_t, Ann, Con, Types, Default}, Options) ->
|
||||||
|
{named_arg_t, Ann, Con, unfold_types_in_type(Env, Types, Options), Default};
|
||||||
unfold_types_in_type(Env, T, Options) when is_tuple(T) ->
|
unfold_types_in_type(Env, T, Options) when is_tuple(T) ->
|
||||||
list_to_tuple(unfold_types_in_type(Env, tuple_to_list(T), Options));
|
list_to_tuple(unfold_types_in_type(Env, tuple_to_list(T), Options));
|
||||||
unfold_types_in_type(Env, [H|T], Options) ->
|
unfold_types_in_type(Env, [H|T], Options) ->
|
||||||
@@ -1632,6 +1753,8 @@ unify1(_Env, {qid, _, Name}, {qid, _, Name}, _When) ->
|
|||||||
true;
|
true;
|
||||||
unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _When) ->
|
unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _When) ->
|
||||||
true;
|
true;
|
||||||
|
unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) ->
|
||||||
|
true;
|
||||||
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) ->
|
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) ->
|
||||||
unify(Env, Named1, Named2, When) andalso
|
unify(Env, Named1, Named2, When) andalso
|
||||||
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
|
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
|
||||||
@@ -1649,26 +1772,8 @@ unify1(Env, {app_t, _, T, []}, B, When) ->
|
|||||||
unify1(Env, A, {app_t, _, T, []}, When) ->
|
unify1(Env, A, {app_t, _, T, []}, When) ->
|
||||||
unify(Env, A, T, When);
|
unify(Env, A, T, When);
|
||||||
unify1(_Env, A, B, When) ->
|
unify1(_Env, A, B, When) ->
|
||||||
Ok =
|
cannot_unify(A, B, When),
|
||||||
case get_option(permissive_address_literals, false) of
|
false.
|
||||||
true ->
|
|
||||||
Kind = fun({qcon, _, _}) -> con;
|
|
||||||
({con, _, _}) -> con;
|
|
||||||
({id, _, "address"}) -> addr;
|
|
||||||
({id, _, "hash"}) -> hash;
|
|
||||||
({app_t, _, {id, _, "oracle"}, _}) -> oracle;
|
|
||||||
({app_t, _, {id, _, "oracle_query"}, _}) -> query;
|
|
||||||
(_) -> other end,
|
|
||||||
%% If permissive_address_literals we allow unifying adresses
|
|
||||||
%% with contract types or oracles/oracle queries
|
|
||||||
case lists:usort([Kind(A), Kind(B)]) of
|
|
||||||
[addr, K] -> K /= other;
|
|
||||||
_ -> false
|
|
||||||
end;
|
|
||||||
false -> false
|
|
||||||
end,
|
|
||||||
[ cannot_unify(A, B, When) || not Ok ],
|
|
||||||
Ok.
|
|
||||||
|
|
||||||
dereference(T = {uvar, _, R}) ->
|
dereference(T = {uvar, _, R}) ->
|
||||||
case ets_lookup(type_vars, R) of
|
case ets_lookup(type_vars, R) of
|
||||||
@@ -1697,6 +1802,7 @@ occurs_check1(_, {con, _, _}) -> false;
|
|||||||
occurs_check1(_, {qid, _, _}) -> false;
|
occurs_check1(_, {qid, _, _}) -> false;
|
||||||
occurs_check1(_, {qcon, _, _}) -> false;
|
occurs_check1(_, {qcon, _, _}) -> false;
|
||||||
occurs_check1(_, {tvar, _, _}) -> false;
|
occurs_check1(_, {tvar, _, _}) -> false;
|
||||||
|
occurs_check1(_, {bytes_t, _, _}) -> false;
|
||||||
occurs_check1(R, {fun_t, _, Named, Args, Res}) ->
|
occurs_check1(R, {fun_t, _, Named, Args, Res}) ->
|
||||||
occurs_check(R, [Res, Named | Args]);
|
occurs_check(R, [Res, Named | Args]);
|
||||||
occurs_check1(R, {app_t, _, T, Ts}) ->
|
occurs_check1(R, {app_t, _, T, Ts}) ->
|
||||||
@@ -1779,7 +1885,7 @@ create_type_errors() ->
|
|||||||
ets_new(type_errors, [bag]).
|
ets_new(type_errors, [bag]).
|
||||||
|
|
||||||
destroy_and_report_type_errors(Env) ->
|
destroy_and_report_type_errors(Env) ->
|
||||||
Errors = ets_tab2list(type_errors),
|
Errors = lists:reverse(ets_tab2list(type_errors)),
|
||||||
%% io:format("Type errors now: ~p\n", [Errors]),
|
%% io:format("Type errors now: ~p\n", [Errors]),
|
||||||
PPErrors = [ pp_error(unqualify(Env, Err)) || Err <- Errors ],
|
PPErrors = [ pp_error(unqualify(Env, Err)) || Err <- Errors ],
|
||||||
ets_delete(type_errors),
|
ets_delete(type_errors),
|
||||||
@@ -1813,6 +1919,11 @@ pp_error({undefined_field, Id}) ->
|
|||||||
io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]);
|
io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]);
|
||||||
pp_error({not_a_record_type, Type, Why}) ->
|
pp_error({not_a_record_type, Type, Why}) ->
|
||||||
io_lib:format("~s\n~s\n", [pp_type("Not a record type: ", Type), pp_why_record(Why)]);
|
io_lib:format("~s\n~s\n", [pp_type("Not a record type: ", Type), pp_why_record(Why)]);
|
||||||
|
pp_error({not_a_contract_type, Type, Lit}) ->
|
||||||
|
io_lib:format("The type ~s is not a contract type\n"
|
||||||
|
"when checking that the contract literal at ~s\n~s\n"
|
||||||
|
"has the type\n~s\n",
|
||||||
|
[pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]);
|
||||||
pp_error({non_linear_pattern, Pattern, Nonlinear}) ->
|
pp_error({non_linear_pattern, Pattern, Nonlinear}) ->
|
||||||
Plural = [ $s || length(Nonlinear) > 1 ],
|
Plural = [ $s || length(Nonlinear) > 1 ],
|
||||||
io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n",
|
io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n",
|
||||||
@@ -1890,6 +2001,21 @@ pp_error({include, {string, Pos, Name}}) ->
|
|||||||
pp_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
|
pp_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
|
||||||
io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n",
|
io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n",
|
||||||
[Name, pp_loc(Pos)]);
|
[Name, pp_loc(Pos)]);
|
||||||
|
pp_error({repeated_arg, Fun, Arg}) ->
|
||||||
|
io_lib:format("Repeated argument ~s to function ~s (at ~s).\n",
|
||||||
|
[Arg, pp(Fun), pp_loc(Fun)]);
|
||||||
|
pp_error({stateful_not_allowed, Id, Fun}) ->
|
||||||
|
io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
|
||||||
|
[pp(Id), pp_loc(Id), pp(Fun)]);
|
||||||
|
pp_error({value_arg_not_allowed, Value, Fun}) ->
|
||||||
|
io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
|
||||||
|
[pp_expr("", Value), pp_loc(Value), pp(Fun)]);
|
||||||
|
pp_error({init_depends_on_state, Which, [_Init | Chain]}) ->
|
||||||
|
WhichCalls = fun("put") -> ""; ("state") -> ""; (_) -> ", which calls" end,
|
||||||
|
io_lib:format("The init function should return the initial state as its result and cannot ~s the state,\nbut it calls\n~s",
|
||||||
|
[if Which == put -> "write"; true -> "read" end,
|
||||||
|
[ io_lib:format(" - ~s (at ~s)~s\n", [Fun, pp_loc(Ann), WhichCalls(Fun)])
|
||||||
|
|| {[_, Fun], Ann} <- Chain]]);
|
||||||
pp_error(Err) ->
|
pp_error(Err) ->
|
||||||
io_lib:format("Unknown error: ~p\n", [Err]).
|
io_lib:format("Unknown error: ~p\n", [Err]).
|
||||||
|
|
||||||
@@ -2037,6 +2163,8 @@ pp({qid, _, Name}) ->
|
|||||||
string:join(Name, ".");
|
string:join(Name, ".");
|
||||||
pp({con, _, Name}) ->
|
pp({con, _, Name}) ->
|
||||||
Name;
|
Name;
|
||||||
|
pp({qcon, _, Name}) ->
|
||||||
|
string:join(Name, ".");
|
||||||
pp({uvar, _, Ref}) ->
|
pp({uvar, _, Ref}) ->
|
||||||
%% Show some unique representation
|
%% Show some unique representation
|
||||||
["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ];
|
["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ];
|
||||||
@@ -2044,6 +2172,8 @@ pp({tvar, _, Name}) ->
|
|||||||
Name;
|
Name;
|
||||||
pp({tuple_t, _, Cpts}) ->
|
pp({tuple_t, _, Cpts}) ->
|
||||||
["(", pp(Cpts), ")"];
|
["(", pp(Cpts), ")"];
|
||||||
|
pp({bytes_t, _, Len}) ->
|
||||||
|
["bytes(", integer_to_list(Len), ")"];
|
||||||
pp({app_t, _, T, []}) ->
|
pp({app_t, _, T, []}) ->
|
||||||
pp(T);
|
pp(T);
|
||||||
pp({app_t, _, Type, Args}) ->
|
pp({app_t, _, Type, Args}) ->
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+66
-23
@@ -102,13 +102,6 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest]
|
|||||||
QName = aeso_icode:qualify(Name, Icode),
|
QName = aeso_icode:qualify(Name, Icode),
|
||||||
NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode),
|
NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode),
|
||||||
contract_to_icode(Rest, NewIcode);
|
contract_to_icode(Rest, NewIcode);
|
||||||
contract_to_icode([{letrec,_,Defs}|Rest], Icode) ->
|
|
||||||
%% OBS! This code ignores the letrec structure of the source,
|
|
||||||
%% because the back end treats ALL declarations as recursive! We
|
|
||||||
%% need to decide whether to (a) modify the back end to respect
|
|
||||||
%% the letrec structure, or (b) (preferably) modify the front end
|
|
||||||
%% just to parse a list of (mutually recursive) definitions.
|
|
||||||
contract_to_icode(Defs++Rest, Icode);
|
|
||||||
contract_to_icode([], Icode) -> Icode;
|
contract_to_icode([], Icode) -> Icode;
|
||||||
contract_to_icode([{fun_decl, _, _, _} | Code], Icode) ->
|
contract_to_icode([{fun_decl, _, _, _} | Code], Icode) ->
|
||||||
contract_to_icode(Code, Icode);
|
contract_to_icode(Code, Icode);
|
||||||
@@ -152,6 +145,7 @@ ast_body(?qid_app(["Chain", "block_hash"], [Height], _, _), Icode) ->
|
|||||||
ast_body(?qid_app(["Call", "gas_left"], [], _, _), _Icode) ->
|
ast_body(?qid_app(["Call", "gas_left"], [], _, _), _Icode) ->
|
||||||
prim_gas_left;
|
prim_gas_left;
|
||||||
ast_body({qid, _, ["Contract", "address"]}, _Icode) -> prim_contract_address;
|
ast_body({qid, _, ["Contract", "address"]}, _Icode) -> prim_contract_address;
|
||||||
|
ast_body({qid, _, ["Contract", "creator"]}, _Icode) -> prim_contract_creator;
|
||||||
ast_body({qid, _, ["Contract", "balance"]}, _Icode) -> #prim_balance{ address = prim_contract_address };
|
ast_body({qid, _, ["Contract", "balance"]}, _Icode) -> #prim_balance{ address = prim_contract_address };
|
||||||
ast_body({qid, _, ["Call", "origin"]}, _Icode) -> prim_call_origin;
|
ast_body({qid, _, ["Call", "origin"]}, _Icode) -> prim_call_origin;
|
||||||
ast_body({qid, _, ["Call", "caller"]}, _Icode) -> prim_caller;
|
ast_body({qid, _, ["Call", "caller"]}, _Icode) -> prim_caller;
|
||||||
@@ -182,6 +176,11 @@ ast_body(?id_app("abort", [String], _, _), Icode) ->
|
|||||||
#funcall{ function = #var_ref{ name = {builtin, abort} },
|
#funcall{ function = #var_ref{ name = {builtin, abort} },
|
||||||
args = [ast_body(String, Icode)] };
|
args = [ast_body(String, Icode)] };
|
||||||
|
|
||||||
|
%% Authentication
|
||||||
|
ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) ->
|
||||||
|
prim_call(?PRIM_CALL_AUTH_TX_HASH, #integer{value = 0},
|
||||||
|
[], [], aeso_icode:option_typerep(word));
|
||||||
|
|
||||||
%% Oracles
|
%% Oracles
|
||||||
ast_body(?qid_app(["Oracle", "register"], Args, _, ?oracle_t(QType, RType)), Icode) ->
|
ast_body(?qid_app(["Oracle", "register"], Args, _, ?oracle_t(QType, RType)), Icode) ->
|
||||||
{Sign, [Acct, QFee, TTL]} = get_signature_arg(Args),
|
{Sign, [Acct, QFee, TTL]} = get_signature_arg(Args),
|
||||||
@@ -219,6 +218,17 @@ ast_body(?qid_app(["Oracle", "get_answer"], [Oracle, Q], [_, ?query_t(_, RType)]
|
|||||||
prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0},
|
prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0},
|
||||||
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode)));
|
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode)));
|
||||||
|
|
||||||
|
ast_body(?qid_app(["Oracle", "check"], [Oracle], [?oracle_t(Q, R)], _), Icode) ->
|
||||||
|
prim_call(?PRIM_CALL_ORACLE_CHECK, #integer{value = 0},
|
||||||
|
[ast_body(Oracle, Icode), ast_type_value(Q, Icode), ast_type_value(R, Icode)],
|
||||||
|
[word, typerep, typerep], word);
|
||||||
|
|
||||||
|
ast_body(?qid_app(["Oracle", "check_query"], [Oracle, Query], [_, ?query_t(Q, R)], _), Icode) ->
|
||||||
|
prim_call(?PRIM_CALL_ORACLE_CHECK_QUERY, #integer{value = 0},
|
||||||
|
[ast_body(Oracle, Icode), ast_body(Query, Icode),
|
||||||
|
ast_type_value(Q, Icode), ast_type_value(R, Icode)],
|
||||||
|
[word, typerep, typerep], word);
|
||||||
|
|
||||||
ast_body({qid, _, ["Oracle", "register"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.register'});
|
ast_body({qid, _, ["Oracle", "register"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.register'});
|
||||||
ast_body({qid, _, ["Oracle", "query"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query'});
|
ast_body({qid, _, ["Oracle", "query"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query'});
|
||||||
ast_body({qid, _, ["Oracle", "extend"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.extend'});
|
ast_body({qid, _, ["Oracle", "extend"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.extend'});
|
||||||
@@ -341,6 +351,11 @@ ast_body(?qid_app(["Crypto", "ecverify"], [Msg, PK, Sig], _, _), Icode) ->
|
|||||||
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
|
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
|
||||||
[word, word, sign_t()], word);
|
[word, word, sign_t()], word);
|
||||||
|
|
||||||
|
ast_body(?qid_app(["Crypto", "ecverify_secp256k1"], [Msg, PK, Sig], _, _), Icode) ->
|
||||||
|
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0},
|
||||||
|
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
|
||||||
|
[bytes_t(32), bytes_t(64), bytes_t(64)], word);
|
||||||
|
|
||||||
ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) ->
|
ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) ->
|
||||||
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode);
|
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode);
|
||||||
ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) ->
|
ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) ->
|
||||||
@@ -400,6 +415,12 @@ ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) ->
|
|||||||
|
|
||||||
ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) ->
|
ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) ->
|
||||||
builtin_call(addr_to_str, [ast_body(Addr, Icode)]);
|
builtin_call(addr_to_str, [ast_body(Addr, Icode)]);
|
||||||
|
ast_body(?qid_app(["Address", "is_oracle"], [Addr], _, _), Icode) ->
|
||||||
|
prim_call(?PRIM_CALL_ADDR_IS_ORACLE, #integer{value = 0},
|
||||||
|
[ast_body(Addr, Icode)], [word], word);
|
||||||
|
ast_body(?qid_app(["Address", "is_contract"], [Addr], _, _), Icode) ->
|
||||||
|
prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0},
|
||||||
|
[ast_body(Addr, Icode)], [word], word);
|
||||||
|
|
||||||
%% Other terms
|
%% Other terms
|
||||||
ast_body({id, _, Name}, _Icode) ->
|
ast_body({id, _, Name}, _Icode) ->
|
||||||
@@ -411,16 +432,19 @@ ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints
|
|||||||
#integer{value = Value};
|
#integer{value = Value};
|
||||||
ast_body({int, _, Value}, _Icode) ->
|
ast_body({int, _, Value}, _Icode) ->
|
||||||
#integer{value = Value};
|
#integer{value = Value};
|
||||||
ast_body({hash, _, Hash}, _Icode) ->
|
ast_body({bytes, _, Bin}, _Icode) ->
|
||||||
case Hash of
|
case aeb_memory:binary_to_words(Bin) of
|
||||||
<<Value:32/unit:8>> -> %% address
|
[Word] -> #integer{value = Word};
|
||||||
#integer{value = Value};
|
Words -> #tuple{cpts = [#integer{value = W} || W <- Words]}
|
||||||
<<Hi:32/unit:8, Lo:32/unit:8>> -> %% signature
|
|
||||||
#tuple{cpts = [#integer{value = Hi},
|
|
||||||
#integer{value = Lo}]}
|
|
||||||
end;
|
end;
|
||||||
|
ast_body({Key, _, Bin}, _Icode) when Key == account_pubkey;
|
||||||
|
Key == contract_pubkey;
|
||||||
|
Key == oracle_pubkey;
|
||||||
|
Key == oracle_query_id ->
|
||||||
|
<<Value:32/unit:8>> = Bin,
|
||||||
|
#integer{value = Value};
|
||||||
ast_body({string,_,Bin}, _Icode) ->
|
ast_body({string,_,Bin}, _Icode) ->
|
||||||
Cpts = [size(Bin) | aeso_memory:binary_to_words(Bin)],
|
Cpts = [size(Bin) | aeb_memory:binary_to_words(Bin)],
|
||||||
#tuple{cpts = [#integer{value=X} || X <- Cpts]};
|
#tuple{cpts = [#integer{value=X} || X <- Cpts]};
|
||||||
ast_body({tuple,_,Args}, Icode) ->
|
ast_body({tuple,_,Args}, Icode) ->
|
||||||
#tuple{cpts = [ast_body(A, Icode) || A <- Args]};
|
#tuple{cpts = [ast_body(A, Icode) || A <- Args]};
|
||||||
@@ -444,7 +468,7 @@ ast_body({app, _, {typed, _, {proj, _, {typed, _, Addr, {con, _, Contract}}, {id
|
|||||||
Gas = proplists:get_value("gas", ArgOpts ++ Defaults),
|
Gas = proplists:get_value("gas", ArgOpts ++ Defaults),
|
||||||
Value = proplists:get_value("value", ArgOpts ++ Defaults),
|
Value = proplists:get_value("value", ArgOpts ++ Defaults),
|
||||||
OutType = ast_typerep(OutT, Icode),
|
OutType = ast_typerep(OutT, Icode),
|
||||||
<<TypeHash:256>> = aeso_abi:function_type_hash(list_to_binary(FunName), ArgType, OutType),
|
<<TypeHash:256>> = aeb_abi:function_type_hash(list_to_binary(FunName), ArgType, OutType),
|
||||||
%% The function is represented by its type hash (which includes the name)
|
%% The function is represented by its type hash (which includes the name)
|
||||||
Fun = #integer{value = TypeHash},
|
Fun = #integer{value = TypeHash},
|
||||||
#prim_call_contract{
|
#prim_call_contract{
|
||||||
@@ -499,6 +523,8 @@ ast_body({switch,_,A,Cases}, Icode) ->
|
|||||||
ast_body({block,As,[{letval,_,Pat,_,E}|Rest]}, Icode) ->
|
ast_body({block,As,[{letval,_,Pat,_,E}|Rest]}, Icode) ->
|
||||||
#switch{expr=ast_body(E, Icode),
|
#switch{expr=ast_body(E, Icode),
|
||||||
cases=[{ast_body(Pat, Icode),ast_body({block,As,Rest}, Icode)}]};
|
cases=[{ast_body(Pat, Icode),ast_body({block,As,Rest}, Icode)}]};
|
||||||
|
ast_body({block, As, [{letfun, Ann, F, Args, _Type, Expr} | Rest]}, Icode) ->
|
||||||
|
ast_body({block, As, [{letval, Ann, F, unused, {lam, Ann, Args, Expr}} | Rest]}, Icode);
|
||||||
ast_body({block,_,[]}, _Icode) ->
|
ast_body({block,_,[]}, _Icode) ->
|
||||||
#tuple{cpts=[]};
|
#tuple{cpts=[]};
|
||||||
ast_body({block,_,[E]}, Icode) ->
|
ast_body({block,_,[E]}, Icode) ->
|
||||||
@@ -566,16 +592,30 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode)
|
|||||||
_ when not Monomorphic ->
|
_ when not Monomorphic ->
|
||||||
gen_error({cant_compare_polymorphic_type, Ann, Op, Type});
|
gen_error({cant_compare_polymorphic_type, Ann, Op, Type});
|
||||||
word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)};
|
word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)};
|
||||||
string ->
|
OtherType ->
|
||||||
Neg = case Op of
|
Neg = case Op of
|
||||||
'==' -> fun(X) -> X end;
|
'==' -> fun(X) -> X end;
|
||||||
'!=' -> fun(X) -> #unop{ op = '!', rand = X } end;
|
'!=' -> fun(X) -> #unop{ op = '!', rand = X } end;
|
||||||
_ -> gen_error({cant_compare, Ann, Op, Type})
|
_ -> gen_error({cant_compare, Ann, Op, Type})
|
||||||
end,
|
end,
|
||||||
Neg(#funcall{ function = #var_ref{name = {builtin, str_equal}},
|
Args = [ast_body(A, Icode), ast_body(B, Icode)],
|
||||||
args = [ast_body(A, Icode), ast_body(B, Icode)] });
|
Builtin =
|
||||||
|
case OtherType of
|
||||||
|
string ->
|
||||||
|
#funcall{ function = #var_ref{name = {builtin, str_equal}},
|
||||||
|
args = Args };
|
||||||
|
{tuple, Types} ->
|
||||||
|
case lists:usort(Types) of
|
||||||
|
[word] ->
|
||||||
|
#funcall{ function = #var_ref{name = {builtin, str_equal_p}},
|
||||||
|
args = [ #integer{value = 32 * length(Types)} | Args] };
|
||||||
_ -> gen_error({cant_compare, Ann, Op, Type})
|
_ -> gen_error({cant_compare, Ann, Op, Type})
|
||||||
end;
|
end;
|
||||||
|
_ ->
|
||||||
|
gen_error({cant_compare, Ann, Op, Type})
|
||||||
|
end,
|
||||||
|
Neg(Builtin)
|
||||||
|
end;
|
||||||
ast_binop('++', _, A, B, Icode) ->
|
ast_binop('++', _, A, B, Icode) ->
|
||||||
#funcall{ function = #var_ref{ name = {builtin, list_concat} },
|
#funcall{ function = #var_ref{ name = {builtin, list_concat} },
|
||||||
args = [ast_body(A, Icode), ast_body(B, Icode)] };
|
args = [ast_body(A, Icode), ast_body(B, Icode)] };
|
||||||
@@ -645,7 +685,7 @@ prim_call(Prim, Amount, Args, ArgTypes, OutType) ->
|
|||||||
true ->
|
true ->
|
||||||
PrimBin = binary:encode_unsigned(Prim),
|
PrimBin = binary:encode_unsigned(Prim),
|
||||||
ArgType = {tuple, ArgTypes},
|
ArgType = {tuple, ArgTypes},
|
||||||
<<TH:256>> = aeso_abi:function_type_hash(PrimBin, ArgType, OutType),
|
<<TH:256>> = aeb_abi:function_type_hash(PrimBin, ArgType, OutType),
|
||||||
TH;
|
TH;
|
||||||
false ->
|
false ->
|
||||||
0
|
0
|
||||||
@@ -674,7 +714,7 @@ make_type_def(Args, Def, Icode = #{ type_vars := TypeEnv }) ->
|
|||||||
ast_typerep(Def, Icode#{ type_vars := maps:merge(TypeEnv, TypeEnv1) })
|
ast_typerep(Def, Icode#{ type_vars := maps:merge(TypeEnv, TypeEnv1) })
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec ast_typerep(aeso_syntax:type()) -> aeso_sophia:type().
|
-spec ast_typerep(aeso_syntax:type()) -> aeb_aevm_data:type().
|
||||||
ast_typerep(Type) -> ast_typerep(Type, aeso_icode:new([])).
|
ast_typerep(Type) -> ast_typerep(Type, aeso_icode:new([])).
|
||||||
|
|
||||||
ast_typerep({id, _, Name}, Icode) ->
|
ast_typerep({id, _, Name}, Icode) ->
|
||||||
@@ -683,6 +723,8 @@ ast_typerep({qid, _, Name}, Icode) ->
|
|||||||
lookup_type_id(Name, [], Icode);
|
lookup_type_id(Name, [], Icode);
|
||||||
ast_typerep({con, _, _}, _) ->
|
ast_typerep({con, _, _}, _) ->
|
||||||
word; %% Contract type
|
word; %% Contract type
|
||||||
|
ast_typerep({bytes_t, _, Len}, _) ->
|
||||||
|
bytes_t(Len);
|
||||||
ast_typerep({app_t, _, {id, _, Name}, Args}, Icode) ->
|
ast_typerep({app_t, _, {id, _, Name}, Args}, Icode) ->
|
||||||
ArgReps = [ ast_typerep(Arg, Icode) || Arg <- Args ],
|
ArgReps = [ ast_typerep(Arg, Icode) || Arg <- Args ],
|
||||||
lookup_type_id(Name, ArgReps, Icode);
|
lookup_type_id(Name, ArgReps, Icode);
|
||||||
@@ -710,8 +752,9 @@ ast_typerep({variant_t, Cons}, Icode) ->
|
|||||||
ttl_t(Icode) ->
|
ttl_t(Icode) ->
|
||||||
ast_typerep({qid, [], ["Chain", "ttl"]}, Icode).
|
ast_typerep({qid, [], ["Chain", "ttl"]}, Icode).
|
||||||
|
|
||||||
sign_t() ->
|
sign_t() -> bytes_t(64).
|
||||||
{tuple, [word, word]}.
|
bytes_t(Len) when Len =< 32 -> word;
|
||||||
|
bytes_t(Len) -> {tuple, lists:duplicate((31 + Len) div 32, word)}.
|
||||||
|
|
||||||
get_signature_arg(Args0) ->
|
get_signature_arg(Args0) ->
|
||||||
NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0],
|
NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0],
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
%%%=============================================================================
|
|
||||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
|
||||||
%%% @doc
|
|
||||||
%%% BLAKE2b implementation in Erlang - for details see: https://blake2.net
|
|
||||||
%%% @end
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
-module(aeso_blake2).
|
|
||||||
|
|
||||||
-export([ blake2b/2
|
|
||||||
, blake2b/3
|
|
||||||
]).
|
|
||||||
|
|
||||||
-define(MAX_64BIT, 16#ffffffffffffffff).
|
|
||||||
|
|
||||||
-spec blake2b(HashLen :: integer(), Msg :: binary()) -> {ok, binary()}.
|
|
||||||
blake2b(HashLen, Msg) ->
|
|
||||||
blake2b(HashLen, Msg, <<>>).
|
|
||||||
|
|
||||||
-spec blake2b(HashLen :: integer(), Msg :: binary(), Key :: binary()) -> {ok, binary()}.
|
|
||||||
blake2b(HashLen, Msg0, Key) ->
|
|
||||||
%% If message should be keyed, prepend message with padded key.
|
|
||||||
Msg = <<(pad(128, Key))/binary, Msg0/binary>>,
|
|
||||||
|
|
||||||
%% Set up the initial state
|
|
||||||
Init = (16#01010000 + (byte_size(Key) bsl 8) + HashLen),
|
|
||||||
<<H0:64, H1_7/binary>> = blake_iv(),
|
|
||||||
H = <<(H0 bxor Init):64, H1_7/binary>>,
|
|
||||||
|
|
||||||
%% Perform the compression - message will be chopped into 128-byte chunks.
|
|
||||||
State = blake2b_compress(H, Msg, 0),
|
|
||||||
|
|
||||||
%% Just return the requested part of the hash
|
|
||||||
{ok, binary_part(to_little_endian(State), {0, HashLen})}.
|
|
||||||
|
|
||||||
blake2b_compress(H, <<Chunk:(128*8), Rest/binary>>, BCompr) when Rest /= <<>> ->
|
|
||||||
H1 = blake2b_compress(H, <<Chunk:(128*8)>>, BCompr + 128, false),
|
|
||||||
blake2b_compress(H1, Rest, BCompr + 128);
|
|
||||||
blake2b_compress(H, SmallChunk, BCompr) ->
|
|
||||||
Size = byte_size(SmallChunk),
|
|
||||||
FillSize = (128 - Size) * 8,
|
|
||||||
blake2b_compress(H, <<SmallChunk/binary, 0:FillSize>>, BCompr + Size, true).
|
|
||||||
|
|
||||||
blake2b_compress(H, Chunk0, BCompr, Last) ->
|
|
||||||
Chunk = to_big_endian(Chunk0),
|
|
||||||
<<V0_11:(12*64), V12:64, V13:64, V14:64, V15:64>> = <<H/binary, (blake_iv())/binary>>,
|
|
||||||
V12_ = V12 bxor (BCompr band ?MAX_64BIT),
|
|
||||||
V13_ = V13 bxor ((BCompr bsr 64) band ?MAX_64BIT),
|
|
||||||
V14_ = case Last of
|
|
||||||
false -> V14;
|
|
||||||
true -> V14 bxor ?MAX_64BIT
|
|
||||||
end,
|
|
||||||
V = <<V0_11:(12*64), V12_:64, V13_:64, V14_:64, V15:64>>,
|
|
||||||
|
|
||||||
<<VLow:(8*64), VHigh:(8*64)>> =
|
|
||||||
lists:foldl(fun(Round, Vx) -> blake2b_mix(Round, Chunk, Vx) end, V, lists:seq(0, 11)),
|
|
||||||
|
|
||||||
<<HInt:(8*64)>> = H,
|
|
||||||
<<((HInt bxor VLow) bxor VHigh):(8*64)>>.
|
|
||||||
|
|
||||||
blake2b_mix(Rnd, Chunk, V) ->
|
|
||||||
<<V0:64, V1:64, V2:64, V3:64, V4:64, V5:64, V6:64, V7:64, V8:64,
|
|
||||||
V9:64, V10:64, V11:64, V12:64, V13:64, V14:64, V15:64>> = V,
|
|
||||||
<<M0:64, M1:64, M2:64, M3:64, M4:64, M5:64, M6:64, M7:64, M8:64,
|
|
||||||
M9:64, M10:64, M11:64, M12:64, M13:64, M14:64, M15:64>> = Chunk,
|
|
||||||
Ms = {M0, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15},
|
|
||||||
M = fun(Ix) -> element(Ix+1, Ms) end,
|
|
||||||
|
|
||||||
[S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15] = sigma(Rnd rem 10),
|
|
||||||
|
|
||||||
{Vx0, Vx4, Vx8, Vx12} = blake2b_mix(V0, V4, V8, V12, M(S0), M(S1)),
|
|
||||||
{Vx1, Vx5, Vx9, Vx13} = blake2b_mix(V1, V5, V9, V13, M(S2), M(S3)),
|
|
||||||
{Vx2, Vx6, Vx10, Vx14} = blake2b_mix(V2, V6, V10, V14, M(S4), M(S5)),
|
|
||||||
{Vx3, Vx7, Vx11, Vx15} = blake2b_mix(V3, V7, V11, V15, M(S6), M(S7)),
|
|
||||||
|
|
||||||
{Vy0, Vy5, Vy10, Vy15} = blake2b_mix(Vx0, Vx5, Vx10, Vx15, M(S8), M(S9)),
|
|
||||||
{Vy1, Vy6, Vy11, Vy12} = blake2b_mix(Vx1, Vx6, Vx11, Vx12, M(S10), M(S11)),
|
|
||||||
{Vy2, Vy7, Vy8, Vy13} = blake2b_mix(Vx2, Vx7, Vx8, Vx13, M(S12), M(S13)),
|
|
||||||
{Vy3, Vy4, Vy9, Vy14} = blake2b_mix(Vx3, Vx4, Vx9, Vx14, M(S14), M(S15)),
|
|
||||||
|
|
||||||
<<Vy0:64, Vy1:64, Vy2:64, Vy3:64, Vy4:64, Vy5:64, Vy6:64, Vy7:64, Vy8:64,
|
|
||||||
Vy9:64, Vy10:64, Vy11:64, Vy12:64, Vy13:64, Vy14:64, Vy15:64>>.
|
|
||||||
|
|
||||||
blake2b_mix(Va, Vb, Vc, Vd, X, Y) ->
|
|
||||||
Va1 = (Va + Vb + X) band ?MAX_64BIT,
|
|
||||||
Vd1 = rotr64(32, Vd bxor Va1),
|
|
||||||
|
|
||||||
Vc1 = (Vc + Vd1) band ?MAX_64BIT,
|
|
||||||
Vb1 = rotr64(24, Vb bxor Vc1),
|
|
||||||
|
|
||||||
Va2 = (Va1 + Vb1 + Y) band ?MAX_64BIT,
|
|
||||||
Vd2 = rotr64(16, Va2 bxor Vd1),
|
|
||||||
|
|
||||||
Vc2 = (Vc1 + Vd2) band ?MAX_64BIT,
|
|
||||||
Vb2 = rotr64(63, Vb1 bxor Vc2),
|
|
||||||
|
|
||||||
{Va2, Vb2, Vc2, Vd2}.
|
|
||||||
|
|
||||||
blake_iv() ->
|
|
||||||
IV0 = 16#6A09E667F3BCC908,
|
|
||||||
IV1 = 16#BB67AE8584CAA73B,
|
|
||||||
IV2 = 16#3C6EF372FE94F82B,
|
|
||||||
IV3 = 16#A54FF53A5F1D36F1,
|
|
||||||
IV4 = 16#510E527FADE682D1,
|
|
||||||
IV5 = 16#9B05688C2B3E6C1F,
|
|
||||||
IV6 = 16#1F83D9ABFB41BD6B,
|
|
||||||
IV7 = 16#5BE0CD19137E2179,
|
|
||||||
<<IV0:64, IV1:64, IV2:64, IV3:64, IV4:64, IV5:64, IV6:64, IV7:64>>.
|
|
||||||
|
|
||||||
sigma(N) ->
|
|
||||||
{_, Row} = lists:keyfind(N, 1, sigma()), Row.
|
|
||||||
|
|
||||||
sigma() ->
|
|
||||||
[{0, [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]},
|
|
||||||
{1, [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3]},
|
|
||||||
{2, [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4]},
|
|
||||||
{3, [ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8]},
|
|
||||||
{4, [ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13]},
|
|
||||||
{5, [ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9]},
|
|
||||||
{6, [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11]},
|
|
||||||
{7, [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10]},
|
|
||||||
{8, [ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5]},
|
|
||||||
{9, [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]}].
|
|
||||||
|
|
||||||
rotr64(N, I64) ->
|
|
||||||
<<I64rot:64>> = rotr641(N, <<I64:64>>),
|
|
||||||
I64rot.
|
|
||||||
|
|
||||||
rotr641(16, <<X:(64-16), Y:16>>) -> <<Y:16, X:(64-16)>>;
|
|
||||||
rotr641(24, <<X:(64-24), Y:24>>) -> <<Y:24, X:(64-24)>>;
|
|
||||||
rotr641(32, <<X:(64-32), Y:32>>) -> <<Y:32, X:(64-32)>>;
|
|
||||||
rotr641(63, <<X:(64-63), Y:63>>) -> <<Y:63, X:(64-63)>>.
|
|
||||||
|
|
||||||
pad(N, Bin) ->
|
|
||||||
case (N - (byte_size(Bin) rem N)) rem N of
|
|
||||||
0 -> Bin;
|
|
||||||
Pad -> <<Bin/binary, 0:(Pad *8)>>
|
|
||||||
end.
|
|
||||||
|
|
||||||
to_big_endian(Bin) -> to_big_endian(Bin, <<>>).
|
|
||||||
to_big_endian(<<>>, Acc) -> Acc;
|
|
||||||
to_big_endian(<<UInt64:1/little-unsigned-integer-unit:64, Rest/binary>>, Acc) ->
|
|
||||||
to_big_endian(Rest, <<Acc/binary, UInt64:1/big-unsigned-integer-unit:64>>).
|
|
||||||
|
|
||||||
to_little_endian(Bin) -> to_little_endian(Bin, <<>>).
|
|
||||||
to_little_endian(<<>>, Acc) -> Acc;
|
|
||||||
to_little_endian(<<UInt64:1/big-unsigned-integer-unit:64, Rest/binary>>, Acc) ->
|
|
||||||
to_little_endian(Rest, <<Acc/binary, UInt64:1/little-unsigned-integer-unit:64>>).
|
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ operand(T) -> T.
|
|||||||
str_to_icode(String) when is_list(String) ->
|
str_to_icode(String) when is_list(String) ->
|
||||||
str_to_icode(list_to_binary(String));
|
str_to_icode(list_to_binary(String));
|
||||||
str_to_icode(BinStr) ->
|
str_to_icode(BinStr) ->
|
||||||
Cpts = [size(BinStr) | aeso_memory:binary_to_words(BinStr)],
|
Cpts = [size(BinStr) | aeb_memory:binary_to_words(BinStr)],
|
||||||
#tuple{ cpts = [ #integer{value = X} || X <- Cpts ] }.
|
#tuple{ cpts = [ #integer{value = X} || X <- Cpts ] }.
|
||||||
|
|
||||||
check_event_type(Icode) ->
|
check_event_type(Icode) ->
|
||||||
|
|||||||
+71
-25
@@ -17,6 +17,7 @@
|
|||||||
, sophia_type_to_typerep/1
|
, sophia_type_to_typerep/1
|
||||||
, to_sophia_value/4
|
, to_sophia_value/4
|
||||||
, to_sophia_value/5
|
, to_sophia_value/5
|
||||||
|
, decode_calldata/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||||
@@ -103,13 +104,12 @@ from_string(ContractString, Options) ->
|
|||||||
%% General programming errors in the compiler just signal error.
|
%% General programming errors in the compiler just signal error.
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec string_to_icode(string(), [option() | permissive_address_literals]) -> map().
|
-spec string_to_icode(string(), [option()]) -> map().
|
||||||
string_to_icode(ContractString, Options0) ->
|
string_to_icode(ContractString, Options) ->
|
||||||
{InferOptions, Options} = lists:partition(fun(Opt) -> Opt == permissive_address_literals end, Options0),
|
|
||||||
Ast = parse(ContractString, Options),
|
Ast = parse(ContractString, Options),
|
||||||
pp_sophia_code(Ast, Options),
|
pp_sophia_code(Ast, Options),
|
||||||
pp_ast(Ast, Options),
|
pp_ast(Ast, Options),
|
||||||
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | InferOptions]),
|
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]),
|
||||||
pp_typed_ast(TypedAst, Options),
|
pp_typed_ast(TypedAst, Options),
|
||||||
Icode = ast_to_icode(TypedAst, Options),
|
Icode = ast_to_icode(TypedAst, Options),
|
||||||
pp_icode(Icode, Options),
|
pp_icode(Icode, Options),
|
||||||
@@ -150,11 +150,11 @@ check_call(Source, FunName, Args, Options) ->
|
|||||||
|
|
||||||
check_call(ContractString0, FunName, Args, Options, PatchFun) ->
|
check_call(ContractString0, FunName, Args, Options, PatchFun) ->
|
||||||
try
|
try
|
||||||
%% First check the contract without the __call function and no permissive literals
|
%% First check the contract without the __call function
|
||||||
#{} = string_to_icode(ContractString0, Options),
|
#{} = string_to_icode(ContractString0, Options),
|
||||||
ContractString = insert_call_function(ContractString0, FunName, Args, Options),
|
ContractString = insert_call_function(ContractString0, FunName, Args, Options),
|
||||||
#{typed_ast := TypedAst,
|
#{typed_ast := TypedAst,
|
||||||
icode := Icode} = string_to_icode(ContractString, [permissive_address_literals | Options]),
|
icode := Icode} = string_to_icode(ContractString, Options),
|
||||||
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
|
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
|
||||||
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
|
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
|
||||||
RetVMType = case RetType of
|
RetVMType = case RetType of
|
||||||
@@ -188,7 +188,7 @@ insert_call_function(Code, FunName, Args, Options) ->
|
|||||||
[ Code,
|
[ Code,
|
||||||
"\n\n",
|
"\n\n",
|
||||||
lists:duplicate(Ind, " "),
|
lists:duplicate(Ind, " "),
|
||||||
"function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
|
"stateful function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-spec insert_init_function(string(), options()) -> string().
|
-spec insert_init_function(string(), options()) -> string().
|
||||||
@@ -207,7 +207,7 @@ last_contract_indent(Decls) ->
|
|||||||
_ -> 0
|
_ -> 0
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec to_sophia_value(string(), string(), ok | error | revert, aeso_sophia:data()) ->
|
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
|
||||||
{ok, aeso_syntax:expr()} | {error, term()}.
|
{ok, aeso_syntax:expr()} | {error, term()}.
|
||||||
to_sophia_value(ContractString, Fun, ResType, Data) ->
|
to_sophia_value(ContractString, Fun, ResType, Data) ->
|
||||||
to_sophia_value(ContractString, Fun, ResType, Data, []).
|
to_sophia_value(ContractString, Fun, ResType, Data, []).
|
||||||
@@ -217,7 +217,7 @@ to_sophia_value(ContractString, Fun, ResType, Data) ->
|
|||||||
to_sophia_value(_, _, error, Err, _Options) ->
|
to_sophia_value(_, _, error, Err, _Options) ->
|
||||||
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
|
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
|
||||||
to_sophia_value(_, _, revert, Data, _Options) ->
|
to_sophia_value(_, _, revert, Data, _Options) ->
|
||||||
case aeso_heap:from_binary(string, Data) of
|
case aeb_heap:from_binary(string, Data) of
|
||||||
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
|
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
|
||||||
{error, _} = Err -> Err
|
{error, _} = Err -> Err
|
||||||
end;
|
end;
|
||||||
@@ -226,10 +226,10 @@ to_sophia_value(ContractString, FunName, ok, Data, Options) ->
|
|||||||
#{ typed_ast := TypedAst,
|
#{ typed_ast := TypedAst,
|
||||||
type_env := TypeEnv,
|
type_env := TypeEnv,
|
||||||
icode := Icode } = string_to_icode(ContractString, Options),
|
icode := Icode } = string_to_icode(ContractString, Options),
|
||||||
{ok, Type0} = get_decode_type(FunName, TypedAst),
|
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
|
||||||
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
||||||
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
||||||
case aeso_heap:from_binary(VmType, Data) of
|
case aeb_heap:from_binary(VmType, Data) of
|
||||||
{ok, VmValue} ->
|
{ok, VmValue} ->
|
||||||
try
|
try
|
||||||
{ok, translate_vm_value(VmType, Type, VmValue)}
|
{ok, translate_vm_value(VmType, Type, VmValue)}
|
||||||
@@ -256,18 +256,21 @@ to_sophia_value(ContractString, FunName, ok, Data, Options) ->
|
|||||||
fun (E) -> io_lib:format("~p", [E]) end)}
|
fun (E) -> io_lib:format("~p", [E]) end)}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
address_literal(N) -> {hash, [], <<N:256>>}. % TODO
|
address_literal(Type, N) -> {Type, [], <<N:256>>}.
|
||||||
|
|
||||||
%% TODO: somewhere else
|
%% TODO: somewhere else
|
||||||
translate_vm_value(word, {id, _, "address"}, N) -> address_literal(N);
|
-spec translate_vm_value(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
|
||||||
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(N);
|
translate_vm_value(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
|
||||||
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(N);
|
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
|
||||||
translate_vm_value(word, {id, _, "hash"}, N) -> {hash, [], <<N:256>>};
|
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
|
||||||
|
translate_vm_value(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
|
||||||
translate_vm_value(word, {id, _, "int"}, N) -> {int, [], N};
|
translate_vm_value(word, {id, _, "int"}, N) -> {int, [], N};
|
||||||
translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, N});
|
translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, N});
|
||||||
translate_vm_value(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
|
translate_vm_value(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
|
||||||
translate_vm_value({tuple, [word, word]}, {id, _, "signature"}, {tuple, [Hi, Lo]}) ->
|
translate_vm_value(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
|
||||||
{hash, [], <<Hi:256, Lo:256>>};
|
{bytes, [], <<Val:Len/unit:8>>};
|
||||||
|
translate_vm_value({tuple, _}, {bytes_t, _, Len}, Val) ->
|
||||||
|
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
|
||||||
translate_vm_value(string, {id, _, "string"}, S) -> {string, [], S};
|
translate_vm_value(string, {id, _, "string"}, S) -> {string, [], S};
|
||||||
translate_vm_value({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
|
translate_vm_value({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
|
||||||
{list, [], [translate_vm_value(VmType, Type, X) || X <- List]};
|
{list, [], [translate_vm_value(VmType, Type, X) || X <- List]};
|
||||||
@@ -311,15 +314,58 @@ translate_vm_value(_VmType, _Type, _Data) ->
|
|||||||
throw(cannot_translate_to_sophia).
|
throw(cannot_translate_to_sophia).
|
||||||
|
|
||||||
-spec create_calldata(string(), string(), [string()]) ->
|
-spec create_calldata(string(), string(), [string()]) ->
|
||||||
{ok, binary(), aeso_sophia:type(), aeso_sophia:type()}
|
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
|
||||||
| {error, term()}.
|
| {error, term()}.
|
||||||
create_calldata(Code, Fun, Args) ->
|
create_calldata(Code, Fun, Args) ->
|
||||||
case check_call(Code, Fun, Args, []) of
|
case check_call(Code, Fun, Args, []) of
|
||||||
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
|
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
|
||||||
aeso_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
|
aeb_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
|
||||||
{error, _} = Err -> Err
|
{error, _} = Err -> Err
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec decode_calldata(string(), string(), binary()) ->
|
||||||
|
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
|
||||||
|
| {error, term()}.
|
||||||
|
decode_calldata(ContractString, FunName, Calldata) ->
|
||||||
|
try
|
||||||
|
#{ typed_ast := TypedAst,
|
||||||
|
type_env := TypeEnv,
|
||||||
|
icode := Icode } = string_to_icode(ContractString, []),
|
||||||
|
{ok, Args, _} = get_decode_type(FunName, TypedAst),
|
||||||
|
DropArg = fun({arg, _, _, T}) -> T; (T) -> T end,
|
||||||
|
ArgTypes = lists:map(DropArg, Args),
|
||||||
|
Type0 = {tuple_t, [], ArgTypes},
|
||||||
|
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
||||||
|
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
||||||
|
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
|
||||||
|
{ok, {_, VmValue}} ->
|
||||||
|
try
|
||||||
|
{tuple, [], Values} = translate_vm_value(VmType, Type, VmValue),
|
||||||
|
{ok, ArgTypes, Values}
|
||||||
|
catch throw:cannot_translate_to_sophia ->
|
||||||
|
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
||||||
|
{error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
|
||||||
|
[VmValue, VmType, Type0Str]))],
|
||||||
|
fun (E) -> E end)}
|
||||||
|
end;
|
||||||
|
{error, _Err} ->
|
||||||
|
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
|
||||||
|
fun(E) -> E end)}
|
||||||
|
end
|
||||||
|
catch
|
||||||
|
error:{parse_errors, Errors} ->
|
||||||
|
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
|
||||||
|
error:{type_errors, Errors} ->
|
||||||
|
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
|
||||||
|
error:{badmatch, {error, missing_function}} ->
|
||||||
|
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
|
||||||
|
fun (E) -> E end)};
|
||||||
|
throw:Error -> %Don't ask
|
||||||
|
{error, join_errors("Code errors", [Error],
|
||||||
|
fun (E) -> io_lib:format("~p", [E]) end)}
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
get_arg_icode(Funs) ->
|
get_arg_icode(Funs) ->
|
||||||
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
|
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
|
||||||
[Args] -> Args;
|
[Args] -> Args;
|
||||||
@@ -340,11 +386,11 @@ get_call_type([_ | Contracts]) ->
|
|||||||
get_call_type(Contracts).
|
get_call_type(Contracts).
|
||||||
|
|
||||||
get_decode_type(FunName, [{contract, _, _, Defs}]) ->
|
get_decode_type(FunName, [{contract, _, _, Defs}]) ->
|
||||||
GetType = fun({letfun, _, {id, _, Name}, _, Ret, _}) when Name == FunName -> [Ret];
|
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}];
|
||||||
({fun_decl, _, {id, _, Name}, {fun_t, _, _, _, Ret}}) when Name == FunName -> [Ret];
|
({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
|
||||||
(_) -> [] end,
|
(_) -> [] end,
|
||||||
case lists:flatmap(GetType, Defs) of
|
case lists:flatmap(GetType, Defs) of
|
||||||
[Type] -> {ok, Type};
|
[{Args, Ret}] -> {ok, Args, Ret};
|
||||||
[] -> {error, missing_function}
|
[] -> {error, missing_function}
|
||||||
end;
|
end;
|
||||||
get_decode_type(FunName, [_ | Contracts]) ->
|
get_decode_type(FunName, [_ | Contracts]) ->
|
||||||
@@ -352,7 +398,7 @@ get_decode_type(FunName, [_ | Contracts]) ->
|
|||||||
get_decode_type(FunName, Contracts).
|
get_decode_type(FunName, Contracts).
|
||||||
|
|
||||||
%% Translate an icode value (error if not value) to an Erlang term that can be
|
%% Translate an icode value (error if not value) to an Erlang term that can be
|
||||||
%% consumed by aeso_heap:to_binary().
|
%% consumed by aeb_heap:to_binary().
|
||||||
icode_to_term(word, {integer, N}) -> N;
|
icode_to_term(word, {integer, N}) -> N;
|
||||||
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
|
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
|
||||||
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
|
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
|
||||||
@@ -400,7 +446,7 @@ to_bytecode([], _) -> [].
|
|||||||
|
|
||||||
extract_type_info(#{functions := Functions} =_Icode) ->
|
extract_type_info(#{functions := Functions} =_Icode) ->
|
||||||
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
|
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
|
||||||
TypeInfo = [aeso_abi:function_type_info(list_to_binary(lists:last(Name)),
|
TypeInfo = [aeb_abi:function_type_info(list_to_binary(lists:last(Name)),
|
||||||
ArgTypesOnly(Args), TypeRep)
|
ArgTypesOnly(Args), TypeRep)
|
||||||
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
|
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
|
||||||
not is_tuple(Name),
|
not is_tuple(Name),
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,301 +0,0 @@
|
|||||||
-module(aeso_heap).
|
|
||||||
|
|
||||||
-export([ to_binary/1
|
|
||||||
, to_binary/2
|
|
||||||
, from_heap/3
|
|
||||||
, from_binary/2
|
|
||||||
, from_binary/3
|
|
||||||
, maps_with_next_id/1
|
|
||||||
, set_next_id/2
|
|
||||||
, heap_fragment/3
|
|
||||||
, heap_value/3
|
|
||||||
, heap_value/4
|
|
||||||
, heap_value_pointer/1
|
|
||||||
, heap_value_maps/1
|
|
||||||
, heap_value_offset/1
|
|
||||||
, heap_value_heap/1
|
|
||||||
, heap_fragment_maps/1
|
|
||||||
, heap_fragment_offset/1
|
|
||||||
, heap_fragment_heap/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
-export_type([binary_value/0, heap_value/0, offset/0, heap_fragment/0]).
|
|
||||||
|
|
||||||
-include("aeso_icode.hrl").
|
|
||||||
-include_lib("aesophia/include/aeso_heap.hrl").
|
|
||||||
|
|
||||||
-type word() :: non_neg_integer().
|
|
||||||
-type pointer() :: word().
|
|
||||||
-opaque heap_fragment() :: #heap{}.
|
|
||||||
-type offset() :: non_neg_integer().
|
|
||||||
-type binary_value() :: binary().
|
|
||||||
-type heap_value() :: {pointer(), heap_fragment()}.
|
|
||||||
|
|
||||||
|
|
||||||
-spec maps_with_next_id(heap_fragment()) -> #maps{}.
|
|
||||||
%% Create just a maps value, don't keep rest of Heap
|
|
||||||
maps_with_next_id(#heap{maps = #maps{next_id = N}}) ->
|
|
||||||
#maps{ next_id = N }.
|
|
||||||
|
|
||||||
-spec set_next_id(heap_fragment(), non_neg_integer()) -> heap_fragment().
|
|
||||||
set_next_id(Heap, N) ->
|
|
||||||
Heap#heap{ maps = Heap#heap.maps#maps{ next_id = N } }.
|
|
||||||
|
|
||||||
%% -- data type heap_fragment
|
|
||||||
|
|
||||||
-spec heap_fragment(binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_fragment().
|
|
||||||
heap_fragment(Heap) ->
|
|
||||||
heap_fragment(#maps{ next_id = 0 }, 0, Heap).
|
|
||||||
|
|
||||||
-spec heap_fragment(#maps{}, offset(),
|
|
||||||
binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_fragment().
|
|
||||||
heap_fragment(Maps, Offset, Heap) ->
|
|
||||||
#heap{maps = Maps, offset = Offset, heap = Heap}.
|
|
||||||
|
|
||||||
-spec heap_fragment_maps(heap_fragment()) -> #maps{}.
|
|
||||||
heap_fragment_maps(#heap{maps = Maps}) ->
|
|
||||||
Maps.
|
|
||||||
|
|
||||||
-spec heap_fragment_offset(heap_fragment()) -> offset().
|
|
||||||
heap_fragment_offset(#heap{offset = Offs}) ->
|
|
||||||
Offs.
|
|
||||||
|
|
||||||
-spec heap_fragment_heap(heap_fragment()) -> binary() | #{non_neg_integer() => non_neg_integer()}.
|
|
||||||
heap_fragment_heap(#heap{heap = Heap}) ->
|
|
||||||
Heap.
|
|
||||||
|
|
||||||
|
|
||||||
%% -- data type heap_value
|
|
||||||
|
|
||||||
-spec heap_value(#maps{}, pointer(),
|
|
||||||
binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_value().
|
|
||||||
heap_value(Maps, Ptr, Heap) ->
|
|
||||||
heap_value(Maps, Ptr, Heap, 0).
|
|
||||||
|
|
||||||
-spec heap_value(#maps{}, pointer(),
|
|
||||||
binary() | #{non_neg_integer() => non_neg_integer()}, offset()) -> heap_value().
|
|
||||||
heap_value(Maps, Ptr, Heap, Offs) ->
|
|
||||||
{Ptr, heap_fragment(Maps, Offs, Heap)}.
|
|
||||||
|
|
||||||
-spec heap_value_pointer(heap_value()) -> pointer().
|
|
||||||
heap_value_pointer({Ptr, _}) -> Ptr.
|
|
||||||
|
|
||||||
-spec heap_value_maps(heap_value()) -> #maps{}.
|
|
||||||
heap_value_maps({_, Heap}) -> Heap#heap.maps.
|
|
||||||
|
|
||||||
-spec heap_value_offset(heap_value()) -> offset().
|
|
||||||
heap_value_offset({_, Heap}) -> Heap#heap.offset.
|
|
||||||
|
|
||||||
-spec heap_value_heap(heap_value()) ->
|
|
||||||
binary() | #{non_neg_integer() => non_neg_integer()}.
|
|
||||||
heap_value_heap({_, Heap}) -> Heap#heap.heap.
|
|
||||||
|
|
||||||
%% -- Value to binary --------------------------------------------------------
|
|
||||||
|
|
||||||
-spec to_binary(aeso_sophia:data()) -> aeso_sophia:heap().
|
|
||||||
%% Encode the data as a heap where the first word is the value (for unboxed
|
|
||||||
%% types) or a pointer to the value (for boxed types).
|
|
||||||
to_binary(Data) ->
|
|
||||||
to_binary(Data, 0).
|
|
||||||
|
|
||||||
to_binary(Data, BaseAddress) ->
|
|
||||||
{Address, Memory} = to_binary1(Data, BaseAddress + 32),
|
|
||||||
R = <<Address:256, Memory/binary>>,
|
|
||||||
R.
|
|
||||||
|
|
||||||
|
|
||||||
%% Allocate the data in memory, from the given address. Return a pair
|
|
||||||
%% of memory contents from that address and the value representing the
|
|
||||||
%% data.
|
|
||||||
to_binary1(Data,_Address) when is_integer(Data) ->
|
|
||||||
{Data,<<>>};
|
|
||||||
to_binary1(Data, Address) when is_binary(Data) ->
|
|
||||||
%% a string
|
|
||||||
Words = aeso_memory:binary_to_words(Data),
|
|
||||||
{Address,<<(size(Data)):256, << <<W:256>> || W <- Words>>/binary>>};
|
|
||||||
to_binary1(none, Address) -> to_binary1({variant, 0, []}, Address);
|
|
||||||
to_binary1({some, Value}, Address) -> to_binary1({variant, 1, [Value]}, Address);
|
|
||||||
to_binary1(word, Address) -> to_binary1({?TYPEREP_WORD_TAG}, Address);
|
|
||||||
to_binary1(string, Address) -> to_binary1({?TYPEREP_STRING_TAG}, Address);
|
|
||||||
to_binary1(typerep, Address) -> to_binary1({?TYPEREP_TYPEREP_TAG}, Address);
|
|
||||||
to_binary1(function, Address) -> to_binary1({?TYPEREP_FUN_TAG}, Address);
|
|
||||||
to_binary1({list, T}, Address) -> to_binary1({?TYPEREP_LIST_TAG, T}, Address);
|
|
||||||
to_binary1({option, T}, Address) -> to_binary1({variant, [[], [T]]}, Address);
|
|
||||||
to_binary1({tuple, Ts}, Address) -> to_binary1({?TYPEREP_TUPLE_TAG, Ts}, Address);
|
|
||||||
to_binary1({variant, Cons}, Address) -> to_binary1({?TYPEREP_VARIANT_TAG, Cons}, Address);
|
|
||||||
to_binary1({map, K, V}, Address) -> to_binary1({?TYPEREP_MAP_TAG, K, V}, Address);
|
|
||||||
to_binary1({variant, Tag, Args}, Address) ->
|
|
||||||
to_binary1(list_to_tuple([Tag | Args]), Address);
|
|
||||||
to_binary1(Map, Address) when is_map(Map) ->
|
|
||||||
Size = maps:size(Map),
|
|
||||||
%% Sort according to binary ordering
|
|
||||||
KVs = lists:sort([ {to_binary(K), to_binary(V)} || {K, V} <- maps:to_list(Map) ]),
|
|
||||||
{Address, <<Size:256, << <<(byte_size(K)):256, K/binary,
|
|
||||||
(byte_size(V)):256, V/binary>> || {K, V} <- KVs >>/binary >>};
|
|
||||||
to_binary1({}, _Address) ->
|
|
||||||
{0, <<>>};
|
|
||||||
to_binary1(Data, Address) when is_tuple(Data) ->
|
|
||||||
{Elems,Memory} = to_binaries(tuple_to_list(Data),Address+32*size(Data)),
|
|
||||||
ElemsBin = << <<W:256>> || W <- Elems>>,
|
|
||||||
{Address,<< ElemsBin/binary, Memory/binary >>};
|
|
||||||
to_binary1([],_Address) ->
|
|
||||||
<<Nil:256>> = <<(-1):256>>,
|
|
||||||
{Nil,<<>>};
|
|
||||||
to_binary1([H|T],Address) ->
|
|
||||||
to_binary1({H,T},Address).
|
|
||||||
|
|
||||||
|
|
||||||
to_binaries([],_Address) ->
|
|
||||||
{[],<<>>};
|
|
||||||
to_binaries([H|T],Address) ->
|
|
||||||
{HRep,HMem} = to_binary1(H,Address),
|
|
||||||
{TRep,TMem} = to_binaries(T,Address+size(HMem)),
|
|
||||||
{[HRep|TRep],<<HMem/binary, TMem/binary>>}.
|
|
||||||
|
|
||||||
%% Interpret a return value (a binary) using a type rep.
|
|
||||||
|
|
||||||
-spec from_heap(Type :: ?Type(), Heap :: binary(), Ptr :: integer()) ->
|
|
||||||
{ok, term()} | {error, term()}.
|
|
||||||
from_heap(Type, Heap, Ptr) ->
|
|
||||||
try {ok, from_binary(#{}, Type, Heap, Ptr)}
|
|
||||||
catch _:Err ->
|
|
||||||
%% io:format("** Error: from_heap failed with ~p\n ~p\n", [Err, erlang:get_stacktrace()]),
|
|
||||||
{error, Err}
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% Base address is the address of the first word of the given heap.
|
|
||||||
-spec from_binary(T :: ?Type(),
|
|
||||||
Heap :: binary(),
|
|
||||||
BaseAddr :: non_neg_integer()) ->
|
|
||||||
{ok, term()} | {error, term()}.
|
|
||||||
from_binary(T, Heap = <<V:256, _/binary>>, BaseAddr) ->
|
|
||||||
from_heap(T, <<0:BaseAddr/unit:8, Heap/binary>>, V);
|
|
||||||
from_binary(_, Bin, _BaseAddr) ->
|
|
||||||
{error, {binary_too_short, Bin}}.
|
|
||||||
|
|
||||||
-spec from_binary(?Type(), binary()) -> {ok, term()} | {error, term()}.
|
|
||||||
from_binary(T, Heap) ->
|
|
||||||
from_binary(T, Heap, 0).
|
|
||||||
|
|
||||||
from_binary(_, word, _, V) ->
|
|
||||||
V;
|
|
||||||
from_binary(_, signed_word, _, V) ->
|
|
||||||
<<N:256/signed>> = <<V:256>>,
|
|
||||||
N;
|
|
||||||
from_binary(_, bool, _, V) ->
|
|
||||||
case V of
|
|
||||||
0 -> false;
|
|
||||||
1 -> true
|
|
||||||
end;
|
|
||||||
from_binary(_, string, Heap, V) ->
|
|
||||||
StringSize = heap_word(Heap,V),
|
|
||||||
BitAddr = 8*(V+32),
|
|
||||||
<<_:BitAddr,Bytes:StringSize/binary,_/binary>> = Heap,
|
|
||||||
Bytes;
|
|
||||||
from_binary(_, {tuple, []}, _, _) ->
|
|
||||||
{};
|
|
||||||
from_binary(Visited, {tuple,Cpts}, Heap, V) ->
|
|
||||||
check_circular_refs(Visited, V),
|
|
||||||
NewVisited = Visited#{V => true},
|
|
||||||
ElementNums = lists:seq(0, length(Cpts)-1),
|
|
||||||
TypesAndPointers = lists:zip(Cpts, ElementNums),
|
|
||||||
ElementAddress = fun(Index) -> V + 32 * Index end,
|
|
||||||
Element = fun(Index) ->
|
|
||||||
heap_word(Heap, ElementAddress(Index))
|
|
||||||
end,
|
|
||||||
Convert = fun(Type, Index) ->
|
|
||||||
from_binary(NewVisited, Type, Heap, Element(Index))
|
|
||||||
end,
|
|
||||||
Elements = [Convert(T, I) || {T,I} <- TypesAndPointers],
|
|
||||||
list_to_tuple(Elements);
|
|
||||||
from_binary(Visited, {list, Elem}, Heap, V) ->
|
|
||||||
<<Nil:256>> = <<(-1):256>>,
|
|
||||||
if V==Nil ->
|
|
||||||
[];
|
|
||||||
true ->
|
|
||||||
{H,T} = from_binary(Visited, {tuple,[Elem,{list,Elem}]},Heap,V),
|
|
||||||
[H|T]
|
|
||||||
end;
|
|
||||||
from_binary(Visited, {option, A}, Heap, V) ->
|
|
||||||
from_binary(Visited, {variant_t, [{none, []}, {some, [A]}]}, Heap, V);
|
|
||||||
from_binary(Visited, {variant, Cons}, Heap, V) ->
|
|
||||||
Tag = heap_word(Heap, V),
|
|
||||||
Args = lists:nth(Tag + 1, Cons),
|
|
||||||
Visited1 = Visited#{V => true},
|
|
||||||
{variant, Tag, tuple_to_list(from_binary(Visited1, {tuple, Args}, Heap, V + 32))};
|
|
||||||
from_binary(Visited, {variant_t, TCons}, Heap, V) -> %% Tagged variants
|
|
||||||
{Tags, Cons} = lists:unzip(TCons),
|
|
||||||
{variant, I, Args} = from_binary(Visited, {variant, Cons}, Heap, V),
|
|
||||||
Tag = lists:nth(I + 1, Tags),
|
|
||||||
case Args of
|
|
||||||
[] -> Tag;
|
|
||||||
_ -> list_to_tuple([Tag | Args])
|
|
||||||
end;
|
|
||||||
from_binary(_Visited, {map, A, B}, Heap, Ptr) ->
|
|
||||||
%% FORMAT: [Size] [KeySize] Key [ValSize] Val .. [KeySize] Key [ValSize] Val
|
|
||||||
Size = heap_word(Heap, Ptr),
|
|
||||||
map_binary_to_value(A, B, Size, Heap, Ptr + 32);
|
|
||||||
from_binary(Visited, typerep, Heap, V) ->
|
|
||||||
check_circular_refs(Visited, V),
|
|
||||||
Tag = heap_word(Heap, V),
|
|
||||||
Arg1 = fun(T, I) -> from_binary(Visited#{V => true}, T, Heap, heap_word(Heap, V + 32 * I)) end,
|
|
||||||
Arg = fun(T) -> Arg1(T, 1) end,
|
|
||||||
case Tag of
|
|
||||||
?TYPEREP_WORD_TAG -> word;
|
|
||||||
?TYPEREP_STRING_TAG -> string;
|
|
||||||
?TYPEREP_TYPEREP_TAG -> typerep;
|
|
||||||
?TYPEREP_LIST_TAG -> {list, Arg(typerep)};
|
|
||||||
?TYPEREP_TUPLE_TAG -> {tuple, Arg({list, typerep})};
|
|
||||||
?TYPEREP_VARIANT_TAG -> {variant, Arg({list, {list, typerep}})};
|
|
||||||
?TYPEREP_MAP_TAG -> {map, Arg(typerep), Arg1(typerep, 2)};
|
|
||||||
?TYPEREP_FUN_TAG -> function
|
|
||||||
end.
|
|
||||||
|
|
||||||
map_binary_to_value(KeyType, ValType, N, Bin, Ptr) ->
|
|
||||||
%% Avoid looping on bogus sizes
|
|
||||||
MaxN = byte_size(Bin) div 64,
|
|
||||||
Heap = heap_fragment(Bin),
|
|
||||||
map_from_binary({value, KeyType, ValType}, min(N, MaxN), Heap, Ptr, #{}).
|
|
||||||
|
|
||||||
map_from_binary(_, 0, _, _, Map) -> Map;
|
|
||||||
map_from_binary({value, KeyType, ValType} = Output, I, Heap, Ptr, Map) ->
|
|
||||||
KeySize = get_word(Heap, Ptr),
|
|
||||||
KeyPtr = Ptr + 32,
|
|
||||||
KeyBin = get_chunk(Heap, KeyPtr, KeySize),
|
|
||||||
ValSize = get_word(Heap, KeyPtr + KeySize),
|
|
||||||
ValPtr = KeyPtr + KeySize + 32,
|
|
||||||
ValBin = get_chunk(Heap, ValPtr, ValSize),
|
|
||||||
%% Keys and values are self contained binaries
|
|
||||||
{ok, Key} = from_binary(KeyType, KeyBin),
|
|
||||||
{ok, Val} = from_binary(ValType, ValBin),
|
|
||||||
map_from_binary(Output, I - 1, Heap, ValPtr + ValSize, Map#{Key => Val}).
|
|
||||||
|
|
||||||
check_circular_refs(Visited, V) ->
|
|
||||||
case maps:is_key(V, Visited) of
|
|
||||||
true -> exit(circular_references);
|
|
||||||
false -> ok
|
|
||||||
end.
|
|
||||||
|
|
||||||
heap_word(Heap, Addr) when is_binary(Heap) ->
|
|
||||||
BitSize = 8*Addr,
|
|
||||||
<<_:BitSize,W:256,_/binary>> = Heap,
|
|
||||||
W;
|
|
||||||
heap_word(Heap, Addr) when is_map(Heap) ->
|
|
||||||
0 = Addr rem 32, %% Check that it's word aligned.
|
|
||||||
maps:get(Addr, Heap, 0).
|
|
||||||
|
|
||||||
get_word(#heap{offset = Offs, heap = Mem}, Addr) when Addr >= Offs ->
|
|
||||||
get_word(Mem, Addr - Offs);
|
|
||||||
get_word(Mem, Addr) when is_binary(Mem) ->
|
|
||||||
<<_:Addr/unit:8, Word:256, _/binary>> = Mem,
|
|
||||||
Word.
|
|
||||||
|
|
||||||
get_chunk(#heap{offset = Offs, heap = Mem}, Addr, Bytes) when Addr >= Offs ->
|
|
||||||
get_chunk(Mem, Addr - Offs, Bytes);
|
|
||||||
get_chunk(Mem, Addr, Bytes) when is_binary(Mem) ->
|
|
||||||
<<_:Addr/unit:8, Chunk:Bytes/binary, _/binary>> = Mem,
|
|
||||||
Chunk.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+5
-5
@@ -25,14 +25,14 @@
|
|||||||
|
|
||||||
-include("aeso_icode.hrl").
|
-include("aeso_icode.hrl").
|
||||||
|
|
||||||
-type type_def() :: fun(([aeso_sophia:type()]) -> aeso_sophia:type()).
|
-type type_def() :: fun(([aeb_aevm_data:type()]) -> aeb_aevm_data:type()).
|
||||||
|
|
||||||
-type bindings() :: any().
|
-type bindings() :: any().
|
||||||
-type fun_dec() :: { string()
|
-type fun_dec() :: { string()
|
||||||
, [modifier()]
|
, [modifier()]
|
||||||
, arg_list()
|
, arg_list()
|
||||||
, expr()
|
, expr()
|
||||||
, aeso_sophia:type()}.
|
, aeb_aevm_data:type()}.
|
||||||
|
|
||||||
-type modifier() :: private | stateful.
|
-type modifier() :: private | stateful.
|
||||||
|
|
||||||
@@ -42,10 +42,10 @@
|
|||||||
, functions => [fun_dec()]
|
, functions => [fun_dec()]
|
||||||
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
|
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
|
||||||
, env => [bindings()]
|
, env => [bindings()]
|
||||||
, state_type => aeso_sophia:type()
|
, state_type => aeb_aevm_data:type()
|
||||||
, event_type => aeso_sophia:type()
|
, event_type => aeb_aevm_data:type()
|
||||||
, types => #{ type_name() => type_def() }
|
, types => #{ type_name() => type_def() }
|
||||||
, type_vars => #{ string() => aeso_sophia:type() }
|
, type_vars => #{ string() => aeb_aevm_data:type() }
|
||||||
, constructors => #{ [string()] => integer() } %% name to tag
|
, constructors => #{ [string()] => integer() } %% name to tag
|
||||||
, options => [any()]
|
, options => [any()]
|
||||||
}.
|
}.
|
||||||
|
|||||||
+1
-10
@@ -1,14 +1,5 @@
|
|||||||
|
|
||||||
-define(Type(), aeso_sophia:type()).
|
-include_lib("aebytecode/include/aeb_typerep_def.hrl").
|
||||||
|
|
||||||
-define(TYPEREP_WORD_TAG, 0).
|
|
||||||
-define(TYPEREP_STRING_TAG, 1).
|
|
||||||
-define(TYPEREP_LIST_TAG, 2).
|
|
||||||
-define(TYPEREP_TUPLE_TAG, 3).
|
|
||||||
-define(TYPEREP_VARIANT_TAG, 4).
|
|
||||||
-define(TYPEREP_TYPEREP_TAG, 5).
|
|
||||||
-define(TYPEREP_MAP_TAG, 6).
|
|
||||||
-define(TYPEREP_FUN_TAG, 7).
|
|
||||||
|
|
||||||
-record(arg, {name::string(), type::?Type()}).
|
-record(arg, {name::string(), type::?Type()}).
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ make_args(Args) ->
|
|||||||
|
|
||||||
fun_hash({FName, _, Args, _, TypeRep}) ->
|
fun_hash({FName, _, Args, _, TypeRep}) ->
|
||||||
ArgType = {tuple, [T || {_, T} <- Args]},
|
ArgType = {tuple, [T || {_, T} <- Args]},
|
||||||
<<Hash:256>> = aeso_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
|
<<Hash:256>> = aeb_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
|
||||||
{integer, Hash}.
|
{integer, Hash}.
|
||||||
|
|
||||||
%% Expects two return addresses below N elements on the stack. Picks the top
|
%% Expects two return addresses below N elements on the stack. Picks the top
|
||||||
@@ -343,6 +343,8 @@ assemble_expr(Funs, Stack, _Tail, #prim_put{ state = State }) ->
|
|||||||
%% Environment primitives
|
%% Environment primitives
|
||||||
assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) ->
|
assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) ->
|
||||||
[i(?ADDRESS)];
|
[i(?ADDRESS)];
|
||||||
|
assemble_expr(_Funs, _Stack, _Tail, prim_contract_creator) ->
|
||||||
|
[i(?CREATOR)];
|
||||||
assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) ->
|
assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) ->
|
||||||
[i(?ORIGIN)];
|
[i(?ORIGIN)];
|
||||||
assemble_expr(_Funs, _Stack, _Tail, prim_caller) ->
|
assemble_expr(_Funs, _Stack, _Tail, prim_caller) ->
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
%%%-------------------------------------------------------------------
|
|
||||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
|
||||||
%%% @doc
|
|
||||||
%%% Memory speifics that compiler and VM need to agree upon
|
|
||||||
%%% @end
|
|
||||||
%%% Created : 19 Dec 2018
|
|
||||||
%%%-------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(aeso_memory).
|
|
||||||
|
|
||||||
-export([binary_to_words/1]).
|
|
||||||
|
|
||||||
binary_to_words(<<>>) ->
|
|
||||||
[];
|
|
||||||
binary_to_words(<<N:256,Bin/binary>>) ->
|
|
||||||
[N|binary_to_words(Bin)];
|
|
||||||
binary_to_words(Bin) ->
|
|
||||||
binary_to_words(<<Bin/binary,0>>).
|
|
||||||
|
|
||||||
+31
-11
@@ -99,9 +99,7 @@ con_arg() -> choice(type(), ?RULE(keyword(indexed), type(), set_ann(indexed,
|
|||||||
%% -- Let declarations -------------------------------------------------------
|
%% -- Let declarations -------------------------------------------------------
|
||||||
|
|
||||||
letdecl() ->
|
letdecl() ->
|
||||||
choice(
|
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)).
|
||||||
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)),
|
|
||||||
?RULE(keyword('let'), tok(rec), sep1(letdef(), tok('and')), {letrec, _1, _3})).
|
|
||||||
|
|
||||||
letdef() -> choice(valdef(), fundef()).
|
letdef() -> choice(valdef(), fundef()).
|
||||||
|
|
||||||
@@ -136,11 +134,15 @@ type200() ->
|
|||||||
type300() -> type400().
|
type300() -> type400().
|
||||||
|
|
||||||
type400() ->
|
type400() ->
|
||||||
?RULE(typeAtom(), optional(type_args()),
|
choice(
|
||||||
|
[?RULE(typeAtom(), optional(type_args()),
|
||||||
case _2 of
|
case _2 of
|
||||||
none -> _1;
|
none -> _1;
|
||||||
{ok, Args} -> {app_t, get_ann(_1), _1, Args}
|
{ok, Args} -> {app_t, get_ann(_1), _1, Args}
|
||||||
end).
|
end),
|
||||||
|
?RULE(id("bytes"), parens(token(int)),
|
||||||
|
{bytes_t, get_ann(_1), element(3, _2)})
|
||||||
|
]).
|
||||||
|
|
||||||
typeAtom() ->
|
typeAtom() ->
|
||||||
?LAZY_P(choice(
|
?LAZY_P(choice(
|
||||||
@@ -203,8 +205,8 @@ exprAtom() ->
|
|||||||
?LAZY_P(begin
|
?LAZY_P(begin
|
||||||
Expr = ?LAZY_P(expr()),
|
Expr = ?LAZY_P(expr()),
|
||||||
choice(
|
choice(
|
||||||
[ id(), con(), token(qid), token(qcon)
|
[ id_or_addr(), con(), token(qid), token(qcon)
|
||||||
, token(hash), token(string), token(char)
|
, token(bytes), token(string), token(char)
|
||||||
, token(int)
|
, token(int)
|
||||||
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
|
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
|
||||||
, {bool, keyword(true), true}
|
, {bool, keyword(true), true}
|
||||||
@@ -326,6 +328,26 @@ token(Tag) ->
|
|||||||
{Tok, {Line, Col}, Val} -> {Tok, pos_ann(Line, Col), Val}
|
{Tok, {Line, Col}, Val} -> {Tok, pos_ann(Line, Col), Val}
|
||||||
end).
|
end).
|
||||||
|
|
||||||
|
id(Id) ->
|
||||||
|
?LET_P({id, A, X} = Y, id(),
|
||||||
|
if X == Id -> Y;
|
||||||
|
true -> fail({A, "expected 'bytes'"})
|
||||||
|
end).
|
||||||
|
|
||||||
|
id_or_addr() ->
|
||||||
|
?RULE(id(), parse_addr_literal(_1)).
|
||||||
|
|
||||||
|
parse_addr_literal(Id = {id, Ann, Name}) ->
|
||||||
|
case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_"]) of
|
||||||
|
false -> Id;
|
||||||
|
true ->
|
||||||
|
try aeser_api_encoder:decode(list_to_binary(Name)) of
|
||||||
|
{Type, Bin} -> {Type, Ann, Bin}
|
||||||
|
catch _:_ ->
|
||||||
|
Id
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
%% -- Helpers ----------------------------------------------------------------
|
%% -- Helpers ----------------------------------------------------------------
|
||||||
|
|
||||||
keyword(K) -> ann(tok(K)).
|
keyword(K) -> ann(tok(K)).
|
||||||
@@ -417,7 +439,7 @@ build_if(Ann, Cond, Then, [{elif, Ann1, Cond1, Then1} | Elses]) ->
|
|||||||
build_if(Ann, Cond, Then, [{else, _Ann, Else}]) ->
|
build_if(Ann, Cond, Then, [{else, _Ann, Else}]) ->
|
||||||
{'if', Ann, Cond, Then, Else};
|
{'if', Ann, Cond, Then, Else};
|
||||||
build_if(Ann, Cond, Then, []) ->
|
build_if(Ann, Cond, Then, []) ->
|
||||||
{'if', Ann, Cond, Then, {unit, [{origin, system}]}}.
|
{'if', Ann, Cond, Then, {tuple, [{origin, system}], []}}.
|
||||||
|
|
||||||
else_branches([Elif = {elif, _, _, _} | Stmts], Acc) ->
|
else_branches([Elif = {elif, _, _, _} | Stmts], Acc) ->
|
||||||
else_branches(Stmts, [Elif | Acc]);
|
else_branches(Stmts, [Elif | Acc]);
|
||||||
@@ -433,7 +455,6 @@ fun_t(Domains, Type) ->
|
|||||||
lists:foldr(fun({Dom, Ann}, T) -> {fun_t, Ann, [], Dom, T} end,
|
lists:foldr(fun({Dom, Ann}, T) -> {fun_t, Ann, [], Dom, T} end,
|
||||||
Type, Domains).
|
Type, Domains).
|
||||||
|
|
||||||
tuple_e(Ann, []) -> {unit, Ann};
|
|
||||||
tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple
|
tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple
|
||||||
tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
|
tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
|
||||||
|
|
||||||
@@ -454,10 +475,9 @@ parse_pattern({record, Ann, Fs}) ->
|
|||||||
{record, Ann, lists:map(fun parse_field_pattern/1, Fs)};
|
{record, Ann, lists:map(fun parse_field_pattern/1, Fs)};
|
||||||
parse_pattern(E = {con, _, _}) -> E;
|
parse_pattern(E = {con, _, _}) -> E;
|
||||||
parse_pattern(E = {id, _, _}) -> E;
|
parse_pattern(E = {id, _, _}) -> E;
|
||||||
parse_pattern(E = {unit, _}) -> E;
|
|
||||||
parse_pattern(E = {int, _, _}) -> E;
|
parse_pattern(E = {int, _, _}) -> E;
|
||||||
parse_pattern(E = {bool, _, _}) -> E;
|
parse_pattern(E = {bool, _, _}) -> E;
|
||||||
parse_pattern(E = {hash, _, _}) -> E;
|
parse_pattern(E = {bytes, _, _}) -> E;
|
||||||
parse_pattern(E = {string, _, _}) -> E;
|
parse_pattern(E = {string, _, _}) -> E;
|
||||||
parse_pattern(E = {char, _, _}) -> E;
|
parse_pattern(E = {char, _, _}) -> E;
|
||||||
parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
|
parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
|
||||||
|
|||||||
+14
-8
@@ -160,8 +160,7 @@ decl(D = {letfun, Attrs, _, _, _, _}) ->
|
|||||||
text(atom_to_list(Mod));
|
text(atom_to_list(Mod));
|
||||||
(_) -> empty() end,
|
(_) -> empty() end,
|
||||||
hsep(lists:map(Mod, Attrs) ++ [letdecl("function", D)]);
|
hsep(lists:map(Mod, Attrs) ++ [letdecl("function", D)]);
|
||||||
decl(D = {letval, _, _, _, _}) -> letdecl("let", D);
|
decl(D = {letval, _, _, _, _}) -> letdecl("let", D).
|
||||||
decl(D = {letrec, _, _}) -> letdecl("let", D).
|
|
||||||
|
|
||||||
-spec expr(aeso_syntax:expr(), options()) -> doc().
|
-spec expr(aeso_syntax:expr(), options()) -> doc().
|
||||||
expr(E, Options) ->
|
expr(E, Options) ->
|
||||||
@@ -184,9 +183,7 @@ name({typed, _, Name, _}) -> name(Name).
|
|||||||
letdecl(Let, {letval, _, F, T, E}) ->
|
letdecl(Let, {letval, _, F, T, E}) ->
|
||||||
block_expr(0, hsep([text(Let), typed(name(F), T), text("=")]), E);
|
block_expr(0, hsep([text(Let), typed(name(F), T), text("=")]), E);
|
||||||
letdecl(Let, {letfun, _, F, Args, T, E}) ->
|
letdecl(Let, {letfun, _, F, Args, T, E}) ->
|
||||||
block_expr(0, hsep([text(Let), typed(beside(name(F), args(Args)), T), text("=")]), E);
|
block_expr(0, hsep([text(Let), typed(beside(name(F), args(Args)), T), text("=")]), E).
|
||||||
letdecl(Let, {letrec, _, [D | Ds]}) ->
|
|
||||||
hsep(text(Let), above([ letdecl("rec", D) | [ letdecl("and", D1) || D1 <- Ds ] ])).
|
|
||||||
|
|
||||||
-spec args([aeso_syntax:arg()]) -> doc().
|
-spec args([aeso_syntax:arg()]) -> doc().
|
||||||
args(Args) ->
|
args(Args) ->
|
||||||
@@ -236,6 +233,8 @@ type({app_t, _, Type, Args}) ->
|
|||||||
beside(type(Type), tuple_type(Args));
|
beside(type(Type), tuple_type(Args));
|
||||||
type({tuple_t, _, Args}) ->
|
type({tuple_t, _, Args}) ->
|
||||||
tuple_type(Args);
|
tuple_type(Args);
|
||||||
|
type({bytes_t, _, Len}) ->
|
||||||
|
text(lists:concat(["bytes(", Len, ")"]));
|
||||||
type({named_arg_t, _, Name, Type, _Default}) ->
|
type({named_arg_t, _, Name, Type, _Default}) ->
|
||||||
%% Drop the default value
|
%% Drop the default value
|
||||||
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
|
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
|
||||||
@@ -319,9 +318,17 @@ expr_p(_, E = {int, _, N}) ->
|
|||||||
end,
|
end,
|
||||||
text(S);
|
text(S);
|
||||||
expr_p(_, {bool, _, B}) -> text(atom_to_list(B));
|
expr_p(_, {bool, _, B}) -> text(atom_to_list(B));
|
||||||
expr_p(_, {hash, _, <<N:256>>}) -> text("#" ++ integer_to_list(N, 16));
|
expr_p(_, {bytes, _, Bin}) ->
|
||||||
|
Digits = byte_size(Bin),
|
||||||
|
<<N:Digits/unit:8>> = Bin,
|
||||||
|
text(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N])));
|
||||||
expr_p(_, {hash, _, <<N:512>>}) -> text("#" ++ integer_to_list(N, 16));
|
expr_p(_, {hash, _, <<N:512>>}) -> text("#" ++ integer_to_list(N, 16));
|
||||||
expr_p(_, {unit, _}) -> text("()");
|
expr_p(_, {Type, _, Bin})
|
||||||
|
when Type == account_pubkey;
|
||||||
|
Type == contract_pubkey;
|
||||||
|
Type == oracle_pubkey;
|
||||||
|
Type == oracle_query_id ->
|
||||||
|
text(binary_to_list(aeser_api_encoder:encode(Type, Bin)));
|
||||||
expr_p(_, {string, _, S}) -> term(binary_to_list(S));
|
expr_p(_, {string, _, S}) -> term(binary_to_list(S));
|
||||||
expr_p(_, {char, _, C}) ->
|
expr_p(_, {char, _, C}) ->
|
||||||
case C of
|
case C of
|
||||||
@@ -425,7 +432,6 @@ statements(Stmts) ->
|
|||||||
|
|
||||||
statement(S = {letval, _, _, _, _}) -> letdecl("let", S);
|
statement(S = {letval, _, _, _, _}) -> letdecl("let", S);
|
||||||
statement(S = {letfun, _, _, _, _, _}) -> letdecl("let", S);
|
statement(S = {letfun, _, _, _, _, _}) -> letdecl("let", S);
|
||||||
statement(S = {letrec, _, _}) -> letdecl("let", S);
|
|
||||||
statement(E) -> expr(E).
|
statement(E) -> expr(E).
|
||||||
|
|
||||||
get_elifs(Expr) -> get_elifs(Expr, []).
|
get_elifs(Expr) -> get_elifs(Expr, []).
|
||||||
|
|||||||
+7
-9
@@ -20,7 +20,7 @@ lexer() ->
|
|||||||
CON = [UPPER, "[a-zA-Z0-9_]*"],
|
CON = [UPPER, "[a-zA-Z0-9_]*"],
|
||||||
INT = [DIGIT, "+"],
|
INT = [DIGIT, "+"],
|
||||||
HEX = ["0x", HEXDIGIT, "+"],
|
HEX = ["0x", HEXDIGIT, "+"],
|
||||||
HASH = ["#", HEXDIGIT, "+"],
|
BYTES = ["#", HEXDIGIT, "+"],
|
||||||
WS = "[\\000-\\ ]+",
|
WS = "[\\000-\\ ]+",
|
||||||
ID = [LOWER, "[a-zA-Z0-9_']*"],
|
ID = [LOWER, "[a-zA-Z0-9_']*"],
|
||||||
TVAR = ["'", ID],
|
TVAR = ["'", ID],
|
||||||
@@ -36,8 +36,8 @@ lexer() ->
|
|||||||
, {"\\*/", pop(skip())}
|
, {"\\*/", pop(skip())}
|
||||||
, {"[^/*]+|[/*]", skip()} ],
|
, {"[^/*]+|[/*]", skip()} ],
|
||||||
|
|
||||||
Keywords = ["contract", "include", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
||||||
"stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "namespace"],
|
"stateful", "true", "false", "mod", "public", "private", "indexed", "internal", "namespace"],
|
||||||
KW = string:join(Keywords, "|"),
|
KW = string:join(Keywords, "|"),
|
||||||
|
|
||||||
Rules =
|
Rules =
|
||||||
@@ -54,7 +54,7 @@ lexer() ->
|
|||||||
, {STRING, token(string, fun parse_string/1)}
|
, {STRING, token(string, fun parse_string/1)}
|
||||||
, {HEX, token(hex, fun parse_hex/1)}
|
, {HEX, token(hex, fun parse_hex/1)}
|
||||||
, {INT, token(int, fun list_to_integer/1)}
|
, {INT, token(int, fun list_to_integer/1)}
|
||||||
, {HASH, token(hash, fun parse_hash/1)}
|
, {BYTES, token(bytes, fun parse_bytes/1)}
|
||||||
|
|
||||||
%% Identifiers (qualified first!)
|
%% Identifiers (qualified first!)
|
||||||
, {QID, token(qid, fun(S) -> string:tokens(S, ".") end)}
|
, {QID, token(qid, fun(S) -> string:tokens(S, ".") end)}
|
||||||
@@ -117,10 +117,8 @@ unescape([C | Chars], Acc) ->
|
|||||||
|
|
||||||
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
|
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
|
||||||
|
|
||||||
parse_hash("#" ++ Chars) ->
|
parse_bytes("#" ++ Chars) ->
|
||||||
N = list_to_integer(Chars, 16),
|
N = list_to_integer(Chars, 16),
|
||||||
case length(Chars) > 64 of %% 64 hex digits = 32 bytes
|
Digits = (length(Chars) + 1) div 2,
|
||||||
true -> <<N:64/unit:8>>; %% signature
|
<<N:Digits/unit:8>>.
|
||||||
false -> <<N:32/unit:8>> %% address
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
-module(aeso_sophia).
|
|
||||||
|
|
||||||
-export_type([data/0,
|
|
||||||
type/0,
|
|
||||||
heap/0]).
|
|
||||||
|
|
||||||
-type type() :: word | signed_word | string | typerep | function
|
|
||||||
| {list, type()}
|
|
||||||
| {option, type()}
|
|
||||||
| {tuple, [type()]}
|
|
||||||
| {variant, [[type()]]}.
|
|
||||||
|
|
||||||
|
|
||||||
-type data() :: none
|
|
||||||
| {some, data()}
|
|
||||||
| {option, data()}
|
|
||||||
| word
|
|
||||||
| string
|
|
||||||
| {list, data()}
|
|
||||||
| {tuple, [data()]}
|
|
||||||
| {variant, integer(), [data()]}
|
|
||||||
| integer()
|
|
||||||
| binary()
|
|
||||||
| [data()]
|
|
||||||
| {}
|
|
||||||
| {data()}
|
|
||||||
| {data(), data()}.
|
|
||||||
|
|
||||||
-type heap() :: binary().
|
|
||||||
|
|
||||||
+7
-4
@@ -25,7 +25,7 @@
|
|||||||
-type ann_origin() :: system | user.
|
-type ann_origin() :: system | user.
|
||||||
-type ann_format() :: '?:' | hex | infix | prefix | elif.
|
-type ann_format() :: '?:' | hex | infix | prefix | elif.
|
||||||
|
|
||||||
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}].
|
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} | stateful | private].
|
||||||
|
|
||||||
-type name() :: string().
|
-type name() :: string().
|
||||||
-type id() :: {id, ann(), name()}.
|
-type id() :: {id, ann(), name()}.
|
||||||
@@ -43,8 +43,7 @@
|
|||||||
|
|
||||||
-type letbind()
|
-type letbind()
|
||||||
:: {letval, ann(), id(), type(), expr()}
|
:: {letval, ann(), id(), type(), expr()}
|
||||||
| {letfun, ann(), id(), [arg()], type(), expr()}
|
| {letfun, ann(), id(), [arg()], type(), expr()}.
|
||||||
| {letrec, ann(), [letbind()]}.
|
|
||||||
|
|
||||||
-type arg() :: {arg, ann(), id(), type()}.
|
-type arg() :: {arg, ann(), id(), type()}.
|
||||||
|
|
||||||
@@ -60,6 +59,7 @@
|
|||||||
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
|
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
|
||||||
| {app_t, ann(), type(), [type()]}
|
| {app_t, ann(), type(), [type()]}
|
||||||
| {tuple_t, ann(), [type()]}
|
| {tuple_t, ann(), [type()]}
|
||||||
|
| {bytes_t, ann(), integer()}
|
||||||
| id() | qid()
|
| id() | qid()
|
||||||
| con() | qcon() %% contracts
|
| con() | qcon() %% contracts
|
||||||
| tvar().
|
| tvar().
|
||||||
@@ -70,7 +70,10 @@
|
|||||||
:: {int, ann(), integer()}
|
:: {int, ann(), integer()}
|
||||||
| {bool, ann(), true | false}
|
| {bool, ann(), true | false}
|
||||||
| {hash, ann(), binary()}
|
| {hash, ann(), binary()}
|
||||||
| {unit, ann()}
|
| {account_pubkey, binary()}
|
||||||
|
| {contract_pubkey, binary()}
|
||||||
|
| {oracle_pubkey, binary()}
|
||||||
|
| {oracle_query_id, binary()}
|
||||||
| {string, ann(), binary()}
|
| {string, ann(), binary()}
|
||||||
| {char, ann(), integer()}.
|
| {char, ann(), integer()}.
|
||||||
|
|
||||||
|
|||||||
+18
-22
@@ -39,11 +39,6 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
|||||||
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
|
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
|
||||||
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
|
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
|
||||||
Top = Fun(K, X),
|
Top = Fun(K, X),
|
||||||
Bound = fun LB ({letval, _, Y, _, _}) -> BindExpr(Y);
|
|
||||||
LB ({letfun, _, F, _, _, _}) -> BindExpr(F);
|
|
||||||
LB ({letrec, _, Ds}) -> Sum(lists:map(LB, Ds));
|
|
||||||
LB (_) -> Zero
|
|
||||||
end,
|
|
||||||
Rec = case X of
|
Rec = case X of
|
||||||
%% lists (bound things in head scope over tail)
|
%% lists (bound things in head scope over tail)
|
||||||
[A | As] -> Scoped(Same(A), Same(As));
|
[A | As] -> Scoped(Same(A), Same(As));
|
||||||
@@ -55,7 +50,6 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
|||||||
{fun_decl, _, _, T} -> Type(T);
|
{fun_decl, _, _, T} -> Type(T);
|
||||||
{letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]);
|
{letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]);
|
||||||
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Scoped(BindExpr(Xs), Expr(E))]);
|
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Scoped(BindExpr(Xs), Expr(E))]);
|
||||||
{letrec, _, Ds} -> Plus(Bound(Ds), Decl(Ds));
|
|
||||||
%% typedef()
|
%% typedef()
|
||||||
{alias_t, T} -> Type(T);
|
{alias_t, T} -> Type(T);
|
||||||
{record_t, Fs} -> Type(Fs);
|
{record_t, Fs} -> Type(Fs);
|
||||||
@@ -104,29 +98,31 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
|||||||
%% Name dependencies
|
%% Name dependencies
|
||||||
|
|
||||||
used_ids(E) ->
|
used_ids(E) ->
|
||||||
[ X || {term, [X]} <- used(E) ].
|
[ X || {{term, [X]}, _} <- used(E) ].
|
||||||
|
|
||||||
used_types(T) ->
|
used_types(T) ->
|
||||||
[ X || {type, [X]} <- used(T) ].
|
[ X || {{type, [X]}, _} <- used(T) ].
|
||||||
|
|
||||||
-type entity() :: {term, [string()]}
|
-type entity() :: {term, [string()]}
|
||||||
| {type, [string()]}
|
| {type, [string()]}
|
||||||
| {namespace, [string()]}.
|
| {namespace, [string()]}.
|
||||||
|
|
||||||
-spec entity_alg() -> alg([entity()]).
|
-spec entity_alg() -> alg(#{entity() => aeso_syntax:ann()}).
|
||||||
entity_alg() ->
|
entity_alg() ->
|
||||||
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
|
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
|
||||||
Unbind = fun(bound_term) -> term; (bound_type) -> type end,
|
Unbind = fun(bound_term) -> term; (bound_type) -> type end,
|
||||||
|
Remove = fun(Keys, Map) -> lists:foldl(fun maps:remove/2, Map, Keys) end,
|
||||||
Scoped = fun(Xs, Ys) ->
|
Scoped = fun(Xs, Ys) ->
|
||||||
{Bound, Others} = lists:partition(IsBound, Ys),
|
Bound = [E || E <- maps:keys(Ys), IsBound(E)],
|
||||||
|
Others = Remove(Bound, Ys),
|
||||||
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
|
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
|
||||||
lists:umerge(Xs -- Bound1, Others)
|
maps:merge(Remove(Bound1, Xs), Others)
|
||||||
end,
|
end,
|
||||||
#alg{ zero = []
|
#alg{ zero = #{}
|
||||||
, plus = fun lists:umerge/2
|
, plus = fun maps:merge/2
|
||||||
, scoped = Scoped }.
|
, scoped = Scoped }.
|
||||||
|
|
||||||
-spec used(_) -> [entity()].
|
-spec used(_) -> [{entity(), aeso_syntax:ann()}].
|
||||||
used(D) ->
|
used(D) ->
|
||||||
Kind = fun(expr) -> term;
|
Kind = fun(expr) -> term;
|
||||||
(bind_expr) -> bound_term;
|
(bind_expr) -> bound_term;
|
||||||
@@ -134,14 +130,14 @@ used(D) ->
|
|||||||
(bind_type) -> bound_type
|
(bind_type) -> bound_type
|
||||||
end,
|
end,
|
||||||
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
|
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
|
||||||
NotBound = fun({Tag, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
|
NotBound = fun({{Tag, _}, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
|
||||||
Xs =
|
Xs =
|
||||||
fold(entity_alg(),
|
maps:to_list(fold(entity_alg(),
|
||||||
fun(K, {id, _, X}) -> [{Kind(K), [X]}];
|
fun(K, {id, Ann, X}) -> #{{Kind(K), [X]} => Ann};
|
||||||
(K, {qid, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
|
(K, {qid, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
|
||||||
(K, {con, _, X}) -> [{Kind(K), [X]}];
|
(K, {con, Ann, X}) -> #{{Kind(K), [X]} => Ann};
|
||||||
(K, {qcon, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
|
(K, {qcon, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
|
||||||
(_, _) -> []
|
(_, _) -> #{}
|
||||||
end, decl, D),
|
end, decl, D)),
|
||||||
lists:filter(NotBound, Xs).
|
lists:filter(NotBound, Xs).
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{application, aesophia,
|
{application, aesophia,
|
||||||
[{description, "Contract Language for Aethernity"},
|
[{description, "Contract Language for aeternity"},
|
||||||
{vsn, {cmd, "cat VERSION | tr -d '[:space:]'"}},
|
{vsn, "3.0.0"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications,
|
{applications,
|
||||||
[kernel,
|
[kernel,
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
-module(aesophia).
|
|
||||||
|
|
||||||
-export([main/1]).
|
|
||||||
|
|
||||||
-define(OPT_SPEC,
|
|
||||||
[ {src_file, undefined, undefined, string, "Sophia source code file"}
|
|
||||||
, {version, $V, "version", undefined, "Print compiler version"}
|
|
||||||
, {verbose, $v, "verbose", undefined, "Verbose output"}
|
|
||||||
, {help, $h, "help", undefined, "Show this message"}
|
|
||||||
, {outfile, $o, "out", string, "Output file (experimental)"} ]).
|
|
||||||
|
|
||||||
usage() ->
|
|
||||||
getopt:usage(?OPT_SPEC, "aesophia").
|
|
||||||
|
|
||||||
main(Args) ->
|
|
||||||
case getopt:parse(?OPT_SPEC, Args) of
|
|
||||||
{ok, {Opts, []}} ->
|
|
||||||
case Opts of
|
|
||||||
[version] ->
|
|
||||||
print_vsn();
|
|
||||||
[help] ->
|
|
||||||
usage();
|
|
||||||
_ ->
|
|
||||||
compile(Opts)
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ok, {_, NonOpts}} ->
|
|
||||||
io:format("Can't understand ~p\n\n", [NonOpts]),
|
|
||||||
usage();
|
|
||||||
|
|
||||||
{error, {Reason, Data}} ->
|
|
||||||
io:format("Error: ~s ~p\n\n", [Reason, Data]),
|
|
||||||
usage()
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
compile(Opts) ->
|
|
||||||
case proplists:get_value(src_file, Opts, undefined) of
|
|
||||||
undefined ->
|
|
||||||
io:format("Error: no input source file\n\n"),
|
|
||||||
usage();
|
|
||||||
File ->
|
|
||||||
compile(File, Opts)
|
|
||||||
end.
|
|
||||||
|
|
||||||
compile(File, Opts) ->
|
|
||||||
Verbose = proplists:get_value(verbose, Opts, false),
|
|
||||||
OutFile = proplists:get_value(outfile, Opts, undefined),
|
|
||||||
|
|
||||||
try
|
|
||||||
Res = aeso_compiler:file(File, [pp_ast || Verbose]),
|
|
||||||
write_outfile(OutFile, Res),
|
|
||||||
io:format("\nCompiled successfully!\n")
|
|
||||||
catch
|
|
||||||
%% The compiler errors.
|
|
||||||
error:{type_errors, Errors} ->
|
|
||||||
io:format("\n~s\n", [string:join(["** Type errors\n" | Errors], "\n")]);
|
|
||||||
error:{parse_errors, Errors} ->
|
|
||||||
io:format("\n~s\n", [string:join(["** Parse errors\n" | Errors], "\n")]);
|
|
||||||
error:{code_errors, Errors} ->
|
|
||||||
ErrorStrings = [ io_lib:format("~p", [E]) || E <- Errors ],
|
|
||||||
io:format("\n~s\n", [string:join(["** Code errors\n" | ErrorStrings], "\n")]);
|
|
||||||
%% General programming errors in the compiler.
|
|
||||||
error:Error ->
|
|
||||||
Where = hd(erlang:get_stacktrace()),
|
|
||||||
ErrorString = io_lib:format("Error: ~p in\n ~p", [Error,Where]),
|
|
||||||
io:format("\n~s\n", [ErrorString])
|
|
||||||
end.
|
|
||||||
|
|
||||||
write_outfile(undefined, _) -> ok;
|
|
||||||
write_outfile(Out, ResMap) ->
|
|
||||||
%% Lazy approach
|
|
||||||
file:write_file(Out, term_to_binary(ResMap)),
|
|
||||||
io:format("Output written to: ~s\n", [Out]).
|
|
||||||
|
|
||||||
print_vsn() ->
|
|
||||||
{ok, Vsn} = aeso_compiler:version(),
|
|
||||||
io:format("Compiler version: ~s\n", [Vsn]).
|
|
||||||
+32
-9
@@ -4,6 +4,9 @@
|
|||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
|
|
||||||
-define(SANDBOX(Code), sandbox(fun() -> Code end)).
|
-define(SANDBOX(Code), sandbox(fun() -> Code end)).
|
||||||
|
-define(DUMMY_HASH_WORD, 16#123).
|
||||||
|
-define(DUMMY_HASH, <<0:30/unit:8, 127, 119>>). %% 16#123
|
||||||
|
-define(DUMMY_HASH_LIT, "#0000000000000000000000000000000000000000000000000000000000000123").
|
||||||
|
|
||||||
sandbox(Code) ->
|
sandbox(Code) ->
|
||||||
Parent = self(),
|
Parent = self(),
|
||||||
@@ -19,8 +22,8 @@ sandbox(Code) ->
|
|||||||
|
|
||||||
malicious_from_binary_test() ->
|
malicious_from_binary_test() ->
|
||||||
CircularList = from_words([32, 1, 32]), %% Xs = 1 :: Xs
|
CircularList = from_words([32, 1, 32]), %% Xs = 1 :: Xs
|
||||||
{ok, {error, circular_references}} = ?SANDBOX(aeso_heap:from_binary({list, word}, CircularList)),
|
{ok, {error, circular_references}} = ?SANDBOX(aeb_heap:from_binary({list, word}, CircularList)),
|
||||||
{ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeso_heap:from_binary(word, <<1, 2, 3, 4>>)),
|
{ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeb_heap:from_binary(word, <<1, 2, 3, 4>>)),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
from_words(Ws) ->
|
from_words(Ws) ->
|
||||||
@@ -97,7 +100,15 @@ calldata_test() ->
|
|||||||
Map = #{ <<"a">> => 4 },
|
Map = #{ <<"a">> => 4 },
|
||||||
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
|
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
|
||||||
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
|
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
|
||||||
[16#123, 16#456] = encode_decode_calldata("foo", ["hash", "address"], ["#123", "#456"]),
|
[?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"],
|
||||||
|
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
|
||||||
|
[?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] =
|
||||||
|
encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]),
|
||||||
|
|
||||||
|
[119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]),
|
||||||
|
|
||||||
|
[16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
calldata_init_test() ->
|
calldata_init_test() ->
|
||||||
@@ -126,7 +137,9 @@ parameterized_contract(FunName, Types) ->
|
|||||||
|
|
||||||
parameterized_contract(ExtraCode, FunName, Types) ->
|
parameterized_contract(ExtraCode, FunName, Types) ->
|
||||||
lists:flatten(
|
lists:flatten(
|
||||||
["contract Dummy =\n",
|
["contract Remote =\n"
|
||||||
|
" function bla : () => ()\n\n"
|
||||||
|
"contract Dummy =\n",
|
||||||
ExtraCode, "\n",
|
ExtraCode, "\n",
|
||||||
" type an_alias('a) = (string, 'a)\n"
|
" type an_alias('a) = (string, 'a)\n"
|
||||||
" record r = {x : an_alias(int), y : variant}\n"
|
" record r = {x : an_alias(int), y : variant}\n"
|
||||||
@@ -139,13 +152,15 @@ oracle_test() ->
|
|||||||
" function question(o, q : oracle_query(list(string), option(int))) =\n"
|
" function question(o, q : oracle_query(list(string), option(int))) =\n"
|
||||||
" Oracle.get_question(o, q)\n",
|
" Oracle.get_question(o, q)\n",
|
||||||
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
|
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
|
||||||
aeso_compiler:check_call(Contract, "question", ["#123", "#456"], []),
|
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
|
||||||
|
"oq_1111111111111111111111111111113AFEFpt5"], []),
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
permissive_literals_fail_test() ->
|
permissive_literals_fail_test() ->
|
||||||
Contract =
|
Contract =
|
||||||
"contract OracleTest =\n"
|
"contract OracleTest =\n"
|
||||||
" function haxx(o : oracle(list(string), option(int))) =\n"
|
" stateful function haxx(o : oracle(list(string), option(int))) =\n"
|
||||||
" Chain.spend(o, 1000000)\n",
|
" Chain.spend(o, 1000000)\n",
|
||||||
{error, <<"Type errors\nCannot unify", _/binary>>} =
|
{error, <<"Type errors\nCannot unify", _/binary>>} =
|
||||||
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
|
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
|
||||||
@@ -161,7 +176,15 @@ encode_decode_calldata(FunName, Types, Args, RetType) ->
|
|||||||
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
|
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
|
||||||
{ok, Calldata, CalldataType, RetVMType1} = aeso_compiler:create_calldata(Code, FunName, Args),
|
{ok, Calldata, CalldataType, RetVMType1} = aeso_compiler:create_calldata(Code, FunName, Args),
|
||||||
?assertEqual(RetVMType1, RetVMType),
|
?assertEqual(RetVMType1, RetVMType),
|
||||||
{ok, {_Hash, ArgTuple}} = aeso_heap:from_binary(CalldataType, Calldata),
|
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
|
||||||
|
case FunName of
|
||||||
|
"init" ->
|
||||||
|
ok;
|
||||||
|
_ ->
|
||||||
|
{ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata),
|
||||||
|
Values = [ prettypr:format(aeso_pretty:expr(V)) || V <- ValueASTs ],
|
||||||
|
?assertMatch({X, X}, {Args, Values})
|
||||||
|
end,
|
||||||
tuple_to_list(ArgTuple).
|
tuple_to_list(ArgTuple).
|
||||||
|
|
||||||
encode_decode(T, D) ->
|
encode_decode(T, D) ->
|
||||||
@@ -169,8 +192,8 @@ encode_decode(T, D) ->
|
|||||||
D.
|
D.
|
||||||
|
|
||||||
encode(D) ->
|
encode(D) ->
|
||||||
aeso_heap:to_binary(D).
|
aeb_heap:to_binary(D).
|
||||||
|
|
||||||
decode(T,B) ->
|
decode(T,B) ->
|
||||||
{ok, D} = aeso_heap:from_binary(T, B),
|
{ok, D} = aeb_heap:from_binary(T, B),
|
||||||
D.
|
D.
|
||||||
|
|||||||
+58
-27
@@ -5,41 +5,72 @@
|
|||||||
|
|
||||||
do_test() ->
|
do_test() ->
|
||||||
test_contract(1),
|
test_contract(1),
|
||||||
test_contract(2).
|
test_contract(2),
|
||||||
|
test_contract(3).
|
||||||
|
|
||||||
test_contract(N) ->
|
test_contract(N) ->
|
||||||
{Contract,DecACI} = test_cases(N),
|
{Contract,MapACI,DecACI} = test_cases(N),
|
||||||
{ok,Enc} = aeso_aci:encode(Contract),
|
{ok,JSON} = aeso_aci:encode(Contract),
|
||||||
?assertEqual(DecACI, jsx:decode(Enc)).
|
?assertEqual(MapACI, jsx:decode(JSON, [return_maps])),
|
||||||
|
?assertEqual(DecACI, aeso_aci:decode(JSON)).
|
||||||
|
|
||||||
test_cases(1) ->
|
test_cases(1) ->
|
||||||
Contract = <<"contract C =\n"
|
Contract = <<"contract C =\n"
|
||||||
" function a(i : int) = i+1\n">>,
|
" function a(i : int) = i+1\n">>,
|
||||||
DecodedACI = [{<<"contract">>,
|
MapACI = #{<<"contract">> =>
|
||||||
[{<<"name">>,<<"C">>},
|
#{<<"name">> => <<"C">>,
|
||||||
{<<"type_defs">>,[]},
|
<<"type_defs">> => [],
|
||||||
{<<"functions">>,
|
<<"functions">> =>
|
||||||
[[{<<"name">>,<<"a">>},
|
[#{<<"name">> => <<"a">>,
|
||||||
{<<"arguments">>,
|
<<"arguments">> =>
|
||||||
[[{<<"name">>,<<"i">>},{<<"type">>,<<"int">>}]]},
|
[#{<<"name">> => <<"i">>,
|
||||||
{<<"type">>,<<"int">>},
|
<<"type">> => [<<"int">>]}],
|
||||||
{<<"stateful">>,false}]]}]}],
|
<<"returns">> => <<"int">>,
|
||||||
{Contract,DecodedACI};
|
<<"stateful">> => false}]}},
|
||||||
|
DecACI = <<"contract C =\n"
|
||||||
|
" function a : (int) => int\n">>,
|
||||||
|
{Contract,MapACI,DecACI};
|
||||||
|
|
||||||
test_cases(2) ->
|
test_cases(2) ->
|
||||||
Contract = <<"contract C =\n"
|
Contract = <<"contract C =\n"
|
||||||
" type allan = int\n"
|
" type allan = int\n"
|
||||||
" function a(i : allan) = i+1\n">>,
|
" function a(i : allan) = i+1\n">>,
|
||||||
DecodedACI = [{<<"contract">>,
|
MapACI = #{<<"contract">> =>
|
||||||
[{<<"name">>,<<"C">>},
|
#{<<"name">> => <<"C">>,
|
||||||
{<<"type_defs">>,
|
<<"type_defs">> =>
|
||||||
[[{<<"name">>,<<"allan">>},
|
[#{<<"name">> => <<"allan">>,
|
||||||
{<<"vars">>,[]},
|
<<"typedef">> => <<"int">>,
|
||||||
{<<"typedef">>,<<"int">>}]]},
|
<<"vars">> => []}],
|
||||||
{<<"functions">>,
|
<<"functions">> =>
|
||||||
[[{<<"name">>,<<"a">>},
|
[#{<<"arguments">> =>
|
||||||
{<<"arguments">>,
|
[#{<<"name">> => <<"i">>,
|
||||||
[[{<<"name">>,<<"i">>},{<<"type">>,<<"int">>}]]},
|
<<"type">> => [<<"int">>]}],
|
||||||
{<<"type">>,<<"int">>},
|
<<"name">> => <<"a">>,
|
||||||
{<<"stateful">>,false}]]}]}],
|
<<"returns">> => <<"int">>,
|
||||||
{Contract,DecodedACI}.
|
<<"stateful">> => false}]}},
|
||||||
|
DecACI = <<"contract C =\n"
|
||||||
|
" function a : (int) => int\n">>,
|
||||||
|
{Contract,MapACI,DecACI};
|
||||||
|
test_cases(3) ->
|
||||||
|
Contract = <<"contract C =\n"
|
||||||
|
" datatype bert('a) = Bin('a)\n"
|
||||||
|
" function a(i : bert(string)) = 1\n">>,
|
||||||
|
MapACI = #{<<"contract">> =>
|
||||||
|
#{<<"functions">> =>
|
||||||
|
[#{<<"arguments">> =>
|
||||||
|
[#{<<"name">> => <<"i">>,
|
||||||
|
<<"type">> =>
|
||||||
|
[#{<<"C.bert">> => [<<"string">>]}]}],
|
||||||
|
<<"name">> => <<"a">>,<<"returns">> => <<"int">>,
|
||||||
|
<<"stateful">> => false}],
|
||||||
|
<<"name">> => <<"C">>,
|
||||||
|
<<"type_defs">> =>
|
||||||
|
[#{<<"name">> => <<"bert">>,
|
||||||
|
<<"typedef">> =>
|
||||||
|
#{<<"variant">> =>
|
||||||
|
[#{<<"Bin">> => [<<"'a">>]}]},
|
||||||
|
<<"vars">> => [#{<<"name">> => <<"'a">>}]}]}},
|
||||||
|
DecACI = <<"contract C =\n"
|
||||||
|
" datatype bert('a) = Bin('a)\n"
|
||||||
|
" function a : (C.bert(string)) => int\n">>,
|
||||||
|
{Contract,MapACI,DecACI}.
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
%%%=============================================================================
|
|
||||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
|
||||||
%%% @doc
|
|
||||||
%%% Unit tests for the aeso_blake2 module
|
|
||||||
%%%
|
|
||||||
%%% In addition the aeso_blake2 module was compared to the C reference
|
|
||||||
%%% implementation by writing a QuickCheck property.
|
|
||||||
%%% @end
|
|
||||||
%%%=============================================================================
|
|
||||||
-module(aeso_blake2_tests).
|
|
||||||
|
|
||||||
-ifdef(TEST).
|
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
|
||||||
|
|
||||||
blake2b_test_() ->
|
|
||||||
{"Tests for BLAKE2b hash implementation",
|
|
||||||
[ fun() -> blake2b(Data) end || Data <- test_data_blake2b() ]}.
|
|
||||||
|
|
||||||
blake2b({Msg0, Key0, ExpectedOut0}) ->
|
|
||||||
Msg = mk_binary(Msg0),
|
|
||||||
Key = mk_binary(Key0),
|
|
||||||
ExpectedOut = mk_binary(ExpectedOut0),
|
|
||||||
Result = aeso_blake2:blake2b(byte_size(ExpectedOut), Msg, Key),
|
|
||||||
?assertEqual(Result, {ok, ExpectedOut}).
|
|
||||||
|
|
||||||
mk_binary(Bin) when is_binary(Bin) -> Bin;
|
|
||||||
mk_binary(HexStr) when is_list(HexStr) ->
|
|
||||||
<< << (erlang:list_to_integer([H], 16)):4 >> || H <- HexStr >>.
|
|
||||||
|
|
||||||
test_data_blake2b() ->
|
|
||||||
[ %% {Message, Key, ExpectedHash}
|
|
||||||
%% From Wikipedia
|
|
||||||
%% https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2
|
|
||||||
{<<>>,
|
|
||||||
<<>>,
|
|
||||||
"786A02F742015903C6C6FD852552D272912F4740E15847618A86E217F71F5419D25E1031AFEE585313896444934EB04B903A685B1448B755D56F701AFE9BE2CE"}
|
|
||||||
, {<<"The quick brown fox jumps over the lazy dog">>,
|
|
||||||
<<>>,
|
|
||||||
"A8ADD4BDDDFD93E4877D2746E62817B116364A1FA7BC148D95090BC7333B3673F82401CF7AA2E4CB1ECD90296E3F14CB5413F8ED77BE73045B13914CDCD6A918"}
|
|
||||||
|
|
||||||
%% From reference implementation testvectors
|
|
||||||
%% https://github.com/BLAKE2/BLAKE2/tree/master/testvectors
|
|
||||||
%%
|
|
||||||
%% Non-keyed
|
|
||||||
, {"00",
|
|
||||||
"",
|
|
||||||
"2FA3F686DF876995167E7C2E5D74C4C7B6E48F8068FE0E44208344D480F7904C36963E44115FE3EB2A3AC8694C28BCB4F5A0F3276F2E79487D8219057A506E4B"}
|
|
||||||
, {"0001",
|
|
||||||
"",
|
|
||||||
"1C08798DC641ABA9DEE435E22519A4729A09B2BFE0FF00EF2DCD8ED6F8A07D15EAF4AEE52BBF18AB5608A6190F70B90486C8A7D4873710B1115D3DEBBB4327B5"}
|
|
||||||
, {"00010203040506070809",
|
|
||||||
"",
|
|
||||||
"29102511D749DB3CC9B4E335FA1F5E8FACA8421D558F6A3F3321D50D044A248BA595CFC3EFD3D2ADC97334DA732413F5CBF4751C362BA1D53862AC1E8DABEEE8"}
|
|
||||||
|
|
||||||
%% Keyed
|
|
||||||
, {"",
|
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
|
||||||
"10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568"}
|
|
||||||
, {"00",
|
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
|
||||||
"961f6dd1e4dd30f63901690c512e78e4b45e4742ed197c3c5e45c549fd25f2e4187b0bc9fe30492b16b0d0bc4ef9b0f34c7003fac09a5ef1532e69430234cebd"}
|
|
||||||
, {"0001",
|
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
|
||||||
"da2cfbe2d8409a0f38026113884f84b50156371ae304c4430173d08a99d9fb1b983164a3770706d537f49e0c916d9f32b95cc37a95b99d857436f0232c88a965"}
|
|
||||||
, {"00010203040506070809",
|
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
|
||||||
"4fe181f54ad63a2983feaaf77d1e7235c2beb17fa328b6d9505bda327df19fc37f02c4b6f0368ce23147313a8e5738b5fa2a95b29de1c7f8264eb77b69f585cd"}
|
|
||||||
].
|
|
||||||
|
|
||||||
|
|
||||||
-endif.
|
|
||||||
|
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
-module(aeso_compiler_tests).
|
-module(aeso_compiler_tests).
|
||||||
|
|
||||||
|
-compile([export_all, nowarn_export_all]).
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
%% Very simply test compile the given contracts. Only basic checks
|
%% Very simply test compile the given contracts. Only basic checks
|
||||||
@@ -16,10 +18,14 @@
|
|||||||
simple_compile_test_() ->
|
simple_compile_test_() ->
|
||||||
[ {"Testing the " ++ ContractName ++ " contract",
|
[ {"Testing the " ++ ContractName ++ " contract",
|
||||||
fun() ->
|
fun() ->
|
||||||
|
case compile(ContractName) of
|
||||||
#{byte_code := ByteCode,
|
#{byte_code := ByteCode,
|
||||||
contract_source := _,
|
contract_source := _,
|
||||||
type_info := _} = compile(ContractName),
|
type_info := _} -> ?assertMatch(Code when is_binary(Code), ByteCode);
|
||||||
?assertMatch(Code when is_binary(Code), ByteCode)
|
ErrBin ->
|
||||||
|
io:format("\n~s", [ErrBin]),
|
||||||
|
error(ErrBin)
|
||||||
|
end
|
||||||
end} || ContractName <- compilable_contracts() ] ++
|
end} || ContractName <- compilable_contracts() ] ++
|
||||||
[ {"Testing error messages of " ++ ContractName,
|
[ {"Testing error messages of " ++ ContractName,
|
||||||
fun() ->
|
fun() ->
|
||||||
@@ -80,6 +86,7 @@ compilable_contracts() ->
|
|||||||
"dutch_auction",
|
"dutch_auction",
|
||||||
"environment",
|
"environment",
|
||||||
"factorial",
|
"factorial",
|
||||||
|
"functions",
|
||||||
"fundme",
|
"fundme",
|
||||||
"identity",
|
"identity",
|
||||||
"maps",
|
"maps",
|
||||||
@@ -97,7 +104,12 @@ compilable_contracts() ->
|
|||||||
"variant_types",
|
"variant_types",
|
||||||
"state_handling",
|
"state_handling",
|
||||||
"events",
|
"events",
|
||||||
"include"
|
"include",
|
||||||
|
"basic_auth",
|
||||||
|
"bitcoin_auth",
|
||||||
|
"address_literals",
|
||||||
|
"bytes_equality",
|
||||||
|
"address_chain"
|
||||||
].
|
].
|
||||||
|
|
||||||
%% Contracts that should produce type errors
|
%% Contracts that should produce type errors
|
||||||
@@ -179,6 +191,8 @@ failing_contracts() ->
|
|||||||
" - r' (at line 5, column 10)">>,
|
" - r' (at line 5, column 10)">>,
|
||||||
<<"Repeated name x in pattern\n"
|
<<"Repeated name x in pattern\n"
|
||||||
" x :: x (at line 26, column 7)">>,
|
" x :: x (at line 26, column 7)">>,
|
||||||
|
<<"Repeated argument x to function repeated_arg (at line 44, column 12).">>,
|
||||||
|
<<"Repeated argument y to function repeated_arg (at line 44, column 12).">>,
|
||||||
<<"No record type with fields y, z (at line 14, column 22)">>,
|
<<"No record type with fields y, z (at line 14, column 22)">>,
|
||||||
<<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
|
<<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
|
||||||
<<"Record type r2 does not have field y (at line 15, column 22)">>]}
|
<<"Record type r2 does not have field y (at line 15, column 22)">>]}
|
||||||
@@ -217,4 +231,102 @@ failing_contracts() ->
|
|||||||
, {"bad_include_and_ns",
|
, {"bad_include_and_ns",
|
||||||
[<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
|
[<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
|
||||||
<<"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]}
|
<<"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]}
|
||||||
|
, {"bad_address_literals",
|
||||||
|
[<<"The type bytes(32) is not a contract type\n"
|
||||||
|
"when checking that the contract literal at line 32, column 5\n"
|
||||||
|
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
|
||||||
|
"has the type\n"
|
||||||
|
" bytes(32)">>,
|
||||||
|
<<"The type oracle(int, bool) is not a contract type\n"
|
||||||
|
"when checking that the contract literal at line 30, column 5\n"
|
||||||
|
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
|
||||||
|
"has the type\n"
|
||||||
|
" oracle(int, bool)">>,
|
||||||
|
<<"The type address is not a contract type\n"
|
||||||
|
"when checking that the contract literal at line 28, column 5\n"
|
||||||
|
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
|
||||||
|
"has the type\n"
|
||||||
|
" address">>,
|
||||||
|
<<"Cannot unify oracle_query('1, '2)\n"
|
||||||
|
" and Remote\n"
|
||||||
|
"when checking the type of the expression at line 25, column 5\n"
|
||||||
|
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
|
||||||
|
" oracle_query('1, '2)\n"
|
||||||
|
"against the expected type\n"
|
||||||
|
" Remote">>,
|
||||||
|
<<"Cannot unify oracle_query('3, '4)\n"
|
||||||
|
" and bytes(32)\n"
|
||||||
|
"when checking the type of the expression at line 23, column 5\n"
|
||||||
|
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
|
||||||
|
" oracle_query('3, '4)\n"
|
||||||
|
"against the expected type\n"
|
||||||
|
" bytes(32)">>,
|
||||||
|
<<"Cannot unify oracle_query('5, '6)\n"
|
||||||
|
" and oracle(int, bool)\n"
|
||||||
|
"when checking the type of the expression at line 21, column 5\n"
|
||||||
|
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
|
||||||
|
" oracle_query('5, '6)\n"
|
||||||
|
"against the expected type\n"
|
||||||
|
" oracle(int, bool)">>,
|
||||||
|
<<"Cannot unify oracle('7, '8)\n"
|
||||||
|
" and Remote\n"
|
||||||
|
"when checking the type of the expression at line 18, column 5\n"
|
||||||
|
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
|
||||||
|
" oracle('7, '8)\n"
|
||||||
|
"against the expected type\n"
|
||||||
|
" Remote">>,
|
||||||
|
<<"Cannot unify oracle('9, '10)\n"
|
||||||
|
" and bytes(32)\n"
|
||||||
|
"when checking the type of the expression at line 16, column 5\n"
|
||||||
|
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
|
||||||
|
" oracle('9, '10)\n"
|
||||||
|
"against the expected type\n"
|
||||||
|
" bytes(32)">>,
|
||||||
|
<<"Cannot unify oracle('11, '12)\n"
|
||||||
|
" and oracle_query(int, bool)\n"
|
||||||
|
"when checking the type of the expression at line 14, column 5\n"
|
||||||
|
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
|
||||||
|
" oracle('11, '12)\n"
|
||||||
|
"against the expected type\n"
|
||||||
|
" oracle_query(int, bool)">>,
|
||||||
|
<<"Cannot unify address\n"
|
||||||
|
" and oracle(int, bool)\n"
|
||||||
|
"when checking the type of the expression at line 11, column 5\n"
|
||||||
|
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
|
||||||
|
"against the expected type\n"
|
||||||
|
" oracle(int, bool)">>,
|
||||||
|
<<"Cannot unify address\n"
|
||||||
|
" and Remote\n"
|
||||||
|
"when checking the type of the expression at line 9, column 5\n"
|
||||||
|
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
|
||||||
|
"against the expected type\n"
|
||||||
|
" Remote">>,
|
||||||
|
<<"Cannot unify address\n"
|
||||||
|
" and bytes(32)\n"
|
||||||
|
"when checking the type of the expression at line 7, column 5\n"
|
||||||
|
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
|
||||||
|
"against the expected type\n"
|
||||||
|
" bytes(32)">>]}
|
||||||
|
, {"stateful",
|
||||||
|
[<<"Cannot reference stateful function Chain.spend (at line 13, column 33)\nin the definition of non-stateful function fail1.">>,
|
||||||
|
<<"Cannot reference stateful function local_spend (at line 14, column 33)\nin the definition of non-stateful function fail2.">>,
|
||||||
|
<<"Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>,
|
||||||
|
<<"Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>,
|
||||||
|
<<"Cannot reference stateful function Chain.spend (at line 35, column 53)\nin the definition of non-stateful function fail5.">>,
|
||||||
|
<<"Cannot pass non-zero value argument 1000 (at line 48, column 55)\nin the definition of non-stateful function fail6.">>,
|
||||||
|
<<"Cannot pass non-zero value argument 1000 (at line 49, column 54)\nin the definition of non-stateful function fail7.">>,
|
||||||
|
<<"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>]}
|
||||||
|
, {"bad_init_state_access",
|
||||||
|
[<<"The init function should return the initial state as its result and cannot write the state,\n"
|
||||||
|
"but it calls\n"
|
||||||
|
" - set_state (at line 11, column 5), which calls\n"
|
||||||
|
" - roundabout (at line 8, column 36), which calls\n"
|
||||||
|
" - put (at line 7, column 37)">>,
|
||||||
|
<<"The init function should return the initial state as its result and cannot read the state,\n"
|
||||||
|
"but it calls\n"
|
||||||
|
" - new_state (at line 12, column 5), which calls\n"
|
||||||
|
" - state (at line 5, column 27)">>,
|
||||||
|
<<"The init function should return the initial state as its result and cannot read the state,\n"
|
||||||
|
"but it calls\n"
|
||||||
|
" - state (at line 13, column 13)">>]}
|
||||||
].
|
].
|
||||||
|
|||||||
@@ -41,14 +41,14 @@ all_tokens() ->
|
|||||||
%% Operators
|
%% Operators
|
||||||
lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++
|
lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++
|
||||||
%% Keywords
|
%% Keywords
|
||||||
lists:map(Lit, [contract, type, 'let', switch, rec, 'and']) ++
|
lists:map(Lit, [contract, type, 'let', switch]) ++
|
||||||
%% Comment token (not an actual token), just for tests
|
%% Comment token (not an actual token), just for tests
|
||||||
[{comment, 0, "// *Comment!\"\n"},
|
[{comment, 0, "// *Comment!\"\n"},
|
||||||
{comment, 0, "/* bla /* bla bla */*/"}] ++
|
{comment, 0, "/* bla /* bla bla */*/"}] ++
|
||||||
%% Literals
|
%% Literals
|
||||||
[ Lit(true), Lit(false)
|
[ Lit(true), Lit(false)
|
||||||
, Tok(id, "foo"), Tok(id, "_"), Tok(con, "Foo")
|
, Tok(id, "foo"), Tok(id, "_"), Tok(con, "Foo")
|
||||||
, Tok(hash, Hash)
|
, Tok(bytes, Hash)
|
||||||
, Tok(int, 1234567890), Tok(hex, 9876543210)
|
, Tok(int, 1234567890), Tok(hex, 9876543210)
|
||||||
, Tok(string, <<"bla\"\\\b\e\f\n\r\t\vbla">>)
|
, Tok(string, <<"bla\"\\\b\e\f\n\r\t\vbla">>)
|
||||||
].
|
].
|
||||||
@@ -78,7 +78,7 @@ show_token({param, _, P}) -> "@" ++ P;
|
|||||||
show_token({string, _, S}) -> fmt(binary_to_list(S));
|
show_token({string, _, S}) -> fmt(binary_to_list(S));
|
||||||
show_token({int, _, N}) -> fmt(N);
|
show_token({int, _, N}) -> fmt(N);
|
||||||
show_token({hex, _, N}) -> fmt("0x~.16b", N);
|
show_token({hex, _, N}) -> fmt("0x~.16b", N);
|
||||||
show_token({hash, _, <<N:256>>}) -> fmt("#~.16b", N);
|
show_token({bytes, _, <<N:256>>}) -> fmt("#~64.16.0b", N);
|
||||||
show_token({comment, _, S}) -> S;
|
show_token({comment, _, S}) -> S;
|
||||||
show_token({_, _, _}) -> "TODO".
|
show_token({_, _, _}) -> "TODO".
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
contract Remote =
|
||||||
|
function main : (int) => ()
|
||||||
|
|
||||||
|
contract AddrChain =
|
||||||
|
type o_type = oracle(string, map(string, int))
|
||||||
|
type oq_type = oracle_query(string, map(string, int))
|
||||||
|
|
||||||
|
function is_o(a : address) =
|
||||||
|
Address.is_oracle(a)
|
||||||
|
|
||||||
|
function is_c(a : address) =
|
||||||
|
Address.is_contract(a)
|
||||||
|
|
||||||
|
// function get_o(a : address) : option(o_type) =
|
||||||
|
// Address.get_oracle(a)
|
||||||
|
|
||||||
|
// function get_c(a : address) : option(Remote) =
|
||||||
|
// Address.get_contract(a)
|
||||||
|
|
||||||
|
function check_o(o : o_type) =
|
||||||
|
Oracle.check(o)
|
||||||
|
|
||||||
|
function check_oq(o : o_type, oq : oq_type) =
|
||||||
|
Oracle.check_query(o, oq)
|
||||||
|
|
||||||
|
// function h_to_i(h : hash) : int =
|
||||||
|
// Hash.to_int(h)
|
||||||
|
|
||||||
|
// function a_to_i(a : address) : int =
|
||||||
|
// Address.to_int(a) mod 10 ^ 16
|
||||||
|
|
||||||
|
function c_creator() : address =
|
||||||
|
Contract.creator
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
contract Remote =
|
||||||
|
function foo : () => ()
|
||||||
|
|
||||||
|
contract AddressLiterals =
|
||||||
|
function addr() : address =
|
||||||
|
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
|
||||||
|
function oracle() : oracle(int, bool) =
|
||||||
|
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||||
|
function query() : oracle_query(int, bool) =
|
||||||
|
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||||
|
function contr() : Remote =
|
||||||
|
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||||
|
|
||||||
@@ -36,11 +36,6 @@ contract AllSyntax =
|
|||||||
(x, [y, z]) => bar({x = z, y = -y + - -z * (-1)})
|
(x, [y, z]) => bar({x = z, y = -y + - -z * (-1)})
|
||||||
(x, y :: _) => ()
|
(x, 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 hash : address = #01ab0fff11
|
||||||
let b = false
|
let b = false
|
||||||
let qcon = Mod.Con
|
let qcon = Mod.Con
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
contract Remote =
|
||||||
|
function foo : () => ()
|
||||||
|
|
||||||
|
contract AddressLiterals =
|
||||||
|
function addr1() : bytes(32) =
|
||||||
|
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
|
||||||
|
function addr2() : Remote =
|
||||||
|
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
|
||||||
|
function addr3() : oracle(int, bool) =
|
||||||
|
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
|
||||||
|
|
||||||
|
function oracle1() : oracle_query(int, bool) =
|
||||||
|
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||||
|
function oracle2() : bytes(32) =
|
||||||
|
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||||
|
function oracle3() : Remote =
|
||||||
|
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||||
|
|
||||||
|
function query1() : oracle(int, bool) =
|
||||||
|
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||||
|
function query2() : bytes(32) =
|
||||||
|
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||||
|
function query3() : Remote =
|
||||||
|
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||||
|
|
||||||
|
function contr1() : address =
|
||||||
|
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||||
|
function contr2() : oracle(int, bool) =
|
||||||
|
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||||
|
function contr3() : bytes(32) =
|
||||||
|
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
contract BadInit =
|
||||||
|
|
||||||
|
type state = int
|
||||||
|
|
||||||
|
function new_state(n) = state + n
|
||||||
|
|
||||||
|
stateful function roundabout(n) = put(n)
|
||||||
|
stateful function set_state(n) = roundabout(n)
|
||||||
|
|
||||||
|
stateful function init() =
|
||||||
|
set_state(4)
|
||||||
|
new_state(0)
|
||||||
|
state + state
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Contract replicating "normal" Aeternity authentication
|
||||||
|
contract BasicAuth =
|
||||||
|
record state = { nonce : int, owner : address }
|
||||||
|
|
||||||
|
function init() = { nonce = 1, owner = Call.caller }
|
||||||
|
|
||||||
|
stateful function authorize(n : int, s : signature) : bool =
|
||||||
|
require(n >= state.nonce, "Nonce too low")
|
||||||
|
require(n =< state.nonce, "Nonce too high")
|
||||||
|
put(state{ nonce = n + 1 })
|
||||||
|
switch(Auth.tx_hash)
|
||||||
|
None => abort("Not in Auth context")
|
||||||
|
Some(tx_hash) => Crypto.ecverify(to_sign(tx_hash, n), state.owner, s)
|
||||||
|
|
||||||
|
function to_sign(h : hash, n : int) =
|
||||||
|
Crypto.blake2b((h, n))
|
||||||
|
|
||||||
|
private function require(b : bool, err : string) =
|
||||||
|
if(!b) abort(err)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
contract BitcoinAuth =
|
||||||
|
record state = { nonce : int, owner : bytes(64) }
|
||||||
|
|
||||||
|
function init(owner' : bytes(64)) = { nonce = 1, owner = owner' }
|
||||||
|
|
||||||
|
stateful function authorize(n : int, s : signature) : bool =
|
||||||
|
require(n >= state.nonce, "Nonce too low")
|
||||||
|
require(n =< state.nonce, "Nonce too high")
|
||||||
|
put(state{ nonce = n + 1 })
|
||||||
|
switch(Auth.tx_hash)
|
||||||
|
None => abort("Not in Auth context")
|
||||||
|
Some(tx_hash) => Crypto.ecverify_secp256k1(to_sign(tx_hash, n), state.owner, s)
|
||||||
|
|
||||||
|
function to_sign(h : hash, n : int) : hash =
|
||||||
|
Crypto.blake2b((h, n))
|
||||||
|
|
||||||
|
private function require(b : bool, err : string) =
|
||||||
|
if(!b) abort(err)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
contract BytesEquality =
|
||||||
|
|
||||||
|
function eq16(a : bytes(16), b) = a == b
|
||||||
|
function ne16(a : bytes(16), b) = a != b
|
||||||
|
|
||||||
|
function eq32(a : bytes(32), b) = a == b
|
||||||
|
function ne32(a : bytes(32), b) = a != b
|
||||||
|
|
||||||
|
function eq47(a : bytes(47), b) = a == b
|
||||||
|
function ne47(a : bytes(47), b) = a != b
|
||||||
|
|
||||||
|
function eq64(a : bytes(64), b) = a == b
|
||||||
|
function ne64(a : bytes(64), b) = a != b
|
||||||
|
|
||||||
|
function eq65(a : bytes(65), b) = a == b
|
||||||
|
function ne65(a : bytes(65), b) = a != b
|
||||||
|
|
||||||
@@ -5,5 +5,5 @@ contract Counter =
|
|||||||
|
|
||||||
function init(val) = { value = val }
|
function init(val) = { value = val }
|
||||||
function get() = state.value
|
function get() = state.value
|
||||||
function tick() = put(state{ value = state.value + 1 })
|
stateful function tick() = put(state{ value = state.value + 1 })
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ contract DutchAuction =
|
|||||||
sold : bool }
|
sold : bool }
|
||||||
|
|
||||||
// Add to work around current lack of predefined functions
|
// Add to work around current lack of predefined functions
|
||||||
private function spend(to, amount) =
|
private stateful function spend(to, amount) =
|
||||||
let total = Contract.balance
|
let total = Contract.balance
|
||||||
Chain.spend(to, amount)
|
Chain.spend(to, amount)
|
||||||
total - amount
|
total - amount
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ contract Environment =
|
|||||||
|
|
||||||
function init(remote) = {remote = remote}
|
function init(remote) = {remote = remote}
|
||||||
|
|
||||||
function set_remote(remote) = put({remote = remote})
|
stateful function set_remote(remote) = put({remote = remote})
|
||||||
|
|
||||||
// -- Information about the this contract ---
|
// -- Information about the this contract ---
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ contract Environment =
|
|||||||
|
|
||||||
// Value
|
// Value
|
||||||
function call_value() : int = Call.value
|
function call_value() : int = Call.value
|
||||||
function nested_value(value : int) : int =
|
stateful function nested_value(value : int) : int =
|
||||||
state.remote.call_value(value = value / 2)
|
state.remote.call_value(value = value / 2)
|
||||||
|
|
||||||
// Gas price
|
// Gas price
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ contract Factorial =
|
|||||||
|
|
||||||
function init(worker) = {worker = worker}
|
function init(worker) = {worker = worker}
|
||||||
|
|
||||||
function set_worker(worker) = put(state{worker = worker})
|
stateful function set_worker(worker) = put(state{worker = worker})
|
||||||
|
|
||||||
function fac(x : int) : int =
|
function fac(x : int) : int =
|
||||||
if(x == 0) 1
|
if(x == 0) 1
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
contract Functions =
|
||||||
|
private function curry(f : ('a, 'b) => 'c) =
|
||||||
|
(x) => (y) => f(x, y)
|
||||||
|
private function map(f : 'a => 'b, xs : list('a)) =
|
||||||
|
switch(xs)
|
||||||
|
[] => []
|
||||||
|
x :: xs => f(x) :: map(f, xs)
|
||||||
|
private function map'() = map
|
||||||
|
private function plus(x, y) = x + y
|
||||||
|
function test1(xs : list(int)) = map(curry(plus)(5), xs)
|
||||||
|
function test2(xs : list(int)) = map'()(((x) => (y) => ((x, y) => x + y)(x, y))(100), xs)
|
||||||
|
function test3(xs : list(int)) =
|
||||||
|
let m(f, xs) = map(f, xs)
|
||||||
|
m((x) => x + 1, xs)
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ contract FundMe =
|
|||||||
private function require(b : bool, err : string) =
|
private function require(b : bool, err : string) =
|
||||||
if(!b) abort(err)
|
if(!b) abort(err)
|
||||||
|
|
||||||
private function spend(args : spend_args) =
|
private stateful function spend(args : spend_args) =
|
||||||
Chain.spend(args.recipient, args.amount)
|
Chain.spend(args.recipient, args.amount)
|
||||||
|
|
||||||
public function init(beneficiary, deadline, goal) : state =
|
public function init(beneficiary, deadline, goal) : state =
|
||||||
@@ -53,7 +53,7 @@ contract FundMe =
|
|||||||
require(state.total >= state.goal, "Project was not funded")
|
require(state.total >= state.goal, "Project was not funded")
|
||||||
spend({recipient = state.beneficiary,
|
spend({recipient = state.beneficiary,
|
||||||
amount = Contract.balance })
|
amount = Contract.balance })
|
||||||
put(state{ beneficiary = #0 })
|
put(state{ beneficiary = ak_11111111111111111111111111111111273Yts })
|
||||||
|
|
||||||
private stateful function withdraw_contributor() =
|
private stateful function withdraw_contributor() =
|
||||||
if(state.total >= state.goal)
|
if(state.total >= state.goal)
|
||||||
|
|||||||
+12
-12
@@ -17,8 +17,8 @@ contract Maps =
|
|||||||
{ ["one"] = {x = 1, y = 2},
|
{ ["one"] = {x = 1, y = 2},
|
||||||
["two"] = {x = 3, y = 4},
|
["two"] = {x = 3, y = 4},
|
||||||
["three"] = {x = 5, y = 6} }
|
["three"] = {x = 5, y = 6} }
|
||||||
function map_state_i() = put(state{ map_i = map_i() })
|
stateful function map_state_i() = put(state{ map_i = map_i() })
|
||||||
function map_state_s() = put(state{ map_s = map_s() })
|
stateful function map_state_s() = put(state{ map_s = map_s() })
|
||||||
|
|
||||||
// m[k]
|
// m[k]
|
||||||
function get_i(k, m : map(int, pt)) = m[k]
|
function get_i(k, m : map(int, pt)) = m[k]
|
||||||
@@ -35,20 +35,20 @@ contract Maps =
|
|||||||
// m{[k] = v}
|
// m{[k] = v}
|
||||||
function set_i(k, p, m : map(int, pt)) = m{ [k] = p }
|
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_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) })
|
stateful 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) })
|
stateful function set_state_s(k, p) = put(state{ map_s = set_s(k, p, state.map_s) })
|
||||||
|
|
||||||
// m{f[k].x = v}
|
// m{f[k].x = v}
|
||||||
function setx_i(k, x, m : map(int, pt)) = m{ [k].x = x }
|
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_s(k, x, m : map(string, pt)) = m{ [k].x = x }
|
||||||
function setx_state_i(k, x) = put(state{ map_i[k].x = x })
|
stateful 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 })
|
stateful function setx_state_s(k, x) = put(state{ map_s[k].x = x })
|
||||||
|
|
||||||
// m{[k] @ x = v }
|
// m{[k] @ x = v }
|
||||||
function addx_i(k, d, m : map(int, pt)) = m{ [k].x @ x = x + d }
|
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_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 })
|
stateful 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 })
|
stateful function addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d })
|
||||||
|
|
||||||
// m{[k = def] @ x = v }
|
// 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_i(k, v, d, m : map(int, pt)) = m{ [k = v].x @ x = x + d }
|
||||||
@@ -77,8 +77,8 @@ contract Maps =
|
|||||||
// Map.delete
|
// Map.delete
|
||||||
function delete_i(k, m : map(int, pt)) = Map.delete(k, m)
|
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_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) })
|
stateful 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) })
|
stateful function delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) })
|
||||||
|
|
||||||
// Map.size
|
// Map.size
|
||||||
function size_i(m : map(int, pt)) = Map.size(m)
|
function size_i(m : map(int, pt)) = Map.size(m)
|
||||||
@@ -95,6 +95,6 @@ contract Maps =
|
|||||||
// Map.from_list
|
// Map.from_list
|
||||||
function fromlist_i(xs : list((int, pt))) = Map.from_list(xs)
|
function fromlist_i(xs : list((int, pt))) = Map.from_list(xs)
|
||||||
function fromlist_s(xs : list((string, 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) })
|
stateful function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) })
|
||||||
function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) })
|
stateful function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) })
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -9,22 +9,22 @@ contract Oracles =
|
|||||||
type oracle_id = oracle(query_t, answer_t)
|
type oracle_id = oracle(query_t, answer_t)
|
||||||
type query_id = oracle_query(query_t, answer_t)
|
type query_id = oracle_query(query_t, answer_t)
|
||||||
|
|
||||||
function registerOracle(acct : address,
|
stateful function registerOracle(acct : address,
|
||||||
qfee : fee,
|
qfee : fee,
|
||||||
ttl : ttl) : oracle_id =
|
ttl : ttl) : oracle_id =
|
||||||
Oracle.register(acct, qfee, ttl)
|
Oracle.register(acct, qfee, ttl)
|
||||||
|
|
||||||
function registerIntIntOracle(acct : address,
|
stateful function registerIntIntOracle(acct : address,
|
||||||
qfee : fee,
|
qfee : fee,
|
||||||
ttl : ttl) : oracle(int, int) =
|
ttl : ttl) : oracle(int, int) =
|
||||||
Oracle.register(acct, qfee, ttl)
|
Oracle.register(acct, qfee, ttl)
|
||||||
|
|
||||||
function registerStringStringOracle(acct : address,
|
stateful function registerStringStringOracle(acct : address,
|
||||||
qfee : fee,
|
qfee : fee,
|
||||||
ttl : ttl) : oracle(string, string) =
|
ttl : ttl) : oracle(string, string) =
|
||||||
Oracle.register(acct, qfee, ttl)
|
Oracle.register(acct, qfee, ttl)
|
||||||
|
|
||||||
function signedRegisterOracle(acct : address,
|
stateful function signedRegisterOracle(acct : address,
|
||||||
sign : signature,
|
sign : signature,
|
||||||
qfee : fee,
|
qfee : fee,
|
||||||
ttl : ttl) : oracle_id =
|
ttl : ttl) : oracle_id =
|
||||||
@@ -33,7 +33,7 @@ contract Oracles =
|
|||||||
function queryFee(o : oracle_id) : fee =
|
function queryFee(o : oracle_id) : fee =
|
||||||
Oracle.query_fee(o)
|
Oracle.query_fee(o)
|
||||||
|
|
||||||
function createQuery(o : oracle_id,
|
stateful function createQuery(o : oracle_id,
|
||||||
q : query_t,
|
q : query_t,
|
||||||
qfee : fee,
|
qfee : fee,
|
||||||
qttl : ttl,
|
qttl : ttl,
|
||||||
@@ -42,7 +42,7 @@ contract Oracles =
|
|||||||
Oracle.query(o, q, qfee, qttl, rttl)
|
Oracle.query(o, q, qfee, qttl, rttl)
|
||||||
|
|
||||||
// Do not use in production!
|
// Do not use in production!
|
||||||
function unsafeCreateQuery(o : oracle_id,
|
stateful function unsafeCreateQuery(o : oracle_id,
|
||||||
q : query_t,
|
q : query_t,
|
||||||
qfee : fee,
|
qfee : fee,
|
||||||
qttl : ttl,
|
qttl : ttl,
|
||||||
@@ -50,7 +50,7 @@ contract Oracles =
|
|||||||
Oracle.query(o, q, qfee, qttl, rttl)
|
Oracle.query(o, q, qfee, qttl, rttl)
|
||||||
|
|
||||||
// Do not use in production!
|
// Do not use in production!
|
||||||
function unsafeCreateQueryThenErr(o : oracle_id,
|
stateful function unsafeCreateQueryThenErr(o : oracle_id,
|
||||||
q : query_t,
|
q : query_t,
|
||||||
qfee : fee,
|
qfee : fee,
|
||||||
qttl : ttl,
|
qttl : ttl,
|
||||||
@@ -59,21 +59,21 @@ contract Oracles =
|
|||||||
require(qfee >= 100000000000000000, "causing a late error")
|
require(qfee >= 100000000000000000, "causing a late error")
|
||||||
res
|
res
|
||||||
|
|
||||||
function extendOracle(o : oracle_id,
|
stateful function extendOracle(o : oracle_id,
|
||||||
ttl : ttl) : () =
|
ttl : ttl) : () =
|
||||||
Oracle.extend(o, ttl)
|
Oracle.extend(o, ttl)
|
||||||
|
|
||||||
function signedExtendOracle(o : oracle_id,
|
stateful function signedExtendOracle(o : oracle_id,
|
||||||
sign : signature, // Signed oracle address
|
sign : signature, // Signed oracle address
|
||||||
ttl : ttl) : () =
|
ttl : ttl) : () =
|
||||||
Oracle.extend(o, signature = sign, ttl)
|
Oracle.extend(o, signature = sign, ttl)
|
||||||
|
|
||||||
function respond(o : oracle_id,
|
stateful function respond(o : oracle_id,
|
||||||
q : query_id,
|
q : query_id,
|
||||||
r : answer_t) : () =
|
r : answer_t) : () =
|
||||||
Oracle.respond(o, q, r)
|
Oracle.respond(o, q, r)
|
||||||
|
|
||||||
function signedRespond(o : oracle_id,
|
stateful function signedRespond(o : oracle_id,
|
||||||
q : query_id,
|
q : query_id,
|
||||||
sign : signature,
|
sign : signature,
|
||||||
r : answer_t) : () =
|
r : answer_t) : () =
|
||||||
@@ -96,13 +96,13 @@ contract Oracles =
|
|||||||
datatype complexQuestion = Why(int) | How(string)
|
datatype complexQuestion = Why(int) | How(string)
|
||||||
datatype complexAnswer = NoAnswer | Answer(complexQuestion, string, int)
|
datatype complexAnswer = NoAnswer | Answer(complexQuestion, string, int)
|
||||||
|
|
||||||
function complexOracle(question) =
|
stateful function complexOracle(question) =
|
||||||
let o = Oracle.register(Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
|
let o = Oracle.register(Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
|
||||||
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
|
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
|
||||||
Oracle.respond(o, q, Answer(question, "magic", 1337))
|
Oracle.respond(o, q, Answer(question, "magic", 1337))
|
||||||
Oracle.get_answer(o, q)
|
Oracle.get_answer(o, q)
|
||||||
|
|
||||||
function signedComplexOracle(question, sig) =
|
stateful function signedComplexOracle(question, sig) =
|
||||||
let o = Oracle.register(signature = sig, Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
|
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))
|
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
|
||||||
Oracle.respond(o, q, Answer(question, "magic", 1337), signature = sig)
|
Oracle.respond(o, q, Answer(question, "magic", 1337), signature = sig)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ contract Remote3 =
|
|||||||
|
|
||||||
contract RemoteCall =
|
contract RemoteCall =
|
||||||
|
|
||||||
function call(r : Remote1, x : int) : int =
|
stateful function call(r : Remote1, x : int) : int =
|
||||||
r.main(gas = 10000, value = 10, x)
|
r.main(gas = 10000, value = 10, x)
|
||||||
|
|
||||||
function staged_call(r1 : Remote1, r2 : Remote2, x : int) =
|
function staged_call(r1 : Remote1, r2 : Remote2, x : int) =
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -24,5 +24,5 @@ contract SimpleStorage =
|
|||||||
|
|
||||||
function get() : int = state.data
|
function get() : int = state.data
|
||||||
|
|
||||||
function set(value : int) =
|
stateful function set(value : int) =
|
||||||
put(state{data = value})
|
put(state{data = value})
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ contract SpendContract =
|
|||||||
|
|
||||||
contract SpendTest =
|
contract SpendTest =
|
||||||
|
|
||||||
function spend(to, amount) =
|
stateful function spend(to, amount) =
|
||||||
let total = Contract.balance
|
let total = Contract.balance
|
||||||
Chain.spend(to, amount)
|
Chain.spend(to, amount)
|
||||||
total - amount
|
total - amount
|
||||||
|
|
||||||
function withdraw(amount) : int =
|
stateful function withdraw(amount) : int =
|
||||||
spend(Call.caller, amount)
|
spend(Call.caller, amount)
|
||||||
|
|
||||||
function withdraw_from(account, amount) =
|
stateful function withdraw_from(account, amount) =
|
||||||
account.withdraw(amount)
|
account.withdraw(amount)
|
||||||
withdraw(amount)
|
withdraw(amount)
|
||||||
|
|
||||||
function spend_from(from, to, amount) =
|
stateful function spend_from(from, to, amount) =
|
||||||
from.withdraw(amount)
|
from.withdraw(amount)
|
||||||
Chain.spend(to, amount)
|
Chain.spend(to, amount)
|
||||||
Chain.balance(to)
|
Chain.balance(to)
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ contract StateHandling =
|
|||||||
function read_s() = state.s
|
function read_s() = state.s
|
||||||
function read_m() = state.m
|
function read_m() = state.m
|
||||||
|
|
||||||
function update(new_state : state) = put(new_state)
|
stateful function update(new_state : state) = put(new_state)
|
||||||
function update_i(new_i) = put(state{ i = new_i })
|
stateful function update_i(new_i) = put(state{ i = new_i })
|
||||||
function update_s(new_s) = put(state{ s = new_s })
|
stateful function update_s(new_s) = put(state{ s = new_s })
|
||||||
function update_m(new_m) = put(state{ m = new_m })
|
stateful function update_m(new_m) = put(state{ m = new_m })
|
||||||
|
|
||||||
function pass_it(r : Remote) = r.look_at(state)
|
function pass_it(r : Remote) = r.look_at(state)
|
||||||
function nop(r : Remote) = put(state{ i = state.i })
|
stateful function nop(r : Remote) = put(state{ i = state.i })
|
||||||
function return_it_s(r : Remote, big : bool) =
|
function return_it_s(r : Remote, big : bool) =
|
||||||
let x = r.return_s(big)
|
let x = r.return_s(big)
|
||||||
String.length(x)
|
String.length(x)
|
||||||
@@ -50,10 +50,10 @@ contract StateHandling =
|
|||||||
function pass_update_s(r : Remote, s) = r.fun_update_s(state, s)
|
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 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))
|
stateful 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))
|
stateful 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))
|
stateful 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))
|
stateful function remote_update_mk(r : Remote, k, v) = put(r.fun_update_mk(state, k, v))
|
||||||
|
|
||||||
// remote called
|
// remote called
|
||||||
function look_at(s : state) = ()
|
function look_at(s : state) = ()
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
|
||||||
|
contract Remote =
|
||||||
|
stateful function remote_spend : (address, int) => ()
|
||||||
|
function remote_pure : int => int
|
||||||
|
|
||||||
|
contract Stateful =
|
||||||
|
|
||||||
|
private function pure(x) = x + 1
|
||||||
|
private stateful function local_spend(a) =
|
||||||
|
Chain.spend(a, 1000)
|
||||||
|
|
||||||
|
// Non-stateful functions cannot mention stateful functions
|
||||||
|
function fail1(a : address) = Chain.spend(a, 1000)
|
||||||
|
function fail2(a : address) = local_spend(a)
|
||||||
|
function fail3(a : address) =
|
||||||
|
let foo = Chain.spend
|
||||||
|
foo(a, 1000)
|
||||||
|
|
||||||
|
// Private functions must also be annotated
|
||||||
|
private function fail4(a) = Chain.spend(a, 1000)
|
||||||
|
|
||||||
|
// If annotated, stateful functions are allowed
|
||||||
|
stateful function ok1(a : address) = Chain.spend(a, 1000)
|
||||||
|
|
||||||
|
// And pure functions are always allowed
|
||||||
|
stateful function ok2(a : address) = pure(5)
|
||||||
|
stateful function ok3(a : address) =
|
||||||
|
let foo = pure
|
||||||
|
foo(5)
|
||||||
|
|
||||||
|
// No error here (fail4 is annotated as not stateful)
|
||||||
|
function ok4(a : address) = fail4(a)
|
||||||
|
|
||||||
|
// Lamdbas are checked at the construction site
|
||||||
|
private function fail5() : address => () = (a) => Chain.spend(a, 1000)
|
||||||
|
|
||||||
|
// .. so you can pass a stateful lambda to a non-stateful higher-order
|
||||||
|
// function:
|
||||||
|
private function apply(f : 'a => 'b, x) = f(x)
|
||||||
|
stateful function ok5(a : address) =
|
||||||
|
apply((val) => Chain.spend(a, val), 1000)
|
||||||
|
|
||||||
|
// It doesn't matter if remote calls are stateful or not
|
||||||
|
function ok6(r : Remote) = r.remote_spend(Contract.address, 1000)
|
||||||
|
function ok7(r : Remote) = r.remote_pure(5)
|
||||||
|
|
||||||
|
// But you can't send any tokens if not stateful
|
||||||
|
function fail6(r : Remote) = r.remote_spend(value = 1000, Contract.address, 1000)
|
||||||
|
function fail7(r : Remote) = r.remote_pure(value = 1000, 5)
|
||||||
|
function fail8(r : Remote) =
|
||||||
|
let foo = r.remote_pure
|
||||||
|
foo(value = 1000, 5)
|
||||||
|
function ok8(r : Remote) = r.remote_spend(Contract.address, 1000, value = 0)
|
||||||
|
|
||||||
@@ -40,3 +40,5 @@ contract Test =
|
|||||||
|
|
||||||
function type_error(r, x) =
|
function type_error(r, x) =
|
||||||
set_x(set_x(x, r), x)
|
set_x(set_x(x, r), x)
|
||||||
|
|
||||||
|
function repeated_arg(x : int, y, x : string, y : bool) : string = x
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ contract VariantTypes =
|
|||||||
|
|
||||||
function require(b) = if(!b) abort("required")
|
function require(b) = if(!b) abort("required")
|
||||||
|
|
||||||
function start(bal : int) =
|
stateful function start(bal : int) =
|
||||||
switch(state)
|
switch(state)
|
||||||
Stopped => put(Started({owner = Call.caller, balance = bal, color = Grey(0)}))
|
Stopped => put(Started({owner = Call.caller, balance = bal, color = Grey(0)}))
|
||||||
|
|
||||||
function stop() =
|
stateful function stop() =
|
||||||
switch(state)
|
switch(state)
|
||||||
Started(st) =>
|
Started(st) =>
|
||||||
require(Call.caller == st.owner)
|
require(Call.caller == st.owner)
|
||||||
@@ -23,7 +23,7 @@ contract VariantTypes =
|
|||||||
st.balance
|
st.balance
|
||||||
|
|
||||||
function get_color() = switch(state) Started(st) => st.color
|
function get_color() = switch(state) Started(st) => st.color
|
||||||
function set_color(c) = switch(state) Started(st) => put(Started(st{color = c}))
|
stateful function set_color(c) = switch(state) Started(st) => put(Started(st{color = c}))
|
||||||
|
|
||||||
function get_state() = state
|
function get_state() = state
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user