Compare commits

..

5 Commits

Author SHA1 Message Date
Hans Svensson a1d33e93ab Merge pull request #26 from aeternity/improved_roma_standalone
Improved roma standalone
2019-02-12 14:52:12 +01:00
Robert Virding d14c56d4b5 Fix testing to use new error message format 2019-02-12 11:31:30 +01:00
Hans Svensson f41b0a0ba7 Add create calldata to standalone compiler 2019-02-12 11:26:12 +01:00
Hans Svensson cef2383726 Cleanup whitespace, bad typespec, and remaining enacl reference 2019-02-12 11:26:10 +01:00
Robert Virding 3137bc4d4a Improve the interface to the compiler
It is now more consistent though we can still discuss how we want the
interface to look.
2019-02-12 11:25:34 +01:00
112 changed files with 2611 additions and 7821 deletions
+4 -4
View File
@@ -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:
+1 -6
View File
@@ -1,5 +1,5 @@
.rebar3 .rebar3
_[^_]* _*
.eunit .eunit
*.o *.o
*.beam *.beam
@@ -16,8 +16,3 @@ _build
.idea .idea
*.iml *.iml
rebar3.crashdump rebar3.crashdump
*.erl~
*.aes~
aesophia
.qcci
current_counterexample.eqc
-111
View File
@@ -1,111 +0,0 @@
# 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.2.0] - 2019-06-28
### Added
- New builtin function `require : (bool, string) => ()`. Defined as
```
function require(b, err) = if(!b) abort(err)
```
- New builtin functions
```
Bytes.to_str : bytes(_) => string
Bytes.to_int : bytes(_) => int
```
for converting a byte array to a hex string and interpreting it as a
big-endian encoded integer respectively.
### Changed
- Public contract functions must now be declared as *entrypoints*:
```
contract Example =
// Exported
entrypoint exported_fun(x) = local_fun(x)
// Not exported
function local_fun(x) = x
```
Functions in namespaces still use `function` (and `private function` for
private functions).
- The return type of `Chain.block_hash(height)` has changed, it used to
be `int`, where `0` denoted an incorrect height. New return type is
`option(hash)`, where `None` represents an incorrect height.
- Event name hashes now use BLAKE2b instead of Keccak256.
- Fixed bugs when defining record types in namespaces.
- Fixed a bug in include path handling when passing options to the compiler.
### Removed
## [3.1.0] - 2019-06-03
### Added
### Changed
- Keyword `indexed` is now optional for word typed (`bool`, `int`, `address`,
...) event arguments.
- State variable pretty printing now produce `'a, 'b, ...` instead of `'1, '2, ...`.
- ACI is restructured and improved:
- `state` and `event` types (if present) now appear at the top level.
- Namespaces and remote interfaces are no longer ignored.
- All type definitions are included in the interface rendering.
- API functions are renamed, new functions are `contract_interface`
and `render_aci_json`.
- Fixed a bug in `create_calldata`/`to_sophia_value` - it can now handle negative
literals.
### 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.2.0...HEAD
[3.2.0]: https://github.com/aeternity/aesophia/compare/v3.1.0...v3.2.0
[3.1.0]: https://github.com/aeternity/aesophia/compare/v3.0.0...v3.1.0
[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
+2 -17
View File
@@ -5,21 +5,6 @@ This is the __sophia__ compiler for the æternity system which compiles contract
For more information about æternity smart contracts and the sophia language see [Smart Contracts](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md) and the [Sophia Language](https://github.com/aeternity/protocol/blob/master/contracts/sophia.md). For more information about æternity smart contracts and the sophia language see [Smart Contracts](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md) and the [Sophia Language](https://github.com/aeternity/protocol/blob/master/contracts/sophia.md).
It is an OTP application written in Erlang and is by default included in It is an OTP application written in Erlang and is by default included in
[the æternity node](https://github.com/aeternity/epoch). However, it can [æternity Epoch](https://github.com/aeternity/epoch). However, it can
also be included in other systems to compile contracts coded in sophia which also be included in other system to compile contracts coded in sophia which
can then be loaded into the æternity system. can then be loaded into the æternity system.
## Versioning
`aesophia` has a version that is only loosely connected to the version of the
Aeternity node - in principle they will share the major version but not
minor/patch version. The `aesophia` compiler version MUST be bumped whenever
there is a change in how byte code is generated, but it MAY also be bumped upon
API changes etc.
## Interface Modules
The basic modules for interfacing the compiler:
* [aeso_compiler: the Sophia compiler](./docs/aeso_compiler.md)
* [aeso_aci: the ACI interface](./docs/aeso_aci.md)
-156
View File
@@ -1,156 +0,0 @@
# aeso_aci
### Module
### aeso_aci
The ACI interface encoder and decoder.
### Description
This module provides an interface to generate and convert between
Sophia contracts and a suitable JSON encoding of contract
interface. As yet the interface is very basic.
Encoding this contract:
```
contract Answers =
record state = { a : answers }
type answers() = map(string, int)
stateful function init() = { a = {} }
private function the_answer() = 42
function new_answer(q : string, a : int) : answers() = { [q] = a }
```
generates the following JSON structure representing the contract interface:
``` json
{
"contract": {
"functions": [
{
"arguments": [],
"name": "init",
"returns": "Answers.state",
"stateful": true
},
{
"arguments": [
{
"name": "q",
"type": "string"
},
{
"name": "a",
"type": "int"
}
],
"name": "new_answer",
"returns": {
"map": [
"string",
"int"
]
},
"stateful": false
}
],
"name": "Answers",
"state": {
"record": [
{
"name": "a",
"type": "Answers.answers"
}
]
},
"type_defs": [
{
"name": "answers",
"typedef": {
"map": [
"string",
"int"
]
},
"vars": []
}
]
}
}
```
When that encoding is decoded the following include definition is generated:
```
contract Answers =
record state = {a : Answers.answers}
type answers = map(string, int)
function init : () => Answers.state
function new_answer : (string, int) => map(string, int)
```
### Types
```erlang
-type aci_type() :: json | string.
-type json() :: jsx:json_term().
-type json_text() :: binary().
```
### Exports
#### contract\_interface(aci\_type(), string()) -> {ok, json() | string()} | {error, term()}
Generate the JSON encoding of the interface to a contract. The type definitions
and non-private functions are included in the JSON string.
#### render\_aci\_json(json() | json\_text()) -> string().
Take a JSON encoding of a contract interface and generate a contract interface
that can be included in another contract.
### Example run
This is an example of using the ACI generator from an Erlang shell. The file
called `aci_test.aes` contains the contract in the description from which we
want to generate files `aci_test.json` which is the JSON encoding of the
contract interface and `aci_test.include` which is the contract definition to
be included inside another contract.
``` erlang
1> {ok,Contract} = file:read_file("aci_test.aes").
{ok,<<"contract Answers =\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful function"...>>}
2> {ok,JsonACI} = aeso_aci:contract_interface(json, Contract).
{ok,[#{contract =>
#{functions =>
[#{arguments => [],name => <<"init">>,
returns => <<"Answers.state">>,stateful => true},
#{arguments =>
[#{name => <<"q">>,type => <<"string">>},
#{name => <<"a">>,type => <<"int">>}],
name => <<"new_answer">>,
returns => #{<<"map">> => [<<"string">>,<<"int">>]},
stateful => false}],
name => <<"Answers">>,
state =>
#{record =>
[#{name => <<"a">>,type => <<"Answers.answers">>}]},
type_defs =>
[#{name => <<"answers">>,
typedef => #{<<"map">> => [<<"string">>,<<"int">>]},
vars => []}]}}]}
3> file:write_file("aci_test.aci", jsx:encode(JsonACI)).
ok
4> {ok,InterfaceStub} = aeso_aci:render_aci_json(JsonACI).
{ok,<<"contract Answers =\n record state = {a : Answers.answers}\n type answers = map(string, int)\n function init "...>>}
5> file:write_file("aci_test.include", InterfaceStub).
ok
6> jsx:prettify(jsx:encode(JsonACI)).
<<"[\n {\n \"contract\": {\n \"functions\": [\n {\n \"arguments\": [],\n \"name\": \"init\",\n "...>>
```
The final call to `jsx:prettify(jsx:encode(JsonACI))` returns the encoding in a
more easily readable form. This is what is shown in the description above.
-86
View File
@@ -1,86 +0,0 @@
# aeso_compiler
### Module
### aeso_compiler
The Sophia compiler
### Description
This module provides the interface to the standard Sophia compiler. It
returns the compiled module in a map which can then be loaded.
### Types
``` erlang
contract_string() = string() | binary()
contract_map() = #{bytecode => binary(),
compiler_version => binary(),
contract_souce => string(),
type_info => type_info()}
type_info()
errorstring() = binary()
```
### Exports
#### file(File)
#### file(File, Options) -> CompRet
#### from_string(ContractString, Options) -> CompRet
Types
``` erlang
ContractString = contract_string()
Options = [Option]
CompRet = {ok,ContractMap} | {error,ErrorString}
ContractMap = contract_map()
ErrorString = errorstring()
```
Compile a contract defined in a file or in a string.
The **pp_** options all print to standard output the following:
`pp_sophia_code` - print the input Sophia code.
`pp_ast` - print the AST of the code
`pp_types` - print information about the types
`pp_typed_ast` - print the AST with type information at each node
`pp_icode` - print the internal code structure
`pp_assembler` - print the generated assembler code
`pp_bytecode` - print the bytecode instructions
#### check_call(ContractString, Options) -> CheckRet
Types
```
ContractString = string() | binary()
CheckRet = {ok,string(),{Types,Type | any()},Terms} | {error,Term}
Types = [Type]
Type = term()
```
Check a call in contract through the `__call` function.
#### sophia_type_to_typerep(String) -> TypeRep
Types
``` erlang
{ok,TypeRep} | {error, badtype}
```
Get the type representation of a type declaration.
#### version() -> {ok, Version} | {error, term()}
Types
``` erlang
Version = binary()
```
Get the current version of the Sophia compiler.
+15
View File
@@ -0,0 +1,15 @@
-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()} }).
+17 -14
View File
@@ -1,12 +1,23 @@
%% -*- mode: erlang; indent-tabs-mode: nil -*-
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"adf3664"}}} {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
{ref,"99bf097"}}}
, {getopt, "1.0.1"} , {getopt, "1.0.1"}
, {eblake2, "1.0.0"} ]}.
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
{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, [
@@ -14,11 +25,3 @@
{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.2.0"},
[aesophia, aebytecode, getopt]},
{dev_mode, true},
{include_erts, false},
{extended_start_script, true}]}.
+2 -16
View File
@@ -1,24 +1,10 @@
{"1.1.0", {"1.1.0",
[{<<"aebytecode">>, [{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git", {git,"https://github.com/aeternity/aebytecode.git",
{ref,"adf3664dd03626c115756f79fa0e602fda24318d"}}, {ref,"99bf097759dedbe7553f87a796bc7e1c7322e64b"}},
0}, 0},
{<<"aeserialization">>, {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}]}.
{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">>},0},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
{<<"jsx">>,
{git,"https://github.com/talentdeficit/jsx.git",
{ref,"3074d4865b3385a050badf7828ad31490d860df5"}},
0}]}.
[ [
{pkg_hash,[ {pkg_hash,[
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]} {<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
]. ].
BIN
View File
Binary file not shown.
+239
View File
@@ -0,0 +1,239 @@
%%%-------------------------------------------------------------------
%%% @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/5
, 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(Contract, FunName, Args, ArgTypes, RetType) ->
case get_type_info_and_hash(Contract, FunName) of
{ok, TypeInfo, TypeHashInt} ->
Data = aeso_heap:to_binary({TypeHashInt, list_to_tuple(Args)}),
case check_calldata(Data, TypeInfo) of
{ok, CallDataType, OutType} ->
case check_given_type(FunName, ArgTypes, RetType, CallDataType, OutType) of
ok ->
{ok, Data, CallDataType, OutType};
{error, _} = Err ->
Err
end;
{error,_What} = Err -> Err
end;
{error, _} = Err -> Err
end.
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.
%% Check that the given type matches the type from the metadata.
check_given_type(FunName, GivenArgs, GivenRet, CalldataType, ExpectRet) ->
{tuple, [word, {tuple, ExpectArgs}]} = CalldataType,
ReturnOk = if FunName == "init" -> true;
GivenRet == any -> true;
true -> GivenRet == ExpectRet
end,
ArgsOk = ExpectArgs == GivenArgs,
case ReturnOk andalso ArgsOk of
true -> ok;
false when FunName == "init" ->
{error, {init_args_mismatch,
{given, GivenArgs},
{expected, ExpectArgs}}};
false ->
{error, {call_type_mismatch,
{given, GivenArgs, '=>', GivenRet},
{expected, ExpectArgs, '=>', ExpectRet}}}
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, Args, OutType) ->
ArgType = {tuple, [T || {_, T} <- Args]},
{ 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 ]).
-360
View File
@@ -1,360 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author Robert Virding
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% ACI interface
%%% @end
%%% Created : 12 Jan 2019
%%%-------------------------------------------------------------------
-module(aeso_aci).
-export([ file/2
, file/3
, contract_interface/2
, contract_interface/3
, render_aci_json/1
, json_encode_expr/1
, json_encode_type/1]).
-type aci_type() :: json | string.
-type json() :: jsx:json_term().
-type json_text() :: binary().
%% External API
-spec file(aci_type(), string()) -> {ok, json() | string()} | {error, term()}.
file(Type, File) ->
file(Type, File, []).
file(Type, File, Options0) ->
Options = aeso_compiler:add_include_path(File, Options0),
case file:read_file(File) of
{ok, BinCode} ->
do_contract_interface(Type, binary_to_list(BinCode), Options);
{error, _} = Err -> Err
end.
-spec contract_interface(aci_type(), string()) ->
{ok, json() | string()} | {error, term()}.
contract_interface(Type, ContractString) ->
contract_interface(Type, ContractString, []).
-spec contract_interface(aci_type(), string(), [term()]) ->
{ok, json() | string()} | {error, term()}.
contract_interface(Type, ContractString, CompilerOpts) ->
do_contract_interface(Type, ContractString, CompilerOpts).
-spec render_aci_json(json() | json_text()) -> {ok, binary()}.
render_aci_json(Json) ->
do_render_aci_json(Json).
-spec json_encode_expr(aeso_syntax:expr()) -> json().
json_encode_expr(Expr) ->
encode_expr(Expr).
-spec json_encode_type(aeso_syntax:type()) -> json().
json_encode_type(Type) ->
encode_type(Type).
%% Internal functions
do_contract_interface(Type, Contract, Options) when is_binary(Contract) ->
do_contract_interface(Type, binary_to_list(Contract), Options);
do_contract_interface(Type, ContractString, Options) ->
try
Ast = aeso_compiler:parse(ContractString, Options),
%% io:format("~p\n", [Ast]),
TypedAst = aeso_ast_infer_types:infer(Ast, [dont_unfold]),
%% io:format("~p\n", [TypedAst]),
JArray = [ encode_contract(C) || C <- TypedAst ],
case Type of
json -> {ok, JArray};
string -> do_render_aci_json(JArray)
end
catch
%% The compiler errors.
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun(E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun(E) -> E end)};
error:{code_errors, Errors} ->
{error, join_errors("Code errors", Errors,
fun (E) -> io_lib:format("~p", [E]) end)}
%% General programming errors in the compiler just signal error.
end.
join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
C0 = #{name => encode_name(Name)},
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ],
FilterT = fun(N) -> fun(#{name := N1}) -> N == N1 end end,
{Es, Tdefs1} = lists:partition(FilterT(<<"event">>), Tdefs0),
{Ss, Tdefs} = lists:partition(FilterT(<<"state">>), Tdefs1),
C1 = C0#{type_defs => Tdefs},
C2 = case Es of
[] -> C1;
[#{typedef := ET}] -> C1#{event => ET}
end,
C3 = case Ss of
[] -> C2;
[#{typedef := ST}] -> C2#{state => ST}
end,
Fdefs = [ encode_function(F)
|| F <- sort_decls(contract_funcs(Contract)),
is_entrypoint(F) ],
#{contract => C3#{functions => Fdefs}};
encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) ->
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ],
#{namespace => #{name => encode_name(Name),
type_defs => Tdefs}}.
%% Encode a function definition. Currently we are only interested in
%% the interface and type.
encode_function(FDef = {letfun, _, {id, _, Name}, Args, Type, _}) ->
#{name => encode_name(Name),
arguments => encode_args(Args),
returns => encode_type(Type),
stateful => is_stateful(FDef)};
encode_function(FDecl = {fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Type}}) ->
#{name => encode_name(Name),
arguments => encode_anon_args(Args),
returns => encode_type(Type),
stateful => is_stateful(FDecl)}.
encode_anon_args(Types) ->
Anons = [ list_to_binary("_" ++ integer_to_list(X)) || X <- lists:seq(1, length(Types))],
[ #{name => V, type => encode_type(T)}
|| {V, T} <- lists:zip(Anons, Types) ].
encode_args(Args) -> [ encode_arg(A) || A <- Args ].
encode_arg({arg, _, Id, T}) ->
#{name => encode_type(Id),
type => encode_type(T)}.
encode_typedef(Type) ->
Name = typedef_name(Type),
Vars = typedef_vars(Type),
Def = typedef_def(Type),
#{name => encode_name(Name),
vars => encode_tvars(Vars),
typedef => encode_type(Def)}.
encode_tvars(Vars) ->
[ #{name => encode_type(V)} || V <- Vars ].
%% Encode type
encode_type({tvar, _, N}) -> encode_name(N);
encode_type({id, _, N}) -> encode_name(N);
encode_type({con, _, N}) -> encode_name(N);
encode_type({qid, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_type({qcon, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_type({tuple_t, _, As}) -> #{tuple => encode_types(As)};
encode_type({bytes_t, _, Len}) -> #{bytes => Len};
encode_type({record_t, Fs}) -> #{record => encode_type_fields(Fs)};
encode_type({app_t, _, Id, Fs}) -> #{encode_type(Id) => encode_types(Fs)};
encode_type({variant_t, Cs}) -> #{variant => encode_types(Cs)};
encode_type({constr_t, _, C, As}) -> #{encode_type(C) => encode_types(As)};
encode_type({alias_t, Type}) -> encode_type(Type);
encode_type({fun_t, _, _, As, T}) -> #{function =>
#{arguments => encode_types(As),
returns => encode_type(T)}}.
encode_types(Ts) -> [ encode_type(T) || T <- Ts ].
encode_type_fields(Fs) -> [ encode_type_field(F) || F <- Fs ].
encode_type_field({field_t, _, Id, T}) ->
#{name => encode_type(Id),
type => encode_type(T)}.
encode_name(Name) when is_list(Name) ->
list_to_binary(Name);
encode_name(Name) when is_binary(Name) ->
Name.
%% Encode Expr
encode_exprs(Es) -> [ encode_expr(E) || E <- Es ].
encode_expr({id, _, N}) -> encode_name(N);
encode_expr({con, _, N}) -> encode_name(N);
encode_expr({qid, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_expr({qcon, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_expr({typed, _, E}) -> encode_expr(E);
encode_expr({bool, _, B}) -> B;
encode_expr({int, _, V}) -> V;
encode_expr({string, _, S}) -> S;
encode_expr({tuple, _, As}) -> encode_exprs(As);
encode_expr({list, _, As}) -> encode_exprs(As);
encode_expr({bytes, _, B}) ->
Digits = byte_size(B),
<<N:Digits/unit:8>> = B,
list_to_binary(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N])));
encode_expr({Lit, _, L}) when Lit == oracle_pubkey; Lit == oracle_query_id;
Lit == contract_pubkey; Lit == account_pubkey ->
aeser_api_encoder:encode(Lit, L);
encode_expr({app, _, F, As}) ->
Ef = encode_expr(F),
Eas = encode_exprs(As),
#{Ef => Eas};
encode_expr({record, _, Flds}) -> maps:from_list(encode_fields(Flds));
encode_expr({map, _, KVs}) -> [ [encode_expr(K), encode_expr(V)] || {K, V} <- KVs ];
encode_expr({Op,_Ann}) ->
error({encode_expr_todo, Op}).
encode_fields(Flds) -> [ encode_field(F) || F <- Flds ].
encode_field({field, _, [{proj, _, {id, _, Fld}}], Val}) ->
{encode_name(Fld), encode_expr(Val)}.
do_render_aci_json(Json) ->
Contracts =
case Json of
JArray when is_list(JArray) -> JArray;
JObject when is_map(JObject) -> [JObject];
JText when is_binary(JText) ->
case jsx:decode(Json, [{labels, atom}, return_maps]) of
JArray when is_list(JArray) -> JArray;
JObject when is_map(JObject) -> [JObject];
_ -> error(bad_aci_json)
end
end,
DecodedContracts = [ decode_contract(C) || C <- Contracts ],
{ok, list_to_binary(string:join(DecodedContracts, "\n"))}.
decode_contract(#{contract := #{name := Name,
type_defs := Ts0,
functions := Fs} = C}) ->
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
[ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0,
["contract ", io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts), decode_funcs(Fs)];
decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] ->
["namespace ", io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts)];
decode_contract(_) -> [].
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
%% decode_func(#{name := init}) -> [];
decode_func(#{name := Name, arguments := As, returns := T}) ->
[" entrypoint", " ", io_lib:format("~s", [Name]), " : ",
decode_args(As), " => ", decode_type(T), $\n].
decode_args(As) ->
Das = [ decode_arg(A) || A <- As ],
[$(,lists:join(", ", Das),$)].
decode_arg(#{type := T}) -> decode_type(T).
decode_types(Ets) ->
[ decode_type(Et) || Et <- Ets ].
decode_type(#{tuple := Ets}) ->
Ts = decode_types(Ets),
case Ts of
[] -> ["unit"];
_ -> [$(,lists:join(" * ", Ts),$)]
end;
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(#{bytes := Len}) ->
["bytes(", integer_to_list(Len), ")"];
decode_type(#{variant := Ets}) ->
Ts = decode_types(Ets),
lists:join(" | ", Ts);
decode_type(#{function := #{arguments := Args, returns := R}}) ->
[decode_type(#{tuple => Args}), " => ", decode_type(R)];
decode_type(Econs) when is_map(Econs) -> %General constructor
[{Ec,Ets}] = maps:to_list(Econs),
AppName = decode_name(Ec),
AppArgs = decode_types(Ets),
case AppArgs of
[] -> [AppName];
_ -> [AppName,$(,lists:join(", ", AppArgs),$)]
end;
decode_type(T) -> %Just raw names.
decode_name(T).
decode_name(En) when is_atom(En) -> erlang:atom_to_list(En);
decode_name(En) when is_binary(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) || T <- Ts ].
decode_tdef(#{name := Name, vars := Vs, typedef := T}) ->
TypeDef = decode_type(T),
DefType = decode_deftype(T),
[" ", DefType, " ", decode_name(Name), decode_tvars(Vs), " = ", TypeDef, $\n].
decode_deftype(#{record := _Efs}) -> "record";
decode_deftype(#{variant := _}) -> "datatype";
decode_deftype(_T) -> "type".
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_funcs({C, _, _, Decls}) when C == contract; C == namespace ->
[ D || D <- Decls, is_fun(D)].
contract_types({C, _, _, Decls}) when C == contract; C == namespace ->
[ D || D <- Decls, is_type(D) ].
is_fun({letfun, _, _, _, _, _}) -> true;
is_fun({fun_decl, _, _, _}) -> true;
is_fun(_) -> false.
is_type({type_def, _, _, _, _}) -> true;
is_type(_) -> false.
sort_decls(Ds) ->
Sort = fun (D1, D2) ->
aeso_syntax:get_ann(line, D1, 0) =<
aeso_syntax:get_ann(line, D2, 0)
end,
lists:sort(Sort, Ds).
is_entrypoint(Node) -> aeso_syntax:get_ann(entrypoint, Node, false).
is_stateful(Node) -> aeso_syntax:get_ann(stateful, Node, false).
typedef_name({type_def, _, {id, _, Name}, _, _}) -> Name.
typedef_vars({type_def, _, _, Vars, _}) -> Vars.
typedef_def({type_def, _, _, _, Def}) -> Def.
+4 -4
View File
@@ -17,11 +17,11 @@ line({symbol, Line, _}) -> Line.
symbol_name({symbol, _, Name}) -> Name. symbol_name({symbol, _, Name}) -> Name.
pp(Ast) -> pp(Ast) ->
String = prettypr:format(aeso_pretty:decls(Ast, [])), %% TODO: Actually do *Pretty* printing.
io:format("Ast:\n~s\n", [String]). io:format("~p~n", [Ast]).
pp_typed(TypedAst) -> pp_typed(TypedAst) ->
%% io:format("Typed tree:\n~p\n",[TypedAst]),
String = prettypr:format(aeso_pretty:decls(TypedAst, [show_generated])), String = prettypr:format(aeso_pretty:decls(TypedAst, [show_generated])),
io:format("Type ast:\n~s\n",[String]). %%io:format("Typed tree:\n~p\n",[TypedAst]),
io:format("Type info:\n~s\n",[String]).
+527 -1226
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+73 -239
View File
@@ -17,23 +17,14 @@
-spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode(). -spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode().
convert_typed(TypedTree, Options) -> convert_typed(TypedTree, Options) ->
Name = case lists:last(TypedTree) of code(TypedTree, aeso_icode:new(Options)).
{contract, _, {con, _, Con}, _} -> Con;
_ -> gen_error(last_declaration_must_be_contract)
end,
NewIcode = aeso_icode:set_name(Name, aeso_icode:new(Options)),
Icode = code(TypedTree, NewIcode, Options),
deadcode_elimination(Icode).
code([{contract, _Attribs, Con, Code}|Rest], Icode, Options) -> code([{contract, _Attribs, {con, _, Name}, Code}|Rest], Icode) ->
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)), NewIcode = contract_to_icode(Code,
code(Rest, NewIcode, Options); aeso_icode:set_name(Name, Icode)),
code([{namespace, _Ann, Name, Code}|Rest], Icode, Options) -> code(Rest, NewIcode);
%% TODO: nested namespaces code([], Icode) ->
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Name, Icode)), add_default_init_function(add_builtins(Icode)).
code(Rest, NewIcode, Options);
code([], Icode, Options) ->
add_default_init_function(add_builtins(Icode), Options).
%% Generate error on correct format. %% Generate error on correct format.
@@ -41,41 +32,31 @@ gen_error(Error) ->
error({code_errors, [Error]}). error({code_errors, [Error]}).
%% Create default init function (only if state is unit). %% Create default init function (only if state is unit).
add_default_init_function(Icode = #{functions := Funs, state_type := State}, Options) -> add_default_init_function(Icode = #{functions := Funs, state_type := State}) ->
NoCode = proplists:get_value(no_code, Options, false), case lists:keymember("init", 1, Funs) of
{_, _, QInit} = aeso_icode:qualify({id, [], "init"}, Icode),
case lists:keymember(QInit, 1, Funs) of
true -> Icode; true -> Icode;
false when NoCode -> Icode; false when State /= {tuple, []} -> gen_error(missing_init_function);
false when State /= {tuple, []} ->
gen_error(missing_init_function);
false -> false ->
Type = {tuple, [typerep, {tuple, []}]}, Type = {tuple, [typerep, {tuple, []}]},
Value = #tuple{ cpts = [type_value({tuple, []}), {tuple, []}] }, Value = #tuple{ cpts = [type_value({tuple, []}), {tuple, []}] },
DefaultInit = {QInit, [], [], Value, Type}, DefaultInit = {"init", [], [], Value, Type},
Icode#{ functions => [DefaultInit | Funs] } Icode#{ functions => [DefaultInit | Funs] }
end. end.
-spec contract_to_icode(aeso_syntax:ast(), aeso_icode:icode()) -> -spec contract_to_icode(aeso_syntax:ast(), aeso_icode:icode()) ->
aeso_icode:icode(). aeso_icode:icode().
contract_to_icode([{namespace, _, Name, Defs} | Rest], Icode) -> contract_to_icode([{type_def, _Attrib, {id, _, Name}, Args, Def} | Rest],
NS = aeso_icode:get_namespace(Icode),
Icode1 = contract_to_icode(Defs, aeso_icode:enter_namespace(Name, Icode)),
contract_to_icode(Rest, aeso_icode:set_namespace(NS, Icode1));
contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest],
Icode = #{ types := Types, constructors := Constructors }) -> Icode = #{ types := Types, constructors := Constructors }) ->
TypeDef = make_type_def(Args, Def, Icode), TypeDef = make_type_def(Args, Def, Icode),
NewConstructors = NewConstructors =
case Def of case Def of
{variant_t, Cons} -> {variant_t, Cons} ->
Tags = lists:seq(0, length(Cons) - 1), Tags = lists:seq(0, length(Cons) - 1),
GetName = fun({constr_t, _, C, _}) -> C end, GetName = fun({constr_t, _, {con, _, C}, _}) -> C end,
QName = fun(Con) -> {_, _, Xs} = aeso_icode:qualify(GetName(Con), Icode), Xs end, maps:from_list([ {GetName(Con), Tag} || {Tag, Con} <- lists:zip(Tags, Cons) ]);
maps:from_list([ {QName(Con), Tag} || {Tag, Con} <- lists:zip(Tags, Cons) ]);
_ -> #{} _ -> #{}
end, end,
{_, _, TName} = aeso_icode:qualify(Id, Icode), Icode1 = Icode#{ types := Types#{ Name => TypeDef },
Icode1 = Icode#{ types := Types#{ TName => TypeDef },
constructors := maps:merge(Constructors, NewConstructors) }, constructors := maps:merge(Constructors, NewConstructors) },
Icode2 = case Name of Icode2 = case Name of
"state" when Args == [] -> Icode1#{ state_type => ast_typerep(Def, Icode) }; "state" when Args == [] -> Icode1#{ state_type => ast_typerep(Def, Icode) };
@@ -87,7 +68,8 @@ contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest],
contract_to_icode(Rest, Icode2); contract_to_icode(Rest, Icode2);
contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) -> contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) ->
FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++ FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++
[ private || is_private(Attrib, Icode) ], [ private || proplists:get_value(private, Attrib, false) orelse
proplists:get_value(internal, Attrib, false) ],
%% TODO: Handle types %% TODO: Handle types
FunName = ast_id(Name), FunName = ast_id(Name),
%% TODO: push funname to env %% TODO: push funname to env
@@ -102,18 +84,21 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest]
{tuple, [typerep, ast_typerep(T, Icode)]}}; {tuple, [typerep, ast_typerep(T, Icode)]}};
_ -> {ast_body(Body, Icode), ast_typerep(T, Icode)} _ -> {ast_body(Body, Icode), ast_typerep(T, Icode)}
end, end,
QName = aeso_icode:qualify(Name, Icode), NewIcode = ast_fun_to_icode(FunName, 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(_Code, Icode) ->
contract_to_icode(Code, Icode); %% TODO debug output for debug("Unhandled code ~p~n",[Code]),
contract_to_icode([Decl | Code], Icode) -> Icode.
io:format("Unhandled declaration: ~p\n", [Decl]),
contract_to_icode(Code, Icode).
ast_id({id, _, Id}) -> Id; ast_id({id, _, Id}) -> Id.
ast_id({qid, _, Id}) -> Id.
ast_args([{arg, _, Name, Type}|Rest], Acc, Icode) -> ast_args([{arg, _, Name, Type}|Rest], Acc, Icode) ->
ast_args(Rest, [{ast_id(Name), ast_type(Type, Icode)}| Acc], Icode); ast_args(Rest, [{ast_id(Name), ast_type(Type, Icode)}| Acc], Icode);
@@ -136,7 +121,7 @@ ast_type(T, Icode) ->
ast_body(?qid_app(["Chain","spend"], [To, Amount], _, _), Icode) -> ast_body(?qid_app(["Chain","spend"], [To, Amount], _, _), Icode) ->
prim_call(?PRIM_CALL_SPEND, ast_body(Amount, Icode), [ast_body(To, Icode)], [word], {tuple, []}); prim_call(?PRIM_CALL_SPEND, ast_body(Amount, Icode), [ast_body(To, Icode)], [word], {tuple, []});
ast_body(?qid_app([Con, "Chain", "event"], [Event], _, _), Icode = #{ contract_name := Con }) -> ast_body(?qid_app(["Chain","event"], [Event], _, _), Icode) ->
aeso_builtins:check_event_type(Icode), aeso_builtins:check_event_type(Icode),
builtin_call({event, maps:get(event_type, Icode)}, [ast_body(Event, Icode)]); builtin_call({event, maps:get(event_type, Icode)}, [ast_body(Event, Icode)]);
@@ -144,11 +129,10 @@ ast_body(?qid_app([Con, "Chain", "event"], [Event], _, _), Icode = #{ contract_n
ast_body(?qid_app(["Chain", "balance"], [Address], _, _), Icode) -> ast_body(?qid_app(["Chain", "balance"], [Address], _, _), Icode) ->
#prim_balance{ address = ast_body(Address, Icode) }; #prim_balance{ address = ast_body(Address, Icode) };
ast_body(?qid_app(["Chain", "block_hash"], [Height], _, _), Icode) -> ast_body(?qid_app(["Chain", "block_hash"], [Height], _, _), Icode) ->
builtin_call(block_hash, [ast_body(Height, Icode)]); #prim_block_hash{ height = ast_body(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;
@@ -168,22 +152,16 @@ ast_body({qid, _, ["Chain", "spend"]}, _Icode) ->
gen_error({underapplied_primitive, 'Chain.spend'}); gen_error({underapplied_primitive, 'Chain.spend'});
%% State %% State
ast_body({qid, _, [Con, "state"]}, #{ contract_name := Con }) -> prim_state; ast_body({id, _, "state"}, _Icode) -> prim_state;
ast_body(?qid_app([Con, "put"], [NewState], _, _), Icode = #{ contract_name := Con }) -> ast_body(?id_app("put", [NewState], _, _), Icode) ->
#prim_put{ state = ast_body(NewState, Icode) }; #prim_put{ state = ast_body(NewState, Icode) };
ast_body({qid, _, [Con, "put"]}, #{ contract_name := Con }) -> ast_body({id, _, "put"}, _Icode) ->
gen_error({underapplied_primitive, put}); %% TODO: eta gen_error({underapplied_primitive, put}); %% TODO: eta
%% Abort %% Abort
ast_body(?id_app("abort", [String], _, _), Icode) -> ast_body(?id_app("abort", [String], _, _), Icode) ->
builtin_call(abort, [ast_body(String, Icode)]); #funcall{ function = #var_ref{ name = {builtin, abort} },
ast_body(?id_app("require", [Bool, String], _, _), Icode) -> args = [ast_body(String, Icode)] };
builtin_call(require, [ast_body(Bool, Icode), 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) ->
@@ -222,17 +200,6 @@ 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'});
@@ -263,21 +230,21 @@ ast_body(?qid_app(["AENS", "preclaim"], Args, _, _), Icode) ->
[word, word, sign_t()], {tuple, []}); [word, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "claim"], Args, _, _), Icode) -> ast_body(?qid_app(["AENS", "claim"], Args, _, _), Icode) ->
{Sign, [Addr, Name, Salt, NameFee]} = get_signature_arg(Args), {Sign, [Addr, Name, Salt]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_CLAIM, #integer{value = 0}, prim_call(?PRIM_CALL_AENS_CLAIM, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Salt, Icode), ast_body(Sign, Icode), ast_body(NameFee, Icode)], [ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Salt, Icode), ast_body(Sign, Icode)],
[word, string, word, sign_t(), word], {tuple, []}); [word, string, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "transfer"], Args, _, _), Icode) -> ast_body(?qid_app(["AENS", "transfer"], Args, _, _), Icode) ->
{Sign, [FromAddr, ToAddr, Name]} = get_signature_arg(Args), {Sign, [FromAddr, ToAddr, NameHash]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_TRANSFER, #integer{value = 0}, prim_call(?PRIM_CALL_AENS_TRANSFER, #integer{value = 0},
[ast_body(FromAddr, Icode), ast_body(ToAddr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)], [ast_body(FromAddr, Icode), ast_body(ToAddr, Icode), ast_body(NameHash, Icode), ast_body(Sign, Icode)],
[word, word, word, sign_t()], {tuple, []}); [word, word, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "revoke"], Args, _, _), Icode) -> ast_body(?qid_app(["AENS", "revoke"], Args, _, _), Icode) ->
{Sign, [Addr, Name]} = get_signature_arg(Args), {Sign, [Addr, NameHash]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_REVOKE, #integer{value = 0}, prim_call(?PRIM_CALL_AENS_REVOKE, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)], [ast_body(Addr, Icode), ast_body(NameHash, Icode), ast_body(Sign, Icode)],
[word, word, sign_t()], {tuple, []}); [word, word, sign_t()], {tuple, []});
ast_body({qid, _, ["AENS", "resolve"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.resolve'}); ast_body({qid, _, ["AENS", "resolve"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.resolve'});
@@ -349,111 +316,47 @@ ast_body({map, _, Map, [Upd]}, Icode) ->
ast_body({map, Ann, Map, [Upd | Upds]}, Icode) -> ast_body({map, Ann, Map, [Upd | Upds]}, Icode) ->
ast_body({map, Ann, {map, Ann, Map, [Upd]}, Upds}, Icode); ast_body({map, Ann, {map, Ann, Map, [Upd]}, Upds}, Icode);
%% Crypto
ast_body(?qid_app(["Crypto", "ecverify"], [Msg, PK, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[word, word, sign_t()], word);
ast_body(?qid_app(["Crypto", "ecverify_secp256k1"], [Msg, PK, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[bytes_t(32), bytes_t(64), bytes_t(64)], word);
ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode);
ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA256, Term, Type, Icode);
ast_body(?qid_app(["Crypto", "blake2b"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B, Term, Type, Icode);
ast_body(?qid_app(["String", "sha256"], [String], _, _), Icode) ->
string_hash_primop(?PRIM_CALL_CRYPTO_SHA256_STRING, String, Icode);
ast_body(?qid_app(["String", "blake2b"], [String], _, _), Icode) ->
string_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B_STRING, String, Icode);
%% Strings %% Strings
%% -- String length %% -- String length
ast_body(?qid_app(["String", "length"], [String], _, _), Icode) -> ast_body(?qid_app(["String", "length"], [String], _, _), Icode) ->
builtin_call(string_length, [ast_body(String, Icode)]); #funcall{ function = #var_ref{ name = {builtin, string_length} },
args = [ast_body(String, Icode)] };
%% -- String concat %% -- String concat
ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) -> ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) ->
builtin_call(string_concat, [ast_body(String1, Icode), ast_body(String2, Icode)]); #funcall{ function = #var_ref{ name = {builtin, string_concat} },
args = [ast_body(String1, Icode), ast_body(String2, Icode)] };
%% -- String hash (sha3) %% -- String hash (sha3)
ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) -> ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) ->
#unop{ op = 'sha3', rand = ast_body(String, Icode) }; #unop{ op = 'sha3', rand = ast_body(String, Icode) };
%% -- Bits
ast_body(?qid_app(["Bits", Fun], Args, _, _), Icode)
when Fun == "test"; Fun == "set"; Fun == "clear";
Fun == "union"; Fun == "intersection"; Fun == "difference" ->
C = fun(N) when is_integer(N) -> #integer{ value = N };
(X) -> X end,
Bin = fun(O) -> fun(A, B) -> #binop{ op = O, left = C(A), right = C(B) } end end,
And = Bin('band'),
Or = Bin('bor'),
Bsl = fun(A, B) -> (Bin('bsl'))(B, A) end, %% flipped arguments
Bsr = fun(A, B) -> (Bin('bsr'))(B, A) end,
Neg = fun(A) -> #unop{ op = 'bnot', rand = C(A) } end,
case [Fun | [ ast_body(Arg, Icode) || Arg <- Args ]] of
["test", Bits, Ix] -> And(Bsr(Bits, Ix), 1);
["set", Bits, Ix] -> Or(Bits, Bsl(1, Ix));
["clear", Bits, Ix] -> And(Bits, Neg(Bsl(1, Ix)));
["union", A, B] -> Or(A, B);
["intersection", A, B] -> And(A, B);
["difference", A, B] -> And(A, Neg(And(A, B)))
end;
ast_body({qid, _, ["Bits", "none"]}, _Icode) ->
#integer{ value = 0 };
ast_body({qid, _, ["Bits", "all"]}, _Icode) ->
#integer{ value = 1 bsl 256 - 1 };
ast_body(?qid_app(["Bits", "sum"], [Bits], _, _), Icode) ->
builtin_call(popcount, [ast_body(Bits, Icode), #integer{ value = 0 }]);
%% -- Conversion %% -- Conversion
ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) -> ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) ->
builtin_call(int_to_str, [ast_body(Int, Icode)]); builtin_call(int_to_str, [ast_body(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);
ast_body(?qid_app(["Bytes", "to_int"], [Bytes], _, _), Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes,
builtin_call({bytes_to_int, N}, [ast_body(Bytes, Icode)]);
ast_body(?qid_app(["Bytes", "to_str"], [Bytes], _, _), Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes,
builtin_call({bytes_to_str, N}, [ast_body(Bytes, Icode)]);
%% Other terms %% Other terms
ast_body({id, _, Name}, _Icode) -> ast_body({id, _, Name}, _Icode) ->
#var_ref{name = Name}; %% TODO Look up id in env
ast_body({qid, _, Name}, _Icode) ->
#var_ref{name = Name}; #var_ref{name = Name};
ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints
Value = if Bool -> 1 ; true -> 0 end, Value = if Bool -> 1 ; true -> 0 end,
#integer{value = Value}; #integer{value = Value};
ast_body({int, _, Value}, _Icode) -> ast_body({int, _, Value}, _Icode) ->
#integer{value = Value}; #integer{value = Value};
ast_body({bytes, _, Bin}, _Icode) -> ast_body({hash, _, Hash}, _Icode) ->
case aeb_memory:binary_to_words(Bin) of case Hash of
[Word] -> #integer{value = Word}; <<Value:32/unit:8>> -> %% address
Words -> #tuple{cpts = [#integer{value = W} || W <- Words]}
end;
ast_body({Key, _, Bin}, _Icode) when Key == account_pubkey;
Key == contract_pubkey;
Key == oracle_pubkey;
Key == oracle_query_id ->
<<Value:32/unit:8>> = Bin,
#integer{value = Value}; #integer{value = Value};
<<Hi:32/unit:8, Lo:32/unit:8>> -> %% signature
#tuple{cpts = [#integer{value = Hi},
#integer{value = Lo}]}
end;
ast_body({string,_,Bin}, _Icode) -> ast_body({string,_,Bin}, _Icode) ->
Cpts = [size(Bin) | aeb_memory:binary_to_words(Bin)], Cpts = [size(Bin) | aeso_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]};
@@ -477,7 +380,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>> = aeb_aevm_abi:function_type_hash(list_to_binary(FunName), ArgType, OutType), <<TypeHash:256>> = aeso_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{
@@ -494,15 +397,9 @@ ast_body({proj, _, {typed, _, _, {con, _, Contract}}, {id, _, FunName}}, _Icode)
string:join([Contract, FunName], ".")}); string:join([Contract, FunName], ".")});
ast_body({con, _, Name}, Icode) -> ast_body({con, _, Name}, Icode) ->
Tag = aeso_icode:get_constructor_tag([Name], Icode),
#tuple{cpts = [#integer{value = Tag}]};
ast_body({qcon, _, Name}, Icode) ->
Tag = aeso_icode:get_constructor_tag(Name, Icode), Tag = aeso_icode:get_constructor_tag(Name, Icode),
#tuple{cpts = [#integer{value = Tag}]}; #tuple{cpts = [#integer{value = Tag}]};
ast_body({app, _, {typed, _, {con, _, Name}, _}, Args}, Icode) -> ast_body({app, _, {typed, _, {con, _, Name}, _}, Args}, Icode) ->
Tag = aeso_icode:get_constructor_tag([Name], Icode),
#tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]};
ast_body({app, _, {typed, _, {qcon, _, Name}, _}, Args}, Icode) ->
Tag = aeso_icode:get_constructor_tag(Name, Icode), Tag = aeso_icode:get_constructor_tag(Name, Icode),
#tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]}; #tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]};
ast_body({app,As,Fun,Args}, Icode) -> ast_body({app,As,Fun,Args}, Icode) ->
@@ -532,8 +429,6 @@ 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) ->
@@ -601,30 +496,25 @@ 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)};
OtherType -> string ->
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,
Args = [ast_body(A, Icode), ast_body(B, Icode)], Neg(#funcall{ function = #var_ref{name = {builtin, str_equal}},
Builtin = args = [ast_body(A, Icode), ast_body(B, Icode)] });
case OtherType of
string ->
builtin_call(str_equal, Args);
{tuple, Types} ->
case lists:usort(Types) of
[word] ->
builtin_call(str_equal_p, [ #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) ->
builtin_call(list_concat, [ast_body(A, Icode), ast_body(B, Icode)]); #funcall{ function = #var_ref{ name = {builtin, list_concat} },
args = [ast_body(A, Icode), ast_body(B, Icode)] };
ast_binop('bsl', _, A, B, Icode) ->
#binop{op = '*', left = ast_body(A, Icode),
right = #binop{op = '^', left = {integer, 2}, right = ast_body(B, Icode)}};
ast_binop('bsr', _, A, B, Icode) ->
#binop{op = 'div', left = ast_body(A, Icode),
right = #binop{op = '^', left = {integer, 2}, right = ast_body(B, Icode)}};
ast_binop(Op, _, A, B, Icode) -> ast_binop(Op, _, A, B, Icode) ->
#binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}. #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}.
@@ -691,7 +581,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>> = aeb_aevm_abi:function_type_hash(PrimBin, ArgType, OutType), <<TH:256>> = aeso_abi:function_type_hash(PrimBin, ArgType, OutType),
TH; TH;
false -> false ->
0 0
@@ -703,16 +593,6 @@ prim_call(Prim, Amount, Args, ArgTypes, OutType) ->
type_hash= #integer{value = TypeHash} type_hash= #integer{value = TypeHash}
}. }.
generic_hash_primop(PrimOp, Term, Type, Icode) ->
ArgType = ast_type(Type, Icode),
TypeValue = type_value(ArgType),
prim_call(PrimOp, #integer{value = 0},
[TypeValue, ast_body(Term, Icode)],
[typerep, ArgType], word).
string_hash_primop(PrimOp, String, Icode) ->
prim_call(PrimOp, #integer{value = 0}, [ast_body(String, Icode)], [string], word).
make_type_def(Args, Def, Icode = #{ type_vars := TypeEnv }) -> make_type_def(Args, Def, Icode = #{ type_vars := TypeEnv }) ->
TVars = [ X || {tvar, _, X} <- Args ], TVars = [ X || {tvar, _, X} <- Args ],
fun(Types) -> fun(Types) ->
@@ -720,7 +600,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()) -> aeb_aevm_data:type(). -spec ast_typerep(aeso_syntax:type()) -> aeso_sophia: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) ->
@@ -729,9 +609,7 @@ 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}, _) -> ast_typerep({app_t, _, {id, _, Name}, Args}, Icode) ->
bytes_t(Len);
ast_typerep({app_t, _, {I, _, Name}, Args}, Icode) when I =:= id; I =:= qid ->
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);
ast_typerep({tvar,_,A}, #{ type_vars := TypeVars }) -> ast_typerep({tvar,_,A}, #{ type_vars := TypeVars }) ->
@@ -758,9 +636,8 @@ 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() -> bytes_t(64). sign_t() ->
bytes_t(Len) when Len =< 32 -> word; {tuple, [word, 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],
@@ -820,13 +697,6 @@ has_maps({list, T}) -> has_maps(T);
has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts); has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts);
has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)). has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)).
%% A function is private if not an 'entrypoint', or if it's not defined in the
%% main contract name space. (NOTE: changes when we introduce inheritance).
is_private(Ann, #{ contract_name := MainContract } = Icode) ->
{_, _, CurrentNamespace} = aeso_icode:get_namespace(Icode),
not proplists:get_value(entrypoint, Ann, false) orelse
MainContract /= CurrentNamespace.
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
%% Builtins %% Builtins
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
@@ -838,39 +708,3 @@ builtin_call(Builtin, Args) ->
add_builtins(Icode = #{functions := Funs}) -> add_builtins(Icode = #{functions := Funs}) ->
Builtins = aeso_builtins:used_builtins(Funs), Builtins = aeso_builtins:used_builtins(Funs),
Icode#{functions := [ aeso_builtins:builtin_function(B) || B <- Builtins ] ++ Funs}. Icode#{functions := [ aeso_builtins:builtin_function(B) || B <- Builtins ] ++ Funs}.
%% -------------------------------------------------------------------
%% Deadcode elimination
%% -------------------------------------------------------------------
deadcode_elimination(Icode = #{ functions := Funs }) ->
PublicNames = [ Name || {Name, Ann, _, _, _} <- Funs, not lists:member(private, Ann) ],
ArgsToPat = fun(Args) -> [ #var_ref{ name = X } || {X, _} <- Args ] end,
Defs = maps:from_list([ {Name, {binder, ArgsToPat(Args), Body}} || {Name, _, Args, Body, _} <- Funs ]),
UsedNames = chase_names(Defs, PublicNames, #{}),
UsedFuns = [ Def || Def = {Name, _, _, _, _} <- Funs, maps:is_key(Name, UsedNames) ],
Icode#{ functions := UsedFuns }.
chase_names(_Defs, [], Used) -> Used;
chase_names(Defs, [X | Xs], Used) ->
%% can happen when compiling __call contracts
case maps:is_key(X, Used) orelse not maps:is_key(X, Defs) of
true -> chase_names(Defs, Xs, Used); %% already chased
false ->
Def = maps:get(X, Defs),
Vars = maps:keys(free_vars(Def)),
chase_names(Defs, Vars ++ Xs, Used#{ X => true })
end.
free_vars(#var_ref{ name = X }) -> #{ X => true };
free_vars(#arg{ name = X }) -> #{ X => true };
free_vars({binder, Pat, Body}) ->
maps:without(maps:keys(free_vars(Pat)), free_vars(Body));
free_vars(#switch{ expr = E, cases = Cases }) ->
free_vars([E | [{binder, P, B} || {P, B} <- Cases]]);
free_vars(#lambda{ args = Xs, body = E }) ->
free_vars({binder, Xs, E});
free_vars(T) when is_tuple(T) -> free_vars(tuple_to_list(T));
free_vars([H | T]) -> maps:merge(free_vars(H), free_vars(T));
free_vars(_) -> #{}.
+149
View File
@@ -0,0 +1,149 @@
%%%=============================================================================
%%% @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>>).
+53 -147
View File
@@ -38,15 +38,13 @@ builtin_deps1({map_upd, Type}) -> [{map_get, Type}, map_put];
builtin_deps1({map_upd_default, Type}) -> [{map_lookup_default, Type}, map_put]; builtin_deps1({map_upd_default, Type}) -> [{map_lookup_default, Type}, map_put];
builtin_deps1(map_from_list) -> [map_put]; builtin_deps1(map_from_list) -> [map_put];
builtin_deps1(str_equal) -> [str_equal_p]; builtin_deps1(str_equal) -> [str_equal_p];
builtin_deps1(string_concat) -> [string_concat_inner1, string_copy, string_shift_copy]; builtin_deps1(string_concat) -> [string_concat_inner1, string_concat_inner2];
builtin_deps1(int_to_str) -> [{baseX_int, 10}]; builtin_deps1(int_to_str) -> [{baseX_int, 10}];
builtin_deps1(addr_to_str) -> [{baseX_int, 58}]; builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}]; builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}]; builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}];
builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}]; builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}];
builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker];
builtin_deps1(string_reverse) -> [string_reverse_]; builtin_deps1(string_reverse) -> [string_reverse_];
builtin_deps1(require) -> [abort];
builtin_deps1(_) -> []. builtin_deps1(_) -> [].
dep_closure(Deps) -> dep_closure(Deps) ->
@@ -62,14 +60,12 @@ v(X) when is_list(X) -> #var_ref{name = X}.
option_none() -> {tuple, [{integer, 0}]}. option_none() -> {tuple, [{integer, 0}]}.
option_some(X) -> {tuple, [{integer, 1}, X]}. option_some(X) -> {tuple, [{integer, 1}, X]}.
-define(HASH_BYTES, 32).
-define(call(Fun, Args), #funcall{ function = #var_ref{ name = {builtin, Fun} }, args = Args }). -define(call(Fun, Args), #funcall{ function = #var_ref{ name = {builtin, Fun} }, args = Args }).
-define(I(X), {integer, X}). -define(I(X), {integer, X}).
-define(V(X), v(X)). -define(V(X), v(X)).
-define(A(Op), aeb_opcodes:mnemonic(Op)). -define(A(Op), aeb_opcodes:mnemonic(Op)).
-define(LET(Var, Expr, Body), {switch, Expr, [{v(Var), Body}]}). -define(LET(Var, Expr, Body), {switch, Expr, [{v(Var), Body}]}).
-define(DEREF(Var, Ptr, Body), {switch, operand(Ptr), [{{tuple, [v(Var)]}, Body}]}). -define(DEREF(Var, Ptr, Body), {switch, v(Ptr), [{{tuple, [v(Var)]}, Body}]}).
-define(NXT(Ptr), op('+', Ptr, 32)). -define(NXT(Ptr), op('+', Ptr, 32)).
-define(NEG(A), op('/', A, {unop, '-', {integer, 1}})). -define(NEG(A), op('/', A, {unop, '-', {integer, 1}})).
-define(BYTE(Ix, Word), op('byte', Ix, Word)). -define(BYTE(Ix, Word), op('byte', Ix, Word)).
@@ -85,9 +81,8 @@ option_some(X) -> {tuple, [{integer, 1}, X]}.
-define(EXP(A, B), op('^', A, B)). -define(EXP(A, B), op('^', A, B)).
-define(AND(A, B), op('&&', A, B)). -define(AND(A, B), op('&&', A, B)).
%% Bit shift operations takes their arguments backwards!? -define(BSL(X, B), ?MUL(X, ?EXP(2, ?MUL(B, 8)))).
-define(BSL(X, B), op('bsl', ?MUL(B, 8), X)). -define(BSR(X, B), ?DIV(X, ?EXP(2, ?MUL(B, 8)))).
-define(BSR(X, B), op('bsr', ?MUL(B, 8), X)).
op(Op, A, B) -> {binop, Op, operand(A), operand(B)}. op(Op, A, B) -> {binop, Op, operand(A), operand(B)}.
@@ -95,6 +90,12 @@ operand(A) when is_atom(A) -> v(A);
operand(I) when is_integer(I) -> {integer, I}; operand(I) when is_integer(I) -> {integer, I};
operand(T) -> T. operand(T) -> T.
str_to_icode(String) when is_list(String) ->
str_to_icode(list_to_binary(String));
str_to_icode(BinStr) ->
Cpts = [size(BinStr) | aeso_memory:binary_to_words(BinStr)],
#tuple{ cpts = [ #integer{value = X} || X <- Cpts ] }.
check_event_type(Icode) -> check_event_type(Icode) ->
case maps:get(event_type, Icode) of case maps:get(event_type, Icode) of
{variant_t, Cons} -> {variant_t, Cons} ->
@@ -104,23 +105,21 @@ check_event_type(Icode) ->
end. end.
check_event_type(Evts, Icode) -> check_event_type(Evts, Icode) ->
[ check_event_type(Name, Ix, T, Icode) [ check_event_type(Name, T, Icode)
|| {constr_t, Ann, {con, _, Name}, Types} <- Evts, || {constr_t, _, {con, _, Name}, Types} <- Evts, T <- Types ].
{Ix, T} <- lists:zip(aeso_syntax:get_ann(indices, Ann), Types) ].
check_event_type(EvtName, Ix, Type, Icode) -> check_event_type(EvtName, Type, Icode) ->
VMType = VMType =
try try
aeso_ast_to_icode:ast_typerep(Type, Icode) aeso_ast_to_icode:ast_typerep(Type, Icode)
catch _:_ -> catch _:_ ->
error({EvtName, could_not_resolve_type, Type}) error({EvtName, could_not_resolve_type, Type})
end, end,
case {Ix, VMType, Type} of case aeso_syntax:get_ann(indexed, Type, false) of
{indexed, word, _} -> ok; true when VMType == word -> ok;
{notindexed, string, _} -> ok; false when VMType == string -> ok;
{notindexed, _, {bytes_t, _, N}} when N > 32 -> ok; true -> error({EvtName, indexed_field_should_be_word, is, VMType});
{indexed, _, _} -> error({EvtName, indexed_field_should_be_word, is, VMType}); false -> error({EvtName, payload_should_be_string, is, VMType})
{notindexed, _, _} -> error({EvtName, payload_should_be_string, is, VMType})
end. end.
bfun(B, {IArgs, IExpr, IRet}) -> bfun(B, {IArgs, IExpr, IRet}) ->
@@ -130,8 +129,6 @@ builtin_function(BF) ->
case BF of case BF of
{event, EventT} -> bfun(BF, builtin_event(EventT)); {event, EventT} -> bfun(BF, builtin_event(EventT));
abort -> bfun(BF, builtin_abort()); abort -> bfun(BF, builtin_abort());
block_hash -> bfun(BF, builtin_block_hash());
require -> bfun(BF, builtin_require());
{map_lookup, Type} -> bfun(BF, builtin_map_lookup(Type)); {map_lookup, Type} -> bfun(BF, builtin_map_lookup(Type));
map_put -> bfun(BF, builtin_map_put()); map_put -> bfun(BF, builtin_map_put());
map_delete -> bfun(BF, builtin_map_delete()); map_delete -> bfun(BF, builtin_map_delete());
@@ -146,11 +143,9 @@ builtin_function(BF) ->
string_length -> bfun(BF, builtin_string_length()); string_length -> bfun(BF, builtin_string_length());
string_concat -> bfun(BF, builtin_string_concat()); string_concat -> bfun(BF, builtin_string_concat());
string_concat_inner1 -> bfun(BF, builtin_string_concat_inner1()); string_concat_inner1 -> bfun(BF, builtin_string_concat_inner1());
string_copy -> bfun(BF, builtin_string_copy()); string_concat_inner2 -> bfun(BF, builtin_string_concat_inner2());
string_shift_copy -> bfun(BF, builtin_string_shift_copy());
str_equal_p -> bfun(BF, builtin_str_equal_p()); str_equal_p -> bfun(BF, builtin_str_equal_p());
str_equal -> bfun(BF, builtin_str_equal()); str_equal -> bfun(BF, builtin_str_equal());
popcount -> bfun(BF, builtin_popcount());
int_to_str -> bfun(BF, builtin_int_to_str()); int_to_str -> bfun(BF, builtin_int_to_str());
addr_to_str -> bfun(BF, builtin_addr_to_str()); addr_to_str -> bfun(BF, builtin_addr_to_str());
{baseX_int, X} -> bfun(BF, builtin_baseX_int(X)); {baseX_int, X} -> bfun(BF, builtin_baseX_int(X));
@@ -159,9 +154,6 @@ builtin_function(BF) ->
{baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X)); {baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X));
{baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X)); {baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X));
{baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X)); {baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X));
{bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N));
{bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N));
bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker());
string_reverse -> bfun(BF, builtin_string_reverse()); string_reverse -> bfun(BF, builtin_string_reverse());
string_reverse_ -> bfun(BF, builtin_string_reverse_()) string_reverse_ -> bfun(BF, builtin_string_reverse_())
end. end.
@@ -174,24 +166,18 @@ builtin_event(EventT) ->
A = fun(X) -> aeb_opcodes:mnemonic(X) end, A = fun(X) -> aeb_opcodes:mnemonic(X) end,
VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end, VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end,
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end, ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
IsIndexed = fun(T) -> aeso_syntax:get_ann(indexed, T, false) end,
Payload = %% Should put data ptr, length on stack. Payload = %% Should put data ptr, length on stack.
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]}; fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
([{{id, _, "string"}, V}]) -> ([V]) -> {seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
{seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]} %% ptr+32, length
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]}; %% ptr+32, length
([{{bytes_t, _, N}, V}]) -> {seq, [V, {integer, N}, {inline_asm, A(?SWAP1)}]}
end, end,
Ix =
fun({bytes_t, _, N}, V) when N < 32 -> ?BSR(V, 32 - N);
(_, V) -> V end,
Clause = Clause =
fun(_Tag, {con, _, Con}, IxTypes) -> fun(_Tag, {con, _, Con}, Types) ->
Types = [ T || {_Ix, T} <- IxTypes ], Indexed = [ Var || {Var, Type} <- lists:zip(ArgPats(Types), Types),
Indexed = [ Ix(Type, Var) || {Var, {indexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ], IsIndexed(Type) ],
Data = [ {Type, Var} || {Var, {notindexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ], EvtIndex = {unop, 'sha3', str_to_icode(Con)},
{ok, <<EvtIndexN:256>>} = eblake2:blake2b(?HASH_BYTES, list_to_binary(Con)), {event, lists:reverse(Indexed) ++ [EvtIndex], Payload(ArgPats(Types) -- Indexed)}
EvtIndex = {integer, EvtIndexN},
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(Data)}
end, end,
Pat = fun(Tag, Types) -> {tuple, [{integer, Tag} | ArgPats(Types)]} end, Pat = fun(Tag, Types) -> {tuple, [{integer, Tag} | ArgPats(Types)]} end,
@@ -200,8 +186,8 @@ builtin_event(EventT) ->
{[{"e", event}], {[{"e", event}],
{switch, v(e), {switch, v(e),
[{Pat(Tag, Types), Clause(Tag, Con, lists:zip(aeso_syntax:get_ann(indices, Ann), Types))} [{Pat(Tag, Types), Clause(Tag, Con, Types)}
|| {Tag, {constr_t, Ann, Con, Types}} <- lists:zip(Tags, Cons) ]}, || {Tag, {constr_t, _, Con, Types}} <- lists:zip(Tags, Cons) ]},
{tuple, []}}. {tuple, []}}.
%% Abort primitive. %% Abort primitive.
@@ -212,17 +198,6 @@ builtin_abort() ->
A(?REVERT)]}, %% Stack: 0,Ptr A(?REVERT)]}, %% Stack: 0,Ptr
{tuple,[]}}. {tuple,[]}}.
builtin_block_hash() ->
{[{"height", word}],
?LET(hash, #prim_block_hash{ height = ?V(height)},
{ifte, ?EQ(hash, 0), option_none(), option_some(?V(hash))}),
aeso_icode:option_typerep(word)}.
builtin_require() ->
{[{"c", word}, {"msg", string}],
{ifte, ?V(c), {tuple, []}, ?call(abort, [?V(msg)])},
{tuple, []}}.
%% Map primitives %% Map primitives
builtin_map_lookup(Type) -> builtin_map_lookup(Type) ->
Ret = aeso_icode:option_typerep(Type), Ret = aeso_icode:option_typerep(Type),
@@ -346,8 +321,6 @@ builtin_string_concat() ->
{[{"s1", string}, {"s2", string}], {[{"s1", string}, {"s2", string}],
?DEREF(n1, s1, ?DEREF(n1, s1,
?DEREF(n2, s2, ?DEREF(n2, s2,
{ifte, ?EQ(n1, 0),
?V(s2), %% First string is empty return second string
{ifte, ?EQ(n2, 0), {ifte, ?EQ(n2, 0),
?V(s1), %% Second string is empty return first string ?V(s1), %% Second string is empty return first string
?LET(ret, {inline_asm, [?A(?MSIZE)]}, ?LET(ret, {inline_asm, [?A(?MSIZE)]},
@@ -356,7 +329,6 @@ builtin_string_concat() ->
{inline_asm, [?A(?POP)]}, %% Discard fun ret val {inline_asm, [?A(?POP)]}, %% Discard fun ret val
?V(ret) %% Put the actual return value ?V(ret) %% Put the actual return value
]})} ]})}
}
)), )),
word}. word}.
@@ -364,33 +336,33 @@ builtin_string_concat_inner1() ->
%% Copy all whole words from the first string, and set up for word fusion %% Copy all whole words from the first string, and set up for word fusion
%% Special case when the length of the first string is divisible by 32. %% Special case when the length of the first string is divisible by 32.
{[{"n1", word}, {"p1", pointer}, {"n2", word}, {"p2", pointer}], {[{"n1", word}, {"p1", pointer}, {"n2", word}, {"p2", pointer}],
?LET(w1, ?call(string_copy, [?V(n1), ?V(p1)]), ?DEREF(w1, p1,
?LET(nx, ?MOD(n1, 32), {ifte, ?GT(n1, 32),
{ifte, ?EQ(nx, 0), {seq, [?V(w1), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?LET(w2, ?call(string_copy, [?V(n2), ?V(p2)]), ?call(string_concat_inner1, [?SUB(n1, 32), ?NXT(p1), ?V(n2), ?V(p2)])]},
{seq, [?V(w2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}), {ifte, ?EQ(n1, 0),
?call(string_shift_copy, [?V(nx), ?V(w1), ?V(n2), ?V(p2)]) ?call(string_concat_inner2, [?I(32), ?I(0), ?V(n2), ?V(p2)]),
})), ?call(string_concat_inner2, [?SUB(32, n1), ?V(w1), ?V(n2), ?V(p2)])}
word}.
builtin_string_copy() ->
{[{"n", word}, {"p", pointer}],
?DEREF(w, p,
{ifte, ?GT(n, 31),
{seq, [?V(w), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call(string_copy, [?SUB(n, 32), ?NXT(p)])]},
?V(w)
}), }),
word}. word}.
builtin_string_shift_copy() -> builtin_string_concat_inner2() ->
{[{"off", word}, {"dst", word}, {"n", word}, {"p", pointer}], %% Current "work in progess" word 'x', has 'o' bytes that are "free" - fill them from
?DEREF(w, p, %% words of the second string.
{seq, [?ADD(dst, ?BSR(w, off)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, {[{"o", word}, {"x", word}, {"n2", word}, {"p2", pointer}],
{ifte, ?GT(n, ?SUB(32, off)), {ifte, ?LT(n2, 1),
?call(string_shift_copy, [?V(off), ?BSL(w, ?SUB(32, off)), ?SUB(n, 32), ?NXT(p)]), {seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}, %% Use MSIZE as dummy return value
{inline_asm, [?A(?MSIZE)]}}] ?DEREF(w2, p2,
}), {ifte, ?GT(n2, o),
{seq, [?ADD(x, ?BSR(w2, ?SUB(32, o))),
{inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call(string_concat_inner2,
[?V(o), ?BSL(w2, o), ?SUB(n2, 32), ?NXT(p2)])
]},
{seq, [?ADD(x, ?BSR(w2, ?SUB(32, o))),
{inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]} %% Use MSIZE as dummy return value
})
},
word}. word}.
builtin_str_equal_p() -> builtin_str_equal_p() ->
@@ -421,17 +393,6 @@ builtin_str_equal() ->
)), )),
word}. word}.
%% Count the number of 1s in a bit field.
builtin_popcount() ->
%% function popcount(bits, acc) =
%% if (bits == 0) acc
%% else popcount(bits bsr 1, acc + bits band 1)
{[{"bits", word}, {"acc", word}],
{ifte, ?EQ(bits, 0),
?V(acc),
?call(popcount, [op('bsr', 1, bits), ?ADD(acc, op('band', bits, 1))])
}, word}.
builtin_int_to_str() -> builtin_int_to_str() ->
{[{"i", word}], ?call({baseX_int, 10}, [?V(i)]), word}. {[{"i", word}], ?call({baseX_int, 10}, [?V(i)]), word}.
@@ -459,10 +420,6 @@ builtin_baseX_int_pad(X = 10) ->
?call({baseX_int_encode, X}, [?NEG(src), ?I(1), ?BSL($-, 31)]), ?call({baseX_int_encode, X}, [?NEG(src), ?I(1), ?BSL($-, 31)]),
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)])}, ?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)])},
word}; word};
builtin_baseX_int_pad(X = 16) ->
{[{"src", word}, {"ix", word}, {"dst", word}],
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
word};
builtin_baseX_int_pad(X = 58) -> builtin_baseX_int_pad(X = 58) ->
{[{"src", word}, {"ix", word}, {"dst", word}], {[{"src", word}, {"ix", word}, {"dst", word}],
{ifte, ?GT(?ADD(?DIV(ix, 31), ?BYTE(ix, src)), 0), {ifte, ?GT(?ADD(?DIV(ix, 31), ?BYTE(ix, src)), 0),
@@ -497,57 +454,6 @@ builtin_baseX_digits(X) ->
{ifte, ?EQ(x1, 0), ?V(dgts), ?call({baseX_digits, X}, [?V(x1), ?ADD(dgts, 1)])}), {ifte, ?EQ(x1, 0), ?V(dgts), ?call({baseX_digits, X}, [?V(x1), ?ADD(dgts, 1)])}),
word}. word}.
builtin_bytes_to_int(32) ->
{[{"w", word}], ?V(w), word};
builtin_bytes_to_int(N) when N < 32 ->
{[{"w", word}], ?BSR(w, 32 - N), word};
builtin_bytes_to_int(N) when N > 32 ->
LastFullWord = N div 32 - 1,
Body = case N rem 32 of
0 -> ?DEREF(n, ?ADD(b, LastFullWord * 32), ?V(n));
R ->
?DEREF(hi, ?ADD(b, LastFullWord * 32),
?DEREF(lo, ?ADD(b, (LastFullWord + 1) * 32),
?ADD(?BSR(lo, 32 - R), ?BSL(hi, R))))
end,
{[{"b", pointer}], Body, word}.
builtin_bytes_to_str_worker() ->
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
{[{"w", word}, {"offs", word}, {"acc", word}],
{seq, [{ifte, ?AND(?GT(offs, 0), ?EQ(0, ?MOD(offs, 16))),
{seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}]},
{inline_asm, []}},
{ifte, ?EQ(offs, 32), {inline_asm, [?A(?MSIZE)]},
?LET(b, ?BYTE(offs, w),
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?call(bytes_to_str_worker,
[?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))]))))
}
]},
word}.
builtin_bytes_to_str(N) when N =< 32 ->
{[{"w", word}],
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call(bytes_to_str_worker, [?V(w), ?I(0), ?I(0)]),
{inline_asm, [?A(?POP)]},
?V(ret)]}),
string};
builtin_bytes_to_str(N) when N > 32 ->
Work = fun(I) ->
[?DEREF(w, ?ADD(p, 32 * I), ?call(bytes_to_str_worker, [?V(w), ?I(0), ?I(0)])),
{inline_asm, [?A(?POP)]}]
end,
{[{"p", pointer}],
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
lists:append([ Work(I) || I <- lists:seq(0, (N + 31) div 32 - 1) ]) ++
[?V(ret)]}),
string}.
builtin_string_reverse() -> builtin_string_reverse() ->
{[{"s", string}], {[{"s", string}],
?DEREF(n, s, ?DEREF(n, s,
+85 -408
View File
@@ -11,92 +11,68 @@
-export([ file/1 -export([ file/1
, file/2 , file/2
, from_string/2 , from_string/2
, check_call/4 , check_call/2
, create_calldata/3 %% deprecated , create_calldata/3
, create_calldata/4
, version/0 , version/0
, sophia_type_to_typerep/1 , sophia_type_to_typerep/1
, to_sophia_value/4 %% deprecated, need a backend
, to_sophia_value/5
, decode_calldata/3 %% deprecated
, decode_calldata/4
, parse/2
, add_include_path/2
]). ]).
-include_lib("aebytecode/include/aeb_opcodes.hrl"). -include_lib("aebytecode/include/aeb_opcodes.hrl").
-include("aeso_icode.hrl"). -include("aeso_icode.hrl").
-type option() :: pp_sophia_code -type option() :: pp_sophia_code | pp_ast | pp_types | pp_typed_ast |
| pp_ast pp_icode| pp_assembler | pp_bytecode.
| pp_types
| pp_typed_ast
| pp_icode
| pp_assembler
| pp_bytecode
| no_code
| {backend, aevm | fate}
| {include, {file_system, [string()]} |
{explicit_files, #{string() => binary()}}}
| {src_file, string()}.
-type options() :: [option()]. -type options() :: [option()].
-export_type([ option/0 -export_type([ option/0
, options/0 , options/0
]). ]).
-spec version() -> {ok, binary()} | {error, term()}. -define(COMPILER_VERSION_1, 1).
-define(COMPILER_VERSION_2, 2).
-define(COMPILER_VERSION, ?COMPILER_VERSION_2).
-spec version() -> pos_integer().
version() -> version() ->
case lists:keyfind(aesophia, 1, application:loaded_applications()) of ?COMPILER_VERSION.
false ->
case application:load(aesophia) of
ok ->
case application:get_key(aesophia, vsn) of
{ok, VsnString} ->
{ok, list_to_binary(VsnString)};
undefined ->
{error, failed_to_load_aesophia}
end;
Err = {error, _} ->
Err
end;
{_App, _Des, VsnString} ->
{ok, list_to_binary(VsnString)}
end.
-spec file(string()) -> {ok, map()} | {error, binary()}. -spec file(string()) -> {ok, map()} | {error, binary()}.
file(Filename) -> file(Filename) ->
file(Filename, []). file(Filename, []).
-spec file(string(), options()) -> {ok, map()} | {error, binary()}. -spec file(string(), options()) -> {ok, map()} | {error, binary()}.
file(File, Options0) -> file(File, Options) ->
Options = add_include_path(File, Options0),
case read_contract(File) of case read_contract(File) of
{ok, Bin} -> from_string(Bin, [{src_file, File} | Options]); {ok, Bin} -> from_string(Bin, Options);
{error, Error} -> {error, Error} -> {error, {File, Error}}
ErrorString = [File,": ",file:format_error(Error)],
{error, join_errors("File errors", [ErrorString], fun(E) -> E end)}
end.
add_include_path(File, Options) ->
case lists:keymember(include, 1, Options) of
true -> Options;
false ->
Dir = filename:dirname(File),
{ok, Cwd} = file:get_cwd(),
[{include, {file_system, [Cwd, Dir]}} | Options]
end. end.
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}. -spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}.
from_string(Contract, Options) -> from_string(ContractBin, Options) when is_binary(ContractBin) ->
from_string(proplists:get_value(backend, Options, aevm), Contract, Options). from_string(binary_to_list(ContractBin), Options);
from_string(ContractString, Options) ->
from_string(Backend, ContractBin, Options) when is_binary(ContractBin) ->
from_string(Backend, binary_to_list(ContractBin), Options);
from_string(Backend, ContractString, Options) ->
try try
from_string1(Backend, ContractString, Options) Ast = parse(ContractString, Options),
ok = pp_sophia_code(Ast, Options),
ok = pp_ast(Ast, Options),
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
%% pp_types is handled inside aeso_ast_infer_types.
ok = pp_typed_ast(TypedAst, Options),
ICode = to_icode(TypedAst, Options),
TypeInfo = extract_type_info(ICode),
ok = pp_icode(ICode, Options),
Assembler = assemble(ICode, Options),
ok = pp_assembler(Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
ok = pp_bytecode(ByteCode, Options),
{ok, #{byte_code => ByteCode,
compiler_version => version(),
contract_source => ContractString,
type_info => TypeInfo
}}
catch catch
%% The compiler errors. %% The compiler errors.
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
@@ -109,124 +85,38 @@ from_string(Backend, ContractString, Options) ->
%% General programming errors in the compiler just signal error. %% General programming errors in the compiler just signal error.
end. end.
from_string1(aevm, ContractString, Options) ->
#{icode := Icode} = string_to_code(ContractString, Options),
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options),
{ok, Version} = version(),
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo,
abi_version => aeb_aevm_abi:abi_version()
}};
from_string1(fate, ContractString, Options) ->
#{fcode := FCode} = string_to_code(ContractString, Options),
FateCode = aeso_fcode_to_fate:compile(FCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(),
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => [],
fate_code => FateCode,
abi_version => aeb_fate_abi:abi_version()
}}.
-spec string_to_code(string(), options()) -> map().
string_to_code(ContractString, Options) ->
Ast = parse(ContractString, Options),
pp_sophia_code(Ast, Options),
pp_ast(Ast, Options),
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]),
pp_typed_ast(TypedAst, Options),
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = ast_to_icode(TypedAst, Options),
pp_icode(Icode, Options),
#{ icode => Icode,
typed_ast => TypedAst,
type_env => TypeEnv};
fate ->
Fcode = aeso_ast_to_fcode:ast_to_fcode(TypedAst, Options),
#{ fcode => Fcode,
typed_ast => TypedAst,
type_env => TypeEnv}
end.
join_errors(Prefix, Errors, Pfun) -> 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")).
-define(CALL_NAME, "__call"). -define(CALL_NAME, "__call").
-define(DECODE_NAME, "__decode").
%% Takes a string containing a contract with a declaration/prototype of a %% Takes a string containing a contract with a declaration/prototype of a
%% function (foo, say) and adds function __call() = foo(args) calling this %% function (foo, say) and a function __call() = foo(args) calling this
%% function. Returns the name of the called functions, typereps and Erlang %% function. Returns the name of the called functions, typereps and Erlang
%% terms for the arguments. %% terms for the arguments.
%% NOTE: Special treatment for "init" since it might be implicit and has -spec check_call(string(), options()) -> {ok, string(), {[Type], Type | any}, [term()]} | {error, term()}
%% a special return type (typerep, T)
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]}
| {ok, string(), [term()]}
| {error, term()}
when Type :: term(). when Type :: term().
check_call(Source, "init" = FunName, Args, Options) -> check_call(ContractString, Options) ->
case check_call1(Source, FunName, Args, Options) of
Err = {error, _} when Args == [] ->
%% Try with default init-function
case check_call1(insert_init_function(Source, Options), FunName, Args, Options) of
{error, _} -> Err; %% The first error is most likely better...
Res -> Res
end;
Res ->
Res
end;
check_call(Source, FunName, Args, Options) ->
check_call1(Source, FunName, Args, Options).
check_call1(ContractString0, FunName, Args, Options) ->
try try
case proplists:get_value(backend, Options, aevm) of Ast = parse(ContractString, Options),
aevm -> ok = pp_sophia_code(Ast, Options),
%% First check the contract without the __call function ok = pp_ast(Ast, Options),
#{} = string_to_code(ContractString0, Options), TypedAst = aeso_ast_infer_types:infer(Ast, [permissive_address_literals]),
ContractString = insert_call_function(ContractString0, ?CALL_NAME, FunName, Args, Options),
#{typed_ast := TypedAst,
icode := Icode} = string_to_code(ContractString, Options),
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst), {ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
ok = pp_typed_ast(TypedAst, Options),
Icode = to_icode(TypedAst, Options),
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
{id, _, "_"} -> any; {id, _, "_"} -> any;
_ -> aeso_ast_to_icode:ast_typerep(RetType, Icode) _ -> aeso_ast_to_icode:ast_typerep(RetType, Icode)
end, end,
ok = pp_icode(Icode, Options),
#{ functions := Funs } = Icode, #{ functions := Funs } = Icode,
ArgIcode = get_arg_icode(Funs), ArgIcode = get_arg_icode(Funs),
ArgTerms = [ icode_to_term(T, Arg) || ArgTerms = [ icode_to_term(T, Arg) ||
{T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ], {T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
RetVMType1 = {ok, FunName, {ArgVMTypes, RetVMType}, ArgTerms}
case FunName of
"init" -> {tuple, [typerep, RetVMType]};
_ -> RetVMType
end,
{ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms};
fate ->
%% First check the contract without the __call function
#{fcode := OrgFcode} = string_to_code(ContractString0, Options),
FateCode = aeso_fcode_to_fate:compile(OrgFcode, []),
%% collect all hashes and compute the first name without hash collision to
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
CallName = first_none_match(?CALL_NAME, SymbolHashes,
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
ContractString = insert_call_function(ContractString0, CallName, FunName, Args, Options),
#{fcode := Fcode} = string_to_code(ContractString, Options),
CallArgs = arguments_of_body(CallName, FunName, Fcode),
{ok, FunName, CallArgs}
end
catch catch
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)}; {error, join_errors("Parse errors", Errors, fun (E) -> E end)};
@@ -240,232 +130,44 @@ check_call1(ContractString0, FunName, Args, Options) ->
fun (E) -> io_lib:format("~p", [E]) end)} fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
arguments_of_body(CallName, _FunName, Fcode) -> -spec create_calldata(map(), string(), string()) ->
#{body := Body} = maps:get({entrypoint, list_to_binary(CallName)}, maps:get(functions, Fcode)), {ok, binary(), aeso_sophia:type(), aeso_sophia:type()}
{def, _FName, Args} = Body, | {error, argument_syntax_error}.
%% FName is either {entrypoint, list_to_binary(FunName)} or 'init' create_calldata(Contract, "", CallCode) when is_map(Contract) ->
[ aeso_fcode_to_fate:term_to_fate(A) || A <- Args ]. case check_call(CallCode, []) of
{ok, FunName, {ArgTypes, RetType}, Args} ->
first_none_match(_CallName, _Hashes, []) -> aeso_abi:create_calldata(Contract, FunName, Args, ArgTypes, RetType);
error(unable_to_find_unique_call_name);
first_none_match(CallName, Hashes, [Char|Chars]) ->
case not lists:member(aeb_fate_code:symbol_identifier(list_to_binary(CallName)), Hashes) of
true ->
CallName;
false ->
first_none_match(?CALL_NAME++[Char], Hashes, Chars)
end.
%% Add the __call function to a contract.
-spec insert_call_function(string(), string(), string(), [string()], options()) -> string().
insert_call_function(Code, Call, FunName, Args, Options) ->
Ast = parse(Code, Options),
Ind = last_contract_indent(Ast),
lists:flatten(
[ Code,
"\n\n",
lists:duplicate(Ind, " "),
"stateful entrypoint ", Call, "() = ", FunName, "(", string:join(Args, ","), ")\n"
]).
-spec insert_init_function(string(), options()) -> string().
insert_init_function(Code, Options) ->
Ast = parse(Code, Options),
Ind = last_contract_indent(Ast),
lists:flatten(
[ Code,
"\n\n",
lists:duplicate(Ind, " "), "entrypoint init() = ()\n"
]).
last_contract_indent(Decls) ->
case lists:last(Decls) of
{_, _, _, [Decl | _]} -> aeso_syntax:get_ann(col, Decl, 1) - 1;
_ -> 0
end.
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
{ok, aeso_syntax:expr()} | {error, term()}.
to_sophia_value(ContractString, Fun, ResType, Data) ->
to_sophia_value(ContractString, Fun, ResType, Data, [{backend, aevm}]).
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
{ok, aeso_syntax:expr()} | {error, term()}.
to_sophia_value(_, _, error, Err, _Options) ->
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
to_sophia_value(_, _, revert, Data, Options) ->
case proplists:get_value(backend, Options, aevm) of
aevm ->
case aeb_heap:from_binary(string, Data) of
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
{error, _} = Err -> Err {error, _} = Err -> Err
end; end;
fate -> create_calldata(Contract, Function, Argument) when is_map(Contract) ->
Err = aeb_fate_encoding:deserialize(Data), %% Slightly hacky shortcut to let you get away without writing the full
{ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}} %% call contract code.
end; %% Function should be "foo : type", and
to_sophia_value(ContractString, FunName, ok, Data, Options0) -> %% Argument should be "Arg1, Arg2, .., ArgN" (no parens)
Options = [no_code | Options0], case string:lexemes(Function, ": ") of
try %% If function is a single word fallback to old calldata generation
Code = string_to_code(ContractString, Options), [FunName] -> aeso_abi:old_create_calldata(Contract, FunName, Argument);
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code, [FunName | _] ->
{ok, _, Type0} = get_decode_type(FunName, TypedAst), Args = lists:map(fun($\n) -> 32; (X) -> X end, Argument), %% newline to space
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), CallContract = lists:flatten(
[ "contract Call =\n"
case proplists:get_value(backend, Options, aevm) of , " function ", Function, "\n"
aevm -> , " function __call() = ", FunName, "(", Args, ")"
Icode = maps:get(icode, Code), ]),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode), create_calldata(Contract, "", CallContract)
case aeb_heap:from_binary(VmType, Data) of
{ok, VmValue} ->
try
{ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)}
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",
[Data, 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;
fate ->
try
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
catch throw:cannot_translate_to_sophia ->
{error, join_errors("Translation error",
[lists:flatten(io_lib:format("Cannot translate fate value ~p\n of Sophia type ~s\n",
[aeb_fate_encoding:deserialize(Data), Type]))],
fun (E) -> E end)};
_:R ->
{error, iolist_to_binary(io_lib:format("Decode error ~p: ~p\n", [R, erlang:get_stacktrace()]))}
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. end.
-spec create_calldata(string(), string(), [string()]) ->
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
| {error, term()}.
create_calldata(Code, Fun, Args) ->
create_calldata(Code, Fun, Args, [{backend, aevm}]).
-spec create_calldata(string(), string(), [string()], [{atom(), any()}]) ->
{ok, binary()}
| {error, term()}.
create_calldata(Code, Fun, Args, Options0) ->
Options = [no_code | Options0],
case proplists:get_value(backend, Options, aevm) of
aevm ->
case check_call(Code, Fun, Args, Options) of
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
aeb_aevm_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
{error, _} = Err -> Err
end;
fate ->
case check_call(Code, Fun, Args, Options) of
{ok, FunName, FateArgs} ->
aeb_fate_abi:create_calldata(FunName, FateArgs);
{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) ->
decode_calldata(ContractString, FunName, Calldata, [{backend, aevm}]).
decode_calldata(ContractString, FunName, Calldata, Options0) ->
Options = [no_code | Options0],
try
Code = string_to_code(ContractString, Options),
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code,
{ok, Args, _} = get_decode_type(FunName, TypedAst),
DropArg = fun({arg, _, _, T}) -> T; (T) -> T end,
ArgTypes = lists:map(DropArg, Args),
Type0 = {tuple_t, [], ArgTypes},
%% user defined data types such as variants needed to match against
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = maps:get(icode, Code),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
{ok, {_, VmValue}} ->
try
{tuple, [], Values} = aeso_vm_decode:from_aevm(VmType, Type, VmValue),
%% Values are Sophia expressions in AST format
{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;
fate ->
case aeb_fate_abi:decode_calldata(FunName, Calldata) of
{ok, FateArgs} ->
try
{tuple_t, [], ArgTypes1} = Type,
AstArgs = [ aeso_vm_decode:from_fate(ArgType, FateArg)
|| {ArgType, FateArg} <- lists:zip(ArgTypes1, FateArgs)],
{ok, ArgTypes, AstArgs}
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 fate value ~p\n of Sophia type ~s\n",
[FateArgs, Type0Str]))],
fun (E) -> E end)}
end;
{error, _} ->
{error, join_errors("Decode errors", ["Failed to decode binary"],
fun(E) -> E end)}
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 [Args] = [ Args || {?CALL_NAME, _, _, {funcall, _, Args}, _} <- Funs ],
[Args] -> Args; Args.
[] -> error({missing_call_function, Funs})
end.
get_call_type([{contract, _, _, Defs}]) -> get_call_type([{contract, _, _, Defs}]) ->
case [ {lists:last(QFunName), FunType} case [ {FunName, FunType}
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret, || {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
{typed, _, {typed, _,
{app, _, {app, _,
{typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of {typed, _, {id, _, FunName}, FunType}, _}, _}} <- Defs ] of
[Call] -> {ok, Call}; [Call] -> {ok, Call};
[] -> {error, missing_call_function} [] -> {error, missing_call_function}
end; end;
@@ -473,26 +175,9 @@ get_call_type([_ | Contracts]) ->
%% The __call should be in the final contract %% The __call should be in the final contract
get_call_type(Contracts). get_call_type(Contracts).
get_decode_type(FunName, [{contract, _, _, Defs}]) ->
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}];
({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
(_) -> [] end,
case lists:flatmap(GetType, Defs) of
[{Args, Ret}] -> {ok, Args, Ret};
[] ->
case FunName of
"init" -> {ok, [], {tuple_t, [], []}};
_ -> {error, missing_function}
end
end;
get_decode_type(FunName, [_ | Contracts]) ->
%% The __decode should be in the final contract
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 aeb_heap:to_binary(). %% consumed by aeso_heap:to_binary().
icode_to_term(word, {integer, N}) -> N; icode_to_term(word, {integer, N}) -> N;
icode_to_term(word, {unop, '-', {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 >>,
Str; Str;
@@ -524,7 +209,10 @@ icode_to_term(T, V) ->
icodes_to_terms(Ts, Vs) -> icodes_to_terms(Ts, Vs) ->
[ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ]. [ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ].
ast_to_icode(TypedAst, Options) -> parse(C,_Options) ->
parse_string(C).
to_icode(TypedAst, Options) ->
aeso_ast_to_icode:convert_typed(TypedAst, Options). aeso_ast_to_icode:convert_typed(TypedAst, Options).
assemble(Icode, Options) -> assemble(Icode, Options) ->
@@ -538,9 +226,7 @@ to_bytecode([Op|Rest], Options) ->
to_bytecode([], _) -> []. to_bytecode([], _) -> [].
extract_type_info(#{functions := Functions} =_Icode) -> extract_type_info(#{functions := Functions} =_Icode) ->
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end, TypeInfo = [aeso_abi:function_type_info(list_to_binary(Name), Args, TypeRep)
TypeInfo = [aeb_aevm_abi:function_type_info(list_to_binary(lists:last(Name)),
ArgTypesOnly(Args), TypeRep)
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions, || {Name, Attrs, Args,_Body, TypeRep} <- Functions,
not is_tuple(Name), not is_tuple(Name),
not lists:member(private, Attrs) not lists:member(private, Attrs)
@@ -575,9 +261,9 @@ sophia_type_to_typerep(String) ->
catch _:_ -> {error, bad_type} catch _:_ -> {error, bad_type}
end. end.
parse(Text, Options) -> parse_string(Text) ->
%% Try and return something sensible here! %% Try and return something sensible here!
case aeso_parser:string(Text, Options) of case aeso_parser:string(Text) of
%% Yay, it worked! %% Yay, it worked!
{ok, Contract} -> Contract; {ok, Contract} -> Contract;
%% Scan errors. %% Scan errors.
@@ -590,22 +276,13 @@ parse(Text, Options) ->
parse_error(Pos, Error); parse_error(Pos, Error);
{error, {Pos, ambiguous_parse, As}} -> {error, {Pos, ambiguous_parse, As}} ->
ErrorString = io_lib:format("Ambiguous ~p", [As]), ErrorString = io_lib:format("Ambiguous ~p", [As]),
parse_error(Pos, ErrorString); parse_error(Pos, ErrorString)
%% Include error
{error, {Pos, include_error, File}} ->
parse_error(Pos, io_lib:format("could not find include file '~s'", [File]))
end. end.
parse_error(Pos, ErrorString) -> parse_error({Line, Pos}, ErrorString) ->
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]), Error = io_lib:format("line ~p, column ~p: ~s", [Line, Pos, ErrorString]),
error({parse_errors, [Error]}). error({parse_errors, [Error]}).
read_contract(Name) -> read_contract(Name) ->
file:read_file(Name). file:read_file(Name).
pos_error({Line, Pos}) ->
io_lib:format("line ~p, column ~p", [Line, Pos]);
pos_error({no_file, Line, Pos}) ->
pos_error({Line, Pos});
pos_error({File, Line, Pos}) ->
io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]).
File diff suppressed because it is too large Load Diff
+301
View File
@@ -0,0 +1,301 @@
-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.
+12 -44
View File
@@ -9,30 +9,19 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeso_icode). -module(aeso_icode).
-export([new/1, -export([new/1, pp/1, set_name/2, set_functions/2, map_typerep/2, option_typerep/1, get_constructor_tag/2]).
pp/1,
set_name/2,
set_namespace/2,
enter_namespace/2,
get_namespace/1,
qualify/2,
set_functions/2,
map_typerep/2,
option_typerep/1,
get_constructor_tag/2]).
-export_type([icode/0]). -export_type([icode/0]).
-include("aeso_icode.hrl"). -include("aeso_icode.hrl").
-type type_def() :: fun(([aeb_aevm_data:type()]) -> aeb_aevm_data:type()). -type type_def() :: fun(([aeso_sophia:type()]) -> aeso_sophia:type()).
-type bindings() :: any(). -type bindings() :: any().
-type fun_dec() :: { string() -type fun_dec() :: { string()
, [modifier()] , [modifier()]
, arg_list() , arg_list()
, expr() , expr()
, aeb_aevm_data:type()}. , aeso_sophia:type()}.
-type modifier() :: private | stateful. -type modifier() :: private | stateful.
@@ -40,13 +29,12 @@
-type icode() :: #{ contract_name => string() -type icode() :: #{ contract_name => string()
, functions => [fun_dec()] , functions => [fun_dec()]
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
, env => [bindings()] , env => [bindings()]
, state_type => aeb_aevm_data:type() , state_type => aeso_sophia:type()
, event_type => aeb_aevm_data:type() , event_type => aeso_sophia:type()
, types => #{ type_name() => type_def() } , types => #{ type_name() => type_def() }
, type_vars => #{ string() => aeb_aevm_data:type() } , type_vars => #{ string() => aeso_sophia:type() }
, constructors => #{ [string()] => integer() } %% name to tag , constructors => #{ string() => integer() } %% name to tag
, options => [any()] , options => [any()]
}. }.
@@ -71,7 +59,6 @@ builtin_types() ->
Word = fun([]) -> word end, Word = fun([]) -> word end,
#{ "bool" => Word #{ "bool" => Word
, "int" => Word , "int" => Word
, "bits" => Word
, "string" => fun([]) -> string end , "string" => fun([]) -> string end
, "address" => Word , "address" => Word
, "hash" => Word , "hash" => Word
@@ -85,10 +72,10 @@ builtin_types() ->
}. }.
builtin_constructors() -> builtin_constructors() ->
#{ ["RelativeTTL"] => 0 #{ "RelativeTTL" => 0
, ["FixedTTL"] => 1 , "FixedTTL" => 1
, ["None"] => 0 , "None" => 0
, ["Some"] => 1 }. , "Some" => 1 }.
map_typerep(K, V) -> map_typerep(K, V) ->
{map, K, V}. {map, K, V}.
@@ -103,30 +90,11 @@ new_env() ->
set_name(Name, Icode) -> set_name(Name, Icode) ->
maps:put(contract_name, Name, Icode). maps:put(contract_name, Name, Icode).
-spec set_namespace(aeso_syntax:con() | aeso_syntax:qcon(), icode()) -> icode().
set_namespace(NS, Icode) -> Icode#{ namespace => NS }.
-spec enter_namespace(aeso_syntax:con(), icode()) -> icode().
enter_namespace(NS, Icode = #{ namespace := NS1 }) ->
Icode#{ namespace => aeso_syntax:qualify(NS1, NS) };
enter_namespace(NS, Icode) ->
Icode#{ namespace => NS }.
-spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon().
get_namespace(Icode) -> maps:get(namespace, Icode, false).
-spec qualify(aeso_syntax:id() | aeso_syntax:con(), icode()) -> aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon().
qualify(X, Icode) ->
case get_namespace(Icode) of
false -> X;
NS -> aeso_syntax:qualify(NS, X)
end.
-spec set_functions([fun_dec()], icode()) -> icode(). -spec set_functions([fun_dec()], icode()) -> icode().
set_functions(NewFuns, Icode) -> set_functions(NewFuns, Icode) ->
maps:put(functions, NewFuns, Icode). maps:put(functions, NewFuns, Icode).
-spec get_constructor_tag([string()], icode()) -> integer(). -spec get_constructor_tag(string(), icode()) -> integer().
get_constructor_tag(Name, #{constructors := Constructors}) -> get_constructor_tag(Name, #{constructors := Constructors}) ->
case maps:get(Name, Constructors, undefined) of case maps:get(Name, Constructors, undefined) of
undefined -> error({undefined_constructor, Name}); undefined -> error({undefined_constructor, Name});
+11 -2
View File
@@ -1,5 +1,14 @@
-include_lib("aebytecode/include/aeb_typerep_def.hrl"). -define(Type(), aeso_sophia:type()).
-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()}).
@@ -11,7 +20,7 @@
, args :: arg_list() , args :: arg_list()
, body :: expr()}). , body :: expr()}).
-record(var_ref, { name :: string() | list(string()) | {builtin, atom() | tuple()}}). -record(var_ref, { name :: string() | {builtin, atom() | tuple()}}).
-record(prim_call_contract, -record(prim_call_contract,
{ gas :: expr() { gas :: expr()
+2 -6
View File
@@ -17,7 +17,7 @@
i(Code) -> aeb_opcodes:mnemonic(Code). i(Code) -> aeb_opcodes:mnemonic(Code).
%% We don't track purity or statefulness in the type checker yet. %% We don't track purity or statefulness in the type checker yet.
is_stateful({FName, _, _, _, _}) -> lists:last(FName) /= "init". is_stateful({FName, _, _, _, _}) -> FName /= "init".
is_public({_Name, Attrs, _Args, _Body, _Type}) -> not lists:member(private, Attrs). is_public({_Name, Attrs, _Args, _Body, _Type}) -> not lists:member(private, Attrs).
@@ -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>> = aeb_aevm_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep), <<Hash:256>> = aeso_abi:function_type_hash(list_to_binary(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,8 +343,6 @@ 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) ->
@@ -564,8 +562,6 @@ assemble_infix('^') -> i(?EXP);
assemble_infix('bor') -> i(?OR); assemble_infix('bor') -> i(?OR);
assemble_infix('band') -> i(?AND); assemble_infix('band') -> i(?AND);
assemble_infix('bxor') -> i(?XOR); assemble_infix('bxor') -> i(?XOR);
assemble_infix('bsl') -> i(?SHL);
assemble_infix('bsr') -> i(?SHR);
assemble_infix('<') -> i(?SLT); %% comparisons are SIGNED assemble_infix('<') -> i(?SLT); %% comparisons are SIGNED
assemble_infix('>') -> i(?SGT); assemble_infix('>') -> i(?SGT);
assemble_infix('==') -> i(?EQ); assemble_infix('==') -> i(?EQ);
+19
View File
@@ -0,0 +1,19 @@
%%%-------------------------------------------------------------------
%%% @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>>).
+1 -1
View File
@@ -19,7 +19,7 @@
-export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]). -export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]).
-type pos() :: {string() | no_file, integer(), integer()} | {integer(), integer()}. -type pos() :: {integer(), integer()}.
-type token() :: {atom(), pos(), term()} | {atom(), pos()}. -type token() :: {atom(), pos(), term()} | {atom(), pos()}.
-type tokens() :: [token()]. -type tokens() :: [token()].
-type error() :: {pos(), string() | no_error}. -type error() :: {pos(), string() | no_error}.
+52 -152
View File
@@ -5,33 +5,24 @@
-module(aeso_parser). -module(aeso_parser).
-export([string/1, -export([string/1,
string/2,
type/1]). type/1]).
-include("aeso_parse_lib.hrl"). -include("aeso_parse_lib.hrl").
-type parse_result() :: {ok, aeso_syntax:ast()} -spec string(string()) ->
| {error, {aeso_parse_lib:pos(), atom(), term()}} {ok, aeso_syntax:ast()}
| {error, {aeso_parse_lib:pos(), atom()}}. | {error, {aeso_parse_lib:pos(),
atom(),
-spec string(string()) -> parse_result(). term()}}
| {error, {aeso_parse_lib:pos(),
atom()}}.
string(String) -> string(String) ->
string(String, []). parse_and_scan(file(), String).
-spec string(string(), aeso_compiler:options()) -> parse_result().
string(String, Opts) ->
case parse_and_scan(file(), String, Opts) of
{ok, AST} ->
expand_includes(AST, Opts);
Err = {error, _} ->
Err
end.
type(String) -> type(String) ->
parse_and_scan(type(), String, []). parse_and_scan(type(), String).
parse_and_scan(P, S, Opts) -> parse_and_scan(P, S) ->
set_current_file(proplists:get_value(src_file, Opts, no_file)),
case aeso_scan:scan(S) of case aeso_scan:scan(S) of
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens); {ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
Error -> Error Error -> Error
@@ -46,8 +37,6 @@ decl() ->
choice( choice(
%% Contract declaration %% Contract declaration
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4}) [ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
%% Type declarations TODO: format annotation for "type bla" vs "type bla()" %% Type declarations TODO: format annotation for "type bla" vs "type bla()"
, ?RULE(keyword(type), id(), {type_decl, _1, _2, []}) , ?RULE(keyword(type), id(), {type_decl, _1, _2, []})
@@ -60,31 +49,17 @@ decl() ->
, ?RULE(keyword(datatype), id(), type_vars(), tok('='), typedef(variant), {type_def, _1, _2, _3, _5}) , ?RULE(keyword(datatype), id(), type_vars(), tok('='), typedef(variant), {type_def, _1, _2, _3, _5})
%% Function declarations %% Function declarations
, ?RULE(modifiers(), fun_or_entry(), id(), tok(':'), type(), add_modifiers(_1, _2, {fun_decl, get_ann(_2), _3, _5})) , ?RULE(modifiers(), keyword(function), id(), tok(':'), type(), add_modifiers(_1, {fun_decl, _2, _3, _5}))
, ?RULE(modifiers(), fun_or_entry(), fundef(), add_modifiers(_1, _2, set_pos(get_pos(get_ann(_2)), _3))) , ?RULE(modifiers(), keyword(function), fundef(), add_modifiers(_1, set_pos(get_pos(_2), _3)))
, ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2)) , ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2))
])). ])).
fun_or_entry() ->
choice([?RULE(keyword(function), {function, _1}),
?RULE(keyword(entrypoint), {entrypoint, _1})]).
modifiers() -> modifiers() ->
many(choice([token(stateful), token(private), token(public)])). many(choice([token(stateful), token(public), token(private), token(internal)])).
add_modifiers(Mods, Entry = {entrypoint, _}, Node) -> add_modifiers(Mods, Node) ->
add_modifiers(Mods ++ [Entry], Node);
add_modifiers(Mods, {function, _}, Node) ->
add_modifiers(Mods, Node).
add_modifiers([], Node) -> Node;
add_modifiers(Mods = [Tok | _], Node) ->
%% Set the position to the position of the first modifier. This is
%% important for code transformation tools (like what we do in
%% create_calldata) to be able to get the indentation of the declaration.
set_pos(get_pos(Tok),
lists:foldl(fun({Mod, _}, X) -> set_ann(Mod, true, X) end, lists:foldl(fun({Mod, _}, X) -> set_ann(Mod, true, X) end,
Node, Mods)). Node, Mods).
%% -- Type declarations ------------------------------------------------------ %% -- Type declarations ------------------------------------------------------
@@ -108,7 +83,9 @@ con_arg() -> choice(type(), ?RULE(keyword(indexed), type(), set_ann(indexed,
%% -- Let declarations ------------------------------------------------------- %% -- Let declarations -------------------------------------------------------
letdecl() -> letdecl() ->
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)). choice(
?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()).
@@ -140,34 +117,22 @@ type100() -> type200().
type200() -> type200() ->
?RULE(many({fun_domain(), keyword('=>')}), type300(), fun_t(_1, _2)). ?RULE(many({fun_domain(), keyword('=>')}), type300(), fun_t(_1, _2)).
type300() -> type300() -> type400().
?RULE(sep1(type400(), tok('*')), tuple_t(get_ann(lists:nth(1, _1)), _1)).
type400() -> type400() ->
choice( ?RULE(typeAtom(), optional(type_args()),
[?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(
[ parens(type()) [ id(), token(con), token(qcon), token(qid), tvar()
, id(), token(con), token(qcon), token(qid), tvar() , ?RULE(keyword('('), comma_sep(type()), tok(')'), tuple_t(_1, _2))
])). ])).
fun_domain() -> ?LAZY_P(choice( fun_domain() -> ?RULE(?LAZY_P(type300()), fun_domain(_1)).
[ ?RULE(tok('('), tok(')'), [])
%% Note avoidance of ambiguity: `(int)` can be treated as:
%% - literally `int`
%% - list of arguments with just one element int. This approach is dropped.
, ?RULE(tok('('), type(), tok(','), sep1(type(), tok(',')), tok(')'), [_2|_4])
, ?RULE(type300(), [_1])
])).
%% -- Statements ------------------------------------------------------------- %% -- Statements -------------------------------------------------------------
@@ -211,24 +176,24 @@ expr200() -> infixr(expr300(), binop('||')).
expr300() -> infixr(expr400(), binop('&&')). expr300() -> infixr(expr400(), binop('&&')).
expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])). expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])).
expr500() -> infixr(expr600(), binop(['::', '++'])). expr500() -> infixr(expr600(), binop(['::', '++'])).
expr600() -> infixl(expr650(), binop(['+', '-'])). expr600() -> infixl(expr650(), binop(['+', '-', 'bor', 'bxor', 'bsr', 'bsl'])).
expr650() -> ?RULE(many(token('-')), expr700(), prefixes(_1, _2)). expr650() -> ?RULE(many(token('-')), expr700(), prefixes(_1, _2)).
expr700() -> infixl(expr750(), binop(['*', '/', mod])). expr700() -> infixl(expr750(), binop(['*', '/', mod, 'band'])).
expr750() -> infixl(expr800(), binop(['^'])). expr750() -> infixl(expr800(), binop(['^'])).
expr800() -> ?RULE(many(token('!')), expr900(), prefixes(_1, _2)). expr800() -> ?RULE(many(choice(token('!'), token('bnot'))), expr900(), prefixes(_1, _2)).
expr900() -> ?RULE(exprAtom(), many(elim()), elim(_1, _2)). expr900() -> ?RULE(exprAtom(), many(elim()), elim(_1, _2)).
exprAtom() -> exprAtom() ->
?LAZY_P(begin ?LAZY_P(begin
Expr = ?LAZY_P(expr()), Expr = ?LAZY_P(expr()),
choice( choice(
[ id_or_addr(), con(), token(qid), token(qcon) [ id(), con(), token(qid), token(qcon)
, token(bytes), token(string), token(char) , token(hash), 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}
, {bool, keyword(false), false} , {bool, keyword(false), false}
, ?LET_P(Fs, brace_list(?LAZY_P(field_assignment())), record(Fs)) , ?RULE(brace_list(?LAZY_P(field_assignment())), record(_1))
, {list, [], bracket_list(Expr)} , {list, [], bracket_list(Expr)}
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4)) , ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2)) , ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
@@ -271,20 +236,14 @@ record_update(Ann, E, Flds) ->
record([]) -> {map, [], []}; record([]) -> {map, [], []};
record(Fs) -> record(Fs) ->
case record_or_map(Fs) of case record_or_map(Fs) of
record -> record -> {record, get_ann(hd(Fs)), Fs};
Fld = fun({field, _, [_], _} = F) -> F;
({field, Ann, LV, Id, _}) ->
bad_expr_err("Cannot use '@' in record construction", infix({lvalue, Ann, LV}, {'@', Ann}, Id));
({field, Ann, LV, _}) ->
bad_expr_err("Cannot use nested fields or keys in record construction", {lvalue, Ann, LV}) end,
{record, get_ann(hd(Fs)), lists:map(Fld, Fs)};
map -> map ->
Ann = get_ann(hd(Fs ++ [{empty, []}])), %% TODO: source location for empty maps Ann = get_ann(hd(Fs ++ [{empty, []}])), %% TODO: source location for empty maps
KV = fun({field, _, [{map_get, _, Key}], Val}) -> {Key, Val}; KV = fun({field, _, [{map_get, _, Key}], Val}) -> {Key, Val};
({field, FAnn, LV, Id, _}) -> ({field, _, LV, Id, _}) ->
bad_expr_err("Cannot use '@' in map construction", infix({lvalue, FAnn, LV}, {'@', Ann}, Id)); bad_expr_err("Cannot use '@' in map construction", infix(LV, {op, Ann, '@'}, Id));
({field, FAnn, LV, _}) -> ({field, _, LV, _}) ->
bad_expr_err("Cannot use nested fields or keys in map construction", {lvalue, FAnn, LV}) end, bad_expr_err("Cannot use nested fields or keys in map construction", LV) end,
{map, Ann, lists:map(KV, Fs)} {map, Ann, lists:map(KV, Fs)}
end. end.
@@ -342,7 +301,6 @@ binop(Ops) ->
con() -> token(con). con() -> token(con).
id() -> token(id). id() -> token(id).
tvar() -> token(tvar). tvar() -> token(tvar).
str() -> token(string).
token(Tag) -> token(Tag) ->
?RULE(tok(Tag), ?RULE(tok(Tag),
@@ -351,26 +309,6 @@ 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)).
@@ -398,17 +336,10 @@ bracket_list(P) -> brackets(comma_sep(P)).
-type ann_col() :: aeso_syntax:ann_col(). -type ann_col() :: aeso_syntax:ann_col().
-spec pos_ann(ann_line(), ann_col()) -> ann(). -spec pos_ann(ann_line(), ann_col()) -> ann().
pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}]. pos_ann(Line, Col) -> [{line, Line}, {col, Col}].
current_file() ->
get('$current_file').
set_current_file(File) ->
put('$current_file', File).
ann_pos(Ann) -> ann_pos(Ann) ->
{proplists:get_value(file, Ann), {proplists:get_value(line, Ann),
proplists:get_value(line, Ann),
proplists:get_value(col, Ann)}. proplists:get_value(col, Ann)}.
get_ann(Ann) when is_list(Ann) -> Ann; get_ann(Ann) when is_list(Ann) -> Ann;
@@ -426,10 +357,10 @@ set_ann(Key, Val, Node) ->
setelement(2, Node, lists:keystore(Key, 1, Ann, {Key, Val})). setelement(2, Node, lists:keystore(Key, 1, Ann, {Key, Val})).
get_pos(Node) -> get_pos(Node) ->
{current_file(), get_ann(line, Node), get_ann(col, Node)}. {get_ann(line, Node), get_ann(col, Node)}.
set_pos({F, L, C}, Node) -> set_pos({L, C}, Node) ->
set_ann(file, F, set_ann(line, L, set_ann(col, C, Node))). set_ann(line, L, set_ann(col, C, Node)).
infix(L, Op, R) -> set_ann(format, infix, {app, get_ann(L), Op, [L, R]}). infix(L, Op, R) -> set_ann(format, infix, {app, get_ann(L), Op, [L, R]}).
@@ -462,7 +393,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, {tuple, [{origin, system}], []}}. {'if', Ann, Cond, Then, {unit, [{origin, system}]}}.
else_branches([Elif = {elif, _, _, _} | Stmts], Acc) -> else_branches([Elif = {elif, _, _, _} | Stmts], Acc) ->
else_branches(Stmts, [Elif | Acc]); else_branches(Stmts, [Elif | Acc]);
@@ -478,9 +409,14 @@ 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}.
%% TODO: not nice
fun_domain({tuple_t, _, Args}) -> Args;
fun_domain(T) -> [T].
-spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()). -spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()).
parse_pattern({app, Ann, Con = {'::', _}, Es}) -> parse_pattern({app, Ann, Con = {'::', _}, Es}) ->
{app, Ann, Con, lists:map(fun parse_pattern/1, Es)}; {app, Ann, Con, lists:map(fun parse_pattern/1, Es)};
@@ -494,9 +430,10 @@ 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 = {bytes, _, _}) -> E; parse_pattern(E = {hash, _, _}) -> 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).
@@ -505,53 +442,16 @@ parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
parse_field_pattern({field, Ann, F, E}) -> parse_field_pattern({field, Ann, F, E}) ->
{field, Ann, F, parse_pattern(E)}. {field, Ann, F, parse_pattern(E)}.
return_error({no_file, L, C}, Err) -> return_error({L, C}, Err) ->
fail(io_lib:format("~p:~p:\n~s", [L, C, Err])); fail(io_lib:format("~p:~p:\n~s", [L, C, Err])).
return_error({F, L, C}, Err) ->
fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])).
-spec ret_doc_err(ann(), prettypr:document()) -> aeso_parse_lib:parser(none()). -spec ret_doc_err(ann(), prettypr:document()) -> no_return().
ret_doc_err(Ann, Doc) -> ret_doc_err(Ann, Doc) ->
return_error(ann_pos(Ann), prettypr:format(Doc)). return_error(ann_pos(Ann), prettypr:format(Doc)).
-spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()). -spec bad_expr_err(string(), aeso_syntax:expr()) -> no_return().
bad_expr_err(Reason, E) -> bad_expr_err(Reason, E) ->
ret_doc_err(get_ann(E), ret_doc_err(get_ann(E),
prettypr:sep([prettypr:text(Reason ++ ":"), prettypr:sep([prettypr:text(Reason ++ ":"),
prettypr:nest(2, aeso_pretty:expr(E))])). prettypr:nest(2, aeso_pretty:expr(E))])).
%% -- Helper functions -------------------------------------------------------
expand_includes(AST, Opts) ->
expand_includes(AST, [], Opts).
expand_includes([], Acc, _Opts) ->
{ok, lists:reverse(Acc)};
expand_includes([{include, _, S = {string, _, File}} | AST], Acc, Opts) ->
case read_file(File, Opts) of
{ok, Bin} ->
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
case string(binary_to_list(Bin), Opts1) of
{ok, AST1} ->
expand_includes(AST1 ++ AST, Acc, Opts);
Err = {error, _} ->
Err
end;
{error, _} ->
{error, {get_pos(S), include_error, File}}
end;
expand_includes([E | AST], Acc, Opts) ->
expand_includes(AST, [E | Acc], Opts).
read_file(File, Opts) ->
case proplists:get_value(include, Opts, {explicit_files, #{}}) of
{file_system, Paths} ->
CandidateNames = [ filename:join(Dir, File) || Dir <- Paths ],
lists:foldr(fun(F, {error, _}) -> file:read_file(F);
(_F, OK) -> OK end, {error, not_found}, CandidateNames);
{explicit_files, Files} ->
case maps:get(binary_to_list(File), Files, not_found) of
not_found -> {error, not_found};
Src -> {ok, Src}
end
end.
+26 -54
View File
@@ -147,28 +147,19 @@ decl(D, Options) ->
-spec decl(aeso_syntax:decl()) -> doc(). -spec decl(aeso_syntax:decl()) -> doc().
decl({contract, _, C, Ds}) -> decl({contract, _, C, Ds}) ->
block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds)); block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds));
decl({namespace, _, C, Ds}) ->
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars); decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars);
decl({type_def, _, T, Vars, Def}) -> decl({type_def, _, T, Vars, Def}) ->
Kind = element(1, Def), Kind = element(1, Def),
equals(typedecl(Kind, T, Vars), typedef(Def)); equals(typedecl(Kind, T, Vars), typedef(Def));
decl({fun_decl, Ann, F, T}) -> decl({fun_decl, _, F, T}) ->
Fun = case aeso_syntax:get_ann(entrypoint, Ann, false) of hsep(text("function"), typed(name(F), T));
true -> text("entrypoint");
false -> text("function")
end,
hsep(Fun, typed(name(F), T));
decl(D = {letfun, Attrs, _, _, _, _}) -> decl(D = {letfun, Attrs, _, _, _, _}) ->
Mod = fun({Mod, true}) when Mod == private; Mod == stateful -> Mod = fun({Mod, true}) when Mod == private; Mod == internal; Mod == public; Mod == stateful ->
text(atom_to_list(Mod)); text(atom_to_list(Mod));
(_) -> empty() end, (_) -> empty() end,
Fun = case aeso_syntax:get_ann(entrypoint, Attrs, false) of hsep(lists:map(Mod, Attrs) ++ [letdecl("function", D)]);
true -> "entrypoint"; decl(D = {letval, _, _, _, _}) -> letdecl("let", D);
false -> "function" decl(D = {letrec, _, _}) -> letdecl("let", D).
end,
hsep(lists:map(Mod, Attrs) ++ [letdecl(Fun, D)]);
decl(D = {letval, _, _, _, _}) -> letdecl("let", D).
-spec expr(aeso_syntax:expr(), options()) -> doc(). -spec expr(aeso_syntax:expr(), options()) -> doc().
expr(E, Options) -> expr(E, Options) ->
@@ -191,7 +182,9 @@ 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) ->
@@ -222,7 +215,7 @@ typedef({variant_t, Constructors}) ->
-spec constructor_t(aeso_syntax:constructor_t()) -> doc(). -spec constructor_t(aeso_syntax:constructor_t()) -> doc().
constructor_t({constr_t, _, C, []}) -> name(C); constructor_t({constr_t, _, C, []}) -> name(C);
constructor_t({constr_t, _, C, Args}) -> beside(name(C), args_type(Args)). constructor_t({constr_t, _, C, Args}) -> beside(name(C), tuple_type(Args)).
-spec field_t(aeso_syntax:field_t()) -> doc(). -spec field_t(aeso_syntax:field_t()) -> doc().
field_t({field_t, _, Name, Type}) -> field_t({field_t, _, Name, Type}) ->
@@ -234,20 +227,15 @@ type(Type, Options) ->
-spec type(aeso_syntax:type()) -> doc(). -spec type(aeso_syntax:type()) -> doc().
type({fun_t, _, Named, Args, Ret}) -> type({fun_t, _, Named, Args, Ret}) ->
follow(hsep(args_type(Named ++ Args), text("=>")), type(Ret)); follow(hsep(tuple_type(Named ++ Args), text("=>")), type(Ret));
type({app_t, _, Type, []}) -> type({app_t, _, Type, []}) ->
type(Type); type(Type);
type({app_t, _, Type, Args}) -> type({app_t, _, Type, Args}) ->
beside(type(Type), args_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, _, any}) -> text("bytes(_)"); type({named_arg_t, _, Name, Type, Default}) ->
type({bytes_t, _, Len}) -> follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
text(lists:concat(["bytes(", Len, ")"]));
type({named_arg_t, _, Name, Type, _Default}) ->
%% Drop the default value
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
typed(name(Name), Type);
type(R = {record_t, _}) -> typedef(R); type(R = {record_t, _}) -> typedef(R);
type(T = {id, _, _}) -> name(T); type(T = {id, _, _}) -> name(T);
@@ -256,19 +244,9 @@ type(T = {con, _, _}) -> name(T);
type(T = {qcon, _, _}) -> name(T); type(T = {qcon, _, _}) -> name(T);
type(T = {tvar, _, _}) -> name(T). type(T = {tvar, _, _}) -> name(T).
-spec args_type([aeso_syntax:type()]) -> doc().
args_type(Args) ->
tuple(lists:map(fun type/1, Args)).
-spec tuple_type([aeso_syntax:type()]) -> doc(). -spec tuple_type([aeso_syntax:type()]) -> doc().
tuple_type([]) -> tuple_type(Args) ->
text("unit"); tuple(lists:map(fun type/1, Args)).
tuple_type(Factors) ->
beside(
[ text("(")
, par(punctuate(text(" *"), lists:map(fun type/1, Factors)), 0)
, text(")")
]).
-spec arg_expr(aeso_syntax:arg_expr()) -> doc(). -spec arg_expr(aeso_syntax:arg_expr()) -> doc().
arg_expr({named_arg, _, Name, E}) -> arg_expr({named_arg, _, Name, E}) ->
@@ -325,8 +303,6 @@ expr_p(P, E = {app, _, F = {Op, _}, Args}) when is_atom(Op) ->
{prefix, [A]} -> prefix(P, Op, A); {prefix, [A]} -> prefix(P, Op, A);
_ -> app(P, F, Args) _ -> app(P, F, Args)
end; end;
expr_p(_, {app, _, C={Tag, _, _}, []}) when Tag == con; Tag == qcon ->
expr_p(0, C);
expr_p(P, {app, _, F, Args}) -> expr_p(P, {app, _, F, Args}) ->
app(P, F, Args); app(P, F, Args);
%% -- Constants %% -- Constants
@@ -337,18 +313,8 @@ 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(_, {bytes, _, Bin}) -> expr_p(_, {hash, _, <<N:256>>}) -> text("#" ++ integer_to_list(N, 16));
Digits = byte_size(Bin), expr_p(_, {unit, _}) -> text("()");
<<N:Digits/unit:8>> = Bin,
text(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N])));
expr_p(_, {hash, _, <<N:512>>}) -> text("#" ++ integer_to_list(N, 16));
expr_p(_, {Type, _, Bin})
when Type == account_pubkey;
Type == contract_pubkey;
Type == oracle_pubkey;
Type == oracle_query_id ->
text(binary_to_list(aeser_api_encoder:encode(Type, Bin)));
expr_p(_, {string, _, <<>>}) -> text("\"\"");
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
@@ -381,7 +347,6 @@ stmt_p({else, Else}) ->
-spec bin_prec(aeso_syntax:bin_op()) -> {integer(), integer(), integer()}. -spec bin_prec(aeso_syntax:bin_op()) -> {integer(), integer(), integer()}.
bin_prec('..') -> { 0, 0, 0}; %% Always printed inside '[ ]' bin_prec('..') -> { 0, 0, 0}; %% Always printed inside '[ ]'
bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]' bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]'
bin_prec('@') -> { 0, 0, 0}; %% Only in error messages
bin_prec('||') -> {200, 300, 200}; bin_prec('||') -> {200, 300, 200};
bin_prec('&&') -> {300, 400, 300}; bin_prec('&&') -> {300, 400, 300};
bin_prec('<') -> {400, 500, 500}; bin_prec('<') -> {400, 500, 500};
@@ -394,14 +359,20 @@ bin_prec('++') -> {500, 600, 500};
bin_prec('::') -> {500, 600, 500}; bin_prec('::') -> {500, 600, 500};
bin_prec('+') -> {600, 600, 650}; bin_prec('+') -> {600, 600, 650};
bin_prec('-') -> {600, 600, 650}; bin_prec('-') -> {600, 600, 650};
bin_prec('bor') -> {600, 600, 650};
bin_prec('bxor') -> {600, 600, 650};
bin_prec('bsl') -> {600, 600, 650};
bin_prec('bsr') -> {600, 600, 650};
bin_prec('*') -> {700, 700, 750}; bin_prec('*') -> {700, 700, 750};
bin_prec('/') -> {700, 700, 750}; bin_prec('/') -> {700, 700, 750};
bin_prec(mod) -> {700, 700, 750}; bin_prec(mod) -> {700, 700, 750};
bin_prec('band') -> {700, 700, 750};
bin_prec('^') -> {750, 750, 800}. bin_prec('^') -> {750, 750, 800}.
-spec un_prec(aeso_syntax:un_op()) -> {integer(), integer()}. -spec un_prec(aeso_syntax:un_op()) -> {integer(), integer()}.
un_prec('-') -> {650, 650}; un_prec('-') -> {650, 650};
un_prec('!') -> {800, 800}. un_prec('!') -> {800, 800};
un_prec('bnot') -> {800, 800}.
equals(Ann, A, B) -> equals(Ann, A, B) ->
{app, [{format, infix} | Ann], {'=', Ann}, [A, B]}. {app, [{format, infix} | Ann], {'=', Ann}, [A, B]}.
@@ -453,6 +424,7 @@ 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, []).
+10 -7
View File
@@ -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, "+"],
BYTES = ["#", HEXDIGIT, "+"], HASH = ["#", HEXDIGIT, "+"],
WS = "[\\000-\\ ]+", WS = "[\\000-\\ ]+",
ID = [LOWER, "[a-zA-Z0-9_']*"], ID = [LOWER, "[a-zA-Z0-9_']*"],
TVAR = ["'", ID], TVAR = ["'", ID],
@@ -36,8 +36,9 @@ lexer() ->
, {"\\*/", pop(skip())} , {"\\*/", pop(skip())}
, {"[^/*]+|[/*]", skip()} ], , {"[^/*]+|[/*]", skip()} ],
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function", Keywords = ["contract", "import", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace"], "stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal",
"band", "bor", "bxor", "bsl", "bsr", "bnot"],
KW = string:join(Keywords, "|"), KW = string:join(Keywords, "|"),
Rules = Rules =
@@ -54,7 +55,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)}
, {BYTES, token(bytes, fun parse_bytes/1)} , {HASH, token(hash, fun parse_hash/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,8 +118,10 @@ unescape([C | Chars], Acc) ->
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16). parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
parse_bytes("#" ++ Chars) -> parse_hash("#" ++ Chars) ->
N = list_to_integer(Chars, 16), N = list_to_integer(Chars, 16),
Digits = (length(Chars) + 1) div 2, case length(Chars) > 64 of %% 64 hex digits = 32 bytes
<<N:Digits/unit:8>>. true -> <<N:64/unit:8>>; %% signature
false -> <<N:32/unit:8>> %% address
end.
+30
View File
@@ -0,0 +1,30 @@
-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().
+10 -18
View File
@@ -8,14 +8,14 @@
-module(aeso_syntax). -module(aeso_syntax).
-export([get_ann/1, get_ann/2, get_ann/3, set_ann/2, qualify/2]). -export([get_ann/1, get_ann/2, get_ann/3, set_ann/2]).
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]). -export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]). -export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
-export_type([bin_op/0, un_op/0]). -export_type([bin_op/0, un_op/0]).
-export_type([decl/0, letbind/0, typedef/0]). -export_type([decl/0, letbind/0, typedef/0]).
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]). -export_type([arg/0, field_t/0, constructor_t/0]).
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]). -export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, pat/0]).
-export_type([ast/0]). -export_type([ast/0]).
-type ast() :: [decl()]. -type ast() :: [decl()].
@@ -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()} | stateful | private]. -type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}].
-type name() :: string(). -type name() :: string().
-type id() :: {id, ann(), name()}. -type id() :: {id, ann(), name()}.
@@ -35,7 +35,6 @@
-type tvar() :: {tvar, ann(), name()}. -type tvar() :: {tvar, ann(), name()}.
-type decl() :: {contract, ann(), con(), [decl()]} -type decl() :: {contract, ann(), con(), [decl()]}
| {namespace, ann(), con(), [decl()]}
| {type_decl, ann(), id(), [tvar()]} | {type_decl, ann(), id(), [tvar()]}
| {type_def, ann(), id(), [tvar()], typedef()} | {type_def, ann(), id(), [tvar()], typedef()}
| {fun_decl, ann(), id(), type()} | {fun_decl, ann(), id(), type()}
@@ -43,7 +42,8 @@
-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()}.
@@ -59,7 +59,6 @@
-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() | any}
| id() | qid() | id() | qid()
| con() | qcon() %% contracts | con() | qcon() %% contracts
| tvar(). | tvar().
@@ -69,20 +68,17 @@
-type constant() -type constant()
:: {int, ann(), integer()} :: {int, ann(), integer()}
| {bool, ann(), true | false} | {bool, ann(), true | false}
| {bytes, ann(), binary()} | {hash, ann(), binary()}
| {account_pubkey, ann(), binary()} | {unit, ann()}
| {contract_pubkey, ann(), binary()}
| {oracle_pubkey, ann(), binary()}
| {oracle_query_id, ann(), binary()}
| {string, ann(), binary()} | {string, ann(), binary()}
| {char, ann(), integer()}. | {char, ann(), integer()}.
-type op() :: bin_op() | un_op(). -type op() :: bin_op() | un_op().
-type bin_op() :: '+' | '-' | '*' | '/' | mod | '^' -type bin_op() :: '+' | '-' | '*' | '/' | mod | '^' | 'band' | 'bor' | 'bsl' | 'bsr' | 'bxor'
| '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!=' | '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!='
| '||' | '&&' | '..'. | '||' | '&&' | '..'.
-type un_op() :: '-' | '!'. -type un_op() :: '-' | '!' | 'bnot'.
-type expr() -type expr()
:: {lam, ann(), [arg()], expr()} :: {lam, ann(), [arg()], expr()}
@@ -144,7 +140,3 @@ get_ann(Key, Node) ->
get_ann(Key, Node, Default) -> get_ann(Key, Node, Default) ->
proplists:get_value(Key, get_ann(Node), Default). proplists:get_value(Key, get_ann(Node), Default).
qualify({con, Ann, N}, X) -> qualify({qcon, Ann, [N]}, X);
qualify({qcon, _, NS}, {con, Ann, C}) -> {qcon, Ann, NS ++ [C]};
qualify({qcon, _, NS}, {id, Ann, X}) -> {qid, Ann, NS ++ [X]}.
+81 -134
View File
@@ -6,142 +6,89 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeso_syntax_utils). -module(aeso_syntax_utils).
-export([used_ids/1, used_types/2, used/1]). -export([used_ids/1, used_types/1]).
-record(alg, {zero, plus, scoped}). %% Var set combinators
none() -> [].
one(X) -> [X].
union_map(F, Xs) -> lists:umerge(lists:map(F, Xs)).
minus(Xs, Ys) -> Xs -- Ys.
-type alg(A) :: #alg{ zero :: A %% Compute names used by a definition or expression.
, plus :: fun((A, A) -> A) used_ids(Es) when is_list(Es) ->
, scoped :: fun((A, A) -> A) }. union_map(fun used_ids/1, Es);
used_ids({bind, A, B}) ->
minus(used_ids(B), used_ids(A));
%% Declarations
used_ids({contract, _, _, Decls}) -> used_ids(Decls);
used_ids({type_decl, _, _, _}) -> none();
used_ids({type_def, _, _, _, _}) -> none();
used_ids({fun_decl, _, _, _}) -> none();
used_ids({letval, _, _, _, E}) -> used_ids(E);
used_ids({letfun, _, _, Args, _, E}) -> used_ids({bind, Args, E});
used_ids({letrec, _, Decls}) -> used_ids(Decls);
%% Args
used_ids({arg, _, X, _}) -> used_ids(X);
used_ids({named_arg, _, _, E}) -> used_ids(E);
%% Constants
used_ids({int, _, _}) -> none();
used_ids({bool, _, _}) -> none();
used_ids({hash, _, _}) -> none();
used_ids({unit, _}) -> none();
used_ids({string, _, _}) -> none();
used_ids({char, _, _}) -> none();
%% Expressions
used_ids({lam, _, Args, E}) -> used_ids({bind, Args, E});
used_ids({'if', _, A, B, C}) -> used_ids([A, B, C]);
used_ids({switch, _, E, Bs}) -> used_ids([E, Bs]);
used_ids({app, _, E, Es}) -> used_ids([E | Es]);
used_ids({proj, _, E, _}) -> used_ids(E);
used_ids({tuple, _, Es}) -> used_ids(Es);
used_ids({list, _, Es}) -> used_ids(Es);
used_ids({typed, _, E, _}) -> used_ids(E);
used_ids({record, _, Fs}) -> used_ids(Fs);
used_ids({record, _, E, Fs}) -> used_ids([E, Fs]);
used_ids({map, _, E, Fs}) -> used_ids([E, Fs]);
used_ids({map, _, KVs}) -> used_ids([ [K, V] || {K, V} <- KVs ]);
used_ids({map_get, _, M, K}) -> used_ids([M, K]);
used_ids({map_get, _, M, K, V}) -> used_ids([M, K, V]);
used_ids({block, _, Ss}) -> used_ids_s(Ss);
used_ids({Op, _}) when is_atom(Op) -> none();
used_ids({id, _, X}) -> [X];
used_ids({qid, _, _}) -> none();
used_ids({con, _, _}) -> none();
used_ids({qcon, _, _}) -> none();
%% Switch branches
used_ids({'case', _, P, E}) -> used_ids({bind, P, E});
%% Fields
used_ids({field, _, LV, E}) -> used_ids([LV, E]);
used_ids({field, _, LV, X, E}) -> used_ids([LV, {bind, X, E}]);
used_ids({proj, _, _}) -> none();
used_ids({map_get, _, E}) -> used_ids(E).
-type kind() :: decl | type | bind_type | expr | bind_expr. %% Statements
used_ids_s([]) -> none();
used_ids_s([S | Ss]) ->
used_ids([S, {bind, bound_ids(S), {block, [], Ss}}]).
-spec fold(alg(A), fun((kind(), _) -> A), kind(), E | [E]) -> A bound_ids({letval, _, X, _, _}) -> one(X);
when E :: aeso_syntax:decl() bound_ids({letfun, _, X, _, _, _}) -> one(X);
| aeso_syntax:typedef() bound_ids({letrec, _, Decls}) -> union_map(fun bound_ids/1, Decls);
| aeso_syntax:field_t() bound_ids(_) -> none().
| aeso_syntax:constructor_t()
| aeso_syntax:type()
| aeso_syntax:expr()
| aeso_syntax:pat()
| aeso_syntax:arg()
| aeso_syntax:alt()
| aeso_syntax:elim()
| aeso_syntax:arg_expr()
| aeso_syntax:field(aeso_syntax:expr())
| aeso_syntax:stmt().
fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
Sum = fun(Xs) -> lists:foldl(Plus, Zero, Xs) end,
Same = fun(A) -> fold(Alg, Fun, K, A) end,
Decl = fun(D) -> fold(Alg, Fun, decl, D) end,
Type = fun(T) -> fold(Alg, Fun, type, T) end,
Expr = fun(E) -> fold(Alg, Fun, expr, E) end,
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
Top = Fun(K, X),
Rec = case X of
%% lists (bound things in head scope over tail)
[A | As] -> Scoped(Same(A), Same(As));
%% decl()
{contract, _, _, Ds} -> Decl(Ds);
{namespace, _, _, Ds} -> Decl(Ds);
{type_decl, _, I, _} -> BindType(I);
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
{fun_decl, _, _, T} -> Type(T);
{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))]);
%% typedef()
{alias_t, T} -> Type(T);
{record_t, Fs} -> Type(Fs);
{variant_t, Cs} -> Type(Cs);
%% field_t() and constructor_t()
{field_t, _, _, T} -> Type(T);
{constr_t, _, _, Ts} -> Type(Ts);
%% type()
{fun_t, _, Named, Args, Ret} -> Type([Named, Args, Ret]);
{app_t, _, T, Ts} -> Type([T | Ts]);
{tuple_t, _, Ts} -> Type(Ts);
%% named_arg_t()
{named_arg_t, _, _, T, E} -> Plus(Type(T), Expr(E));
%% expr()
{lam, _, Args, E} -> Scoped(BindExpr(Args), Expr(E));
{'if', _, A, B, C} -> Expr([A, B, C]);
{switch, _, E, Alts} -> Expr([E, Alts]);
{app, _, A, As} -> Expr([A | As]);
{proj, _, E, _} -> Expr(E);
{tuple, _, As} -> Expr(As);
{list, _, As} -> Expr(As);
{typed, _, E, T} -> Plus(Expr(E), Type(T));
{record, _, Fs} -> Expr(Fs);
{record, _, E, Fs} -> Expr([E | Fs]);
{map, _, E, Fs} -> Expr([E | Fs]);
{map, _, KVs} -> Sum([Expr([Key, Val]) || {Key, Val} <- KVs]);
{map_get, _, A, B} -> Expr([A, B]);
{map_get, _, A, B, C} -> Expr([A, B, C]);
{block, _, Ss} -> Expr(Ss);
%% field()
{field, _, LV, E} -> Expr([LV, E]);
{field, _, LV, _, E} -> Expr([LV, E]);
%% arg()
{arg, _, X, T} -> Plus(Expr(X), Type(T));
%% alt()
{'case', _, P, E} -> Scoped(BindExpr(P), Expr(E));
%% elim()
{proj, _, _} -> Zero;
{map_get, _, E} -> Expr(E);
%% arg_expr()
{named_arg, _, _, E} -> Expr(E);
_ -> Alg#alg.zero
end,
(Alg#alg.plus)(Top, Rec).
%% Name dependencies
used_ids(E) ->
[ X || {{term, [X]}, _} <- used(E) ].
used_types([Top] = _CurrentNS, T) ->
F = fun({{type, [X]}, _}) -> [X];
({{type, [Top1, X]}, _}) when Top1 == Top -> [X];
(_) -> []
end,
lists:flatmap(F, used(T)).
-type entity() :: {term, [string()]}
| {type, [string()]}
| {namespace, [string()]}.
-spec entity_alg() -> alg(#{entity() => aeso_syntax:ann()}).
entity_alg() ->
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_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) ->
Bound = [E || E <- maps:keys(Ys), IsBound(E)],
Others = Remove(Bound, Ys),
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
maps:merge(Remove(Bound1, Xs), Others)
end,
#alg{ zero = #{}
, plus = fun maps:merge/2
, scoped = Scoped }.
-spec used(_) -> [{entity(), aeso_syntax:ann()}].
used(D) ->
Kind = fun(expr) -> term;
(bind_expr) -> bound_term;
(type) -> type;
(bind_type) -> bound_type
end,
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
NotBound = fun({{Tag, _}, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
Xs =
maps:to_list(fold(entity_alg(),
fun(K, {id, Ann, X}) -> #{{Kind(K), [X]} => Ann};
(K, {qid, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
(K, {con, Ann, X}) -> #{{Kind(K), [X]} => Ann};
(K, {qcon, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
(_, _) -> #{}
end, decl, D)),
lists:filter(NotBound, Xs).
used_types(Ts) when is_list(Ts) -> union_map(fun used_types/1, Ts);
used_types({type_def, _, _, _, T}) -> used_types(T);
used_types({alias_t, T}) -> used_types(T);
used_types({record_t, Fs}) -> used_types(Fs);
used_types({variant_t, Cs}) -> used_types(Cs);
used_types({field_t, _, _, T}) -> used_types(T);
used_types({constr_t, _, _, Ts}) -> used_types(Ts);
used_types({fun_t, _, Named, Args, T}) -> used_types([T | Named ++ Args]);
used_types({named_arg_t, _, _, T, _}) -> used_types(T);
used_types({app_t, _, T, Ts}) -> used_types([T | Ts]);
used_types({tuple_t, _, Ts}) -> used_types(Ts);
used_types({id, _, X}) -> one(X);
used_types({qid, _, _}) -> none();
used_types({con, _, _}) -> none();
used_types({qcon, _, _}) -> none();
used_types({tvar, _, _}) -> none().
-113
View File
@@ -1,113 +0,0 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc Decoding aevm and fate data to AST
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(aeso_vm_decode).
-export([ from_aevm/3, from_fate/2 ]).
-include_lib("aebytecode/include/aeb_fate_data.hrl").
address_literal(Type, N) -> {Type, [], <<N:256>>}.
-spec from_aevm(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
from_aevm(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
from_aevm(word, {id, _, "int"}, N) -> <<N1:256/signed>> = <<N:256>>, {int, [], N1};
from_aevm(word, {id, _, "bits"}, N) -> error({todo, bits, N});
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
<<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>,
{bytes, [], <<Bytes:Len/unit:8>>};
from_aevm({tuple, _}, {bytes_t, _, Len}, Val) ->
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
from_aevm(string, {id, _, "string"}, S) -> {string, [], S};
from_aevm({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
{list, [], [from_aevm(VmType, Type, X) || X <- List]};
from_aevm({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, 0, []} -> {con, [], "None"};
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [from_aevm(VmType, Type, X)]}
end;
from_aevm({tuple, VmTypes}, {tuple_t, _, Types}, Val)
when length(VmTypes) == length(Types),
length(VmTypes) == tuple_size(Val) ->
{tuple, [], [from_aevm(VmType, Type, X)
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
from_aevm({tuple, VmTypes}, {record_t, Fields}, Val)
when length(VmTypes) == length(Fields),
length(VmTypes) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], from_aevm(VmType, FType, X)}
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
from_aevm({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {from_aevm(VmKeyType, KeyType, Key),
from_aevm(VmValType, ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
from_aevm({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
when length(VmCons) == length(Cons),
length(VmCons) > Tag ->
VmTypes = lists:nth(Tag + 1, VmCons),
ConType = lists:nth(Tag + 1, Cons),
from_aevm(VmTypes, ConType, Args);
from_aevm(VmTypes, {constr_t, _, Con, Types}, Args)
when length(VmTypes) == length(Types),
length(VmTypes) == length(Args) ->
{app, [], Con, [ from_aevm(VmType, Type, Arg)
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
from_aevm(_VmType, _Type, _Data) ->
throw(cannot_translate_to_sophia).
-spec from_fate(aeso_syntax:type(), aeb_fate_data:fate_type()) -> aeso_syntax:expr().
from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin};
from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin};
from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin};
from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin};
from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin};
from_fate({id, _, "bits"}, ?FATE_BITS(Bin)) -> error({todo, bits, Bin});
from_fate({id, _, "int"}, N) when is_integer(N) -> {int, [], N};
from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B};
from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S};
from_fate({app_t, _, {id, _, "list"}, [Type]}, List) when is_list(List) ->
{list, [], [from_fate(Type, X) || X <- List]};
from_fate({app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, [0, 1], 0, {}} -> {con, [], "None"};
{variant, [0, 1], 1, {X}} -> {app, [], {con, [], "Some"}, [from_fate(Type, X)]}
end;
from_fate({tuple_t, _, []}, ?FATE_UNIT) ->
{tuple, [], []};
from_fate({tuple_t, _, Types}, ?FATE_TUPLE(Val))
when length(Types) == tuple_size(Val) ->
{tuple, [], [from_fate(Type, X)
|| {Type, X} <- lists:zip(Types, tuple_to_list(Val))]};
from_fate({record_t, Fields}, ?FATE_TUPLE(Val))
when length(Fields) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], from_fate(FType, X)}
|| {{field_t, _, FName, FType}, X} <- lists:zip(Fields, tuple_to_list(Val)) ]};
from_fate({app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {from_fate(KeyType, Key),
from_fate(ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
from_fate({variant_t, Cons}, {variant, Ar, Tag, Args})
when length(Cons) > Tag ->
ConType = lists:nth(Tag + 1, Cons),
Arity = lists:nth(Tag + 1, Ar),
case tuple_to_list(Args) of
ArgList when length(ArgList) == Arity ->
from_fate(ConType, ArgList);
_ -> throw(cannot_translate_to_sophia)
end;
from_fate({constr_t, _, Con, Types}, Args)
when length(Types) == length(Args) ->
{app, [], Con, [ from_fate(Type, Arg)
|| {Type, Arg} <- lists:zip(Types, Args) ]};
from_fate(_Type, _Data) ->
throw(cannot_translate_to_sophia).
+3 -5
View File
@@ -1,15 +1,13 @@
{application, aesophia, {application, aesophia,
[{description, "Contract Language for aeternity"}, [{description, "Contract Language for Aethernity"},
{vsn, "3.2.0"}, {vsn, "1.2.0"},
{registered, []}, {registered, []},
{applications, {applications,
[kernel, [kernel,
stdlib, stdlib,
jsx,
syntax_tools, syntax_tools,
getopt, getopt,
aebytecode, aebytecode
eblake2
]}, ]},
{env,[]}, {env,[]},
{modules, []}, {modules, []},
+135
View File
@@ -0,0 +1,135 @@
-module(aesophia).
-export([main/1]).
-define(OPT_SPEC,
[ {src_file, undefined, undefined, string, "Sophia source code file"}
, {verbose, $v, "verbose", undefined, "Verbose output"}
, {help, $h, "help", undefined, "Show this message"}
, {create_calldata, $c, "create_calldata", string,
"Create calldata with respect to (compiled) contract in this file"}
, {create_calldata_fun, undefined, "calldata_fun", string,
"Deprecated calldata creation - using function + arguments - function"}
, {create_calldata_args, undefined, "calldata_args", string,
"Deprecated calldata creation - using function + arguments - arguments"}
, {outfile, $o, "out", string, "Output the result to file (experimental)"} ]).
usage() ->
getopt:usage(?OPT_SPEC, "aesophia").
main(Args) ->
case getopt:parse(?OPT_SPEC, Args) of
{ok, {Opts, []}} ->
IsHelp = proplists:get_value(help, Opts, false),
CreateCallData = proplists:get_value(create_calldata, Opts, undefined),
if IsHelp ->
usage();
CreateCallData /= undefined ->
create_calldata(CreateCallData, Opts);
true ->
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),
Res =
try aeso_compiler:file(File, [pp_ast || Verbose]) of
{ok, Map} ->
io:format("\nCompiled successfully!\n"),
{ok, Map};
{error, Reason} ->
io:format("\nError: ~p\n\n", [Reason]),
{error, Reason}
catch
error:Error ->
Where = hd(erlang:get_stacktrace()),
ErrorString = io_lib:format("Error: ~p in\n ~p", [Error, Where]),
io:format("~s\n", [ErrorString]),
{error, list_to_binary(lists:flatten(ErrorString))}
end,
write_outfile(OutFile, Res).
create_calldata(ContractFile, Opts) ->
case file:read_file(ContractFile) of
{ok, Bin} ->
try
Contract = binary_to_term(Bin),
create_calldata_(Contract, Opts)
catch _:_ ->
io:format("Error: Bad contract file ~s\n\n", [ContractFile]), usage()
end;
{error, _} ->
io:format("Error: Could not find file ~s\n\n", [ContractFile]), usage()
end.
create_calldata_(Contract, Opts) ->
case proplists:get_value(src_file, Opts, undefined) of
undefined -> %% Check if old deprecated style is used
case {proplists:get_value(create_calldata_fun, Opts, undefined),
proplists:get_value(create_calldata_args, Opts, undefined)} of
{undefined, _} ->
io:format("Error: not enough create call data input\n\n"), usage();
{_, undefined} ->
io:format("Error: not enough create call data input\n\n"), usage();
{Fun, Args} ->
create_calldata(Contract, Fun, Args, Opts)
end;
CallFile ->
case file:read_file(CallFile) of
{ok, Bin} ->
create_calldata(Contract, "", binary_to_list(Bin), Opts);
{error, _} ->
io:format("Error: Could not find file ~s\n\n", [CallFile]), usage()
end
end.
create_calldata(Contract, CallFun, CallArgs, Opts) ->
OutFile = proplists:get_value(outfile, Opts, undefined),
Res = try
case aeso_compiler:create_calldata(Contract, CallFun, CallArgs) of
{ok, CallData, _CallDataType, _OutputType} ->
io:format("Call data created successfully!\n"),
{ok, CallData};
Err = {error, Reason} ->
io:format("Error: Create calldata failed: ~p\n\n", [Reason]),
Err
end
catch
error:Error ->
Where = hd(erlang:get_stacktrace()),
ErrorString = io_lib:format("Error: ~p in\n ~p", [Error, Where]),
io:format("~s\n", [ErrorString]),
{error, list_to_binary(lists:flatten(ErrorString))}
end,
write_outfile(OutFile, Res).
write_outfile(undefined, _) -> ok;
write_outfile(Out, Res) ->
%% Lazy approach
file:write_file(Out, term_to_binary(Res)),
io:format("Output written to: ~s\n\n", [Out]).
+20 -132
View File
@@ -1,12 +1,9 @@
-module(aeso_abi_tests). -module(aeso_abi_tests).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-compile([export_all, nowarn_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(),
@@ -22,8 +19,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(aeb_heap:from_binary({list, word}, CircularList)), {ok, {error, circular_references}} = ?SANDBOX(aeso_heap:from_binary({list, word}, CircularList)),
{ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeb_heap:from_binary(word, <<1, 2, 3, 4>>)), {ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeso_heap:from_binary(word, <<1, 2, 3, 4>>)),
ok. ok.
from_words(Ws) -> from_words(Ws) ->
@@ -57,144 +54,35 @@ encode_decode_test() ->
ok. ok.
encode_decode_sophia_test() -> encode_decode_sophia_test() ->
Check = fun(Type, Str) -> case {encode_decode_sophia_string(Type, Str), Str} of {42} = encode_decode_sophia_string("int", "42"),
{X, X} -> ok; {1} = encode_decode_sophia_string("bool", "true"),
Other -> Other {0} = encode_decode_sophia_string("bool", "false"),
end end, {<<"Hello">>} = encode_decode_sophia_string("string", "\"Hello\""),
ok = Check("int", "42"), {<<"Hello">>, [1,2,3], {variant, 1, [1]}} =
ok = Check("int", "-42"), encode_decode_sophia_string(
ok = Check("bool", "true"), "(string, list(int), option(bool))",
ok = Check("bool", "false"), "\"Hello\", [1,2,3], Some(true)"),
ok = Check("string", "\"Hello\""),
ok = Check("string * list(int) * option(bool)",
"(\"Hello\", [1, 2, 3], Some(true))"),
ok = Check("variant", "Blue({[\"x\"] = 1})"),
ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
ok. ok.
encode_decode_sophia_string(SophiaType, String) -> encode_decode_sophia_string(SophiaType, String) ->
io:format("String ~p~n", [String]), io:format("String ~p~n", [String]),
Code = [ "contract MakeCall =\n" Code = [ "contract Call =\n"
, " type arg_type = ", SophiaType, "\n" , " function foo : ", SophiaType, " => _\n"
, " type an_alias('a) = string * 'a\n" , " function __call() = foo(", String, ")\n" ],
, " record r = {x : an_alias(int), y : variant}\n" {ok, _, {Types, _}, Args} = aeso_compiler:check_call(lists:flatten(Code), []),
, " datatype variant = Red | Blue(map(string, int))\n" Arg = list_to_tuple(Args),
, " entrypoint foo : arg_type => arg_type\n" ], Type = {tuple, Types},
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], []) of
{ok, _, {[Type], _}, [Arg]} ->
io:format("Type ~p~n", [Type]), io:format("Type ~p~n", [Type]),
Data = encode(Arg), Data = encode(Arg),
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data) of decode(Type, Data).
{ok, Sophia} ->
lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))]));
{error, Err} ->
io:format("~s\n", [Err]),
{error, Err}
end;
{error, Err} ->
io:format("~s\n", [Err]),
{error, Err}
end.
calldata_test() ->
[42, <<"foobar">>] = encode_decode_calldata("foo", ["int", "string"], ["42", "\"foobar\""]),
Map = #{ <<"a">> => 4 },
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
[?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"],
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
[?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] =
encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]),
[119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]),
[16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
ok.
calldata_init_test() ->
encode_decode_calldata("init", ["int"], ["42"], {tuple, [typerep, word]}),
Code = parameterized_contract("foo", ["int"]),
encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}).
calldata_indent_test() ->
Test = fun(Extra) ->
Code = parameterized_contract(Extra, "foo", ["int"]),
encode_decode_calldata_(Code, "foo", ["42"], word)
end,
Test(" stateful entrypoint bla() = ()"),
Test(" type x = int"),
Test(" stateful entrypoint bla(x : int) =\n"
" x + 1"),
Test(" stateful entrypoint bla(x : int) : int =\n"
" x + 1"),
ok.
parameterized_contract(FunName, Types) ->
parameterized_contract([], FunName, Types).
parameterized_contract(ExtraCode, FunName, Types) ->
lists:flatten(
["contract Remote =\n"
" entrypoint bla : () => unit\n\n"
"contract Dummy =\n",
ExtraCode, "\n",
" type an_alias('a) = string * 'a\n"
" record r = {x : an_alias(int), y : variant}\n"
" datatype variant = Red | Blue(map(string, int))\n"
" entrypoint ", FunName, " : (", string:join(Types, ", "), ") => int\n" ]).
oracle_test() ->
Contract =
"contract OracleTest =\n"
" entrypoint question(o, q : oracle_query(list(string), option(int))) =\n"
" Oracle.get_question(o, q)\n",
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
"oq_1111111111111111111111111111113AFEFpt5"], []),
ok.
permissive_literals_fail_test() ->
Contract =
"contract OracleTest =\n"
" stateful entrypoint haxx(o : oracle(list(string), option(int))) =\n"
" Chain.spend(o, 1000000)\n",
{error, <<"Type errors\nCannot unify", _/binary>>} =
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
ok.
encode_decode_calldata(FunName, Types, Args) ->
encode_decode_calldata(FunName, Types, Args, word).
encode_decode_calldata(FunName, Types, Args, RetType) ->
Code = parameterized_contract(FunName, Types),
encode_decode_calldata_(Code, FunName, Args, RetType).
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
{ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args),
{ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}]),
?assertEqual(RetType, RetVMType),
CalldataType = {tuple, [word, {tuple, ArgTypes}]},
{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).
encode_decode(T, D) -> encode_decode(T, D) ->
?assertEqual(D, decode(T, encode(D))), ?assertEqual(D, decode(T, encode(D))),
D. D.
encode(D) -> encode(D) ->
aeb_heap:to_binary(D). aeso_heap:to_binary(D).
decode(T,B) -> decode(T,B) ->
{ok, D} = aeb_heap:from_binary(T, B), {ok, D} = aeso_heap:from_binary(T, B),
D. D.
-122
View File
@@ -1,122 +0,0 @@
-module(aeso_aci_tests).
-include_lib("eunit/include/eunit.hrl").
simple_aci_test_() ->
[{"Test contract " ++ integer_to_list(N),
fun() -> test_contract(N) end}
|| N <- [1, 2, 3]].
test_contract(N) ->
{Contract,MapACI,DecACI} = test_cases(N),
{ok,JSON} = aeso_aci:contract_interface(json, Contract),
?assertEqual([MapACI], JSON),
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)).
test_cases(1) ->
Contract = <<"contract C =\n"
" entrypoint a(i : int) = i+1\n">>,
MapACI = #{contract =>
#{name => <<"C">>,
type_defs => [],
functions =>
[#{name => <<"a">>,
arguments =>
[#{name => <<"i">>,
type => <<"int">>}],
returns => <<"int">>,
stateful => false}]}},
DecACI = <<"contract C =\n"
" entrypoint a : (int) => int\n">>,
{Contract,MapACI,DecACI};
test_cases(2) ->
Contract = <<"contract C =\n"
" type allan = int\n"
" entrypoint a(i : allan) = i+1\n">>,
MapACI = #{contract =>
#{name => <<"C">>,
type_defs =>
[#{name => <<"allan">>,
typedef => <<"int">>,
vars => []}],
functions =>
[#{arguments =>
[#{name => <<"i">>,
type => <<"C.allan">>}],
name => <<"a">>,
returns => <<"int">>,
stateful => false}]}},
DecACI = <<"contract C =\n"
" type allan = int\n"
" entrypoint a : (C.allan) => int\n">>,
{Contract,MapACI,DecACI};
test_cases(3) ->
Contract = <<"contract C =\n"
" type state = unit\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n"
" entrypoint a(i : bert(string)) = 1\n">>,
MapACI = #{contract =>
#{functions =>
[#{arguments =>
[#{name => <<"i">>,
type =>
#{<<"C.bert">> => [<<"string">>]}}],
name => <<"a">>,returns => <<"int">>,
stateful => false}],
name => <<"C">>,
event => #{variant => [#{<<"SingleEventDefined">> => []}]},
state => <<"unit">>,
type_defs =>
[#{name => <<"bert">>,
typedef =>
#{variant =>
[#{<<"Bin">> => [<<"'a">>]}]},
vars => [#{name => <<"'a">>}]}]}},
DecACI = <<"contract C =\n"
" type state = unit\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n"
" entrypoint a : (C.bert(string)) => int\n">>,
{Contract,MapACI,DecACI}.
%% Rounttrip
aci_test_() ->
[{"Testing ACI generation for " ++ ContractName,
fun() -> aci_test_contract(ContractName) end}
|| ContractName <- all_contracts()].
all_contracts() -> aeso_compiler_tests:compilable_contracts().
aci_test_contract(Name) ->
String = aeso_test_utils:read_contract(Name),
Opts = [{include, {file_system, [aeso_test_utils:contract_path()]}}],
{ok, JSON} = aeso_aci:contract_interface(json, String, Opts),
io:format("JSON:\n~p\n", [JSON]),
{ok, ContractStub} = aeso_aci:render_aci_json(JSON),
io:format("STUB:\n~s\n", [ContractStub]),
check_stub(ContractStub, [{src_file, Name}]),
ok.
check_stub(Stub, Options) ->
case aeso_parser:string(binary_to_list(Stub), Options) of
{ok, Ast} ->
try
%% io:format("AST: ~120p\n", [Ast]),
aeso_ast_infer_types:infer(Ast, [])
catch _:{type_errors, TE} ->
io:format("Type error:\n~s\n", [TE]),
error(TE);
_:R ->
io:format("Error: ~p\n", [R]),
error(R)
end;
{error, E} ->
io:format("Error: ~p\n", [E]),
error({parse_error, E})
end.
+73
View File
@@ -0,0 +1,73 @@
%%%=============================================================================
%%% @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.
-128
View File
@@ -1,128 +0,0 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc Test Sophia language compiler.
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(aeso_calldata_tests).
-compile([export_all, nowarn_export_all]).
-include_lib("eunit/include/eunit.hrl").
%% Very simply test compile the given contracts. Only basic checks
%% are made on the output, just that it is a binary which indicates
%% that the compilation worked.
calldata_test_() ->
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
fun() ->
ContractString = aeso_test_utils:read_contract(ContractName),
AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}]);
false -> undefined
end,
FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]);
false -> undefined
end,
case FateExprs == undefined orelse AevmExprs == undefined of
true -> ok;
false ->
?assertEqual(FateExprs, AevmExprs)
end
end} || {ContractName, Fun, Args} <- compilable_contracts()].
calldata_aci_test_() ->
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
fun() ->
ContractString = aeso_test_utils:read_contract(ContractName),
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString),
ContractACI = binary_to_list(ContractACIBin),
io:format("ACI:\n~s\n", [ContractACIBin]),
AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, aevm}]);
false -> undefined
end,
FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]);
false -> undefined
end,
case FateExprs == undefined orelse AevmExprs == undefined of
true -> ok;
false ->
?assertEqual(FateExprs, AevmExprs)
end
end} || {ContractName, Fun, Args} <- compilable_contracts()].
ast_exprs(ContractString, Fun, Args, Opts) ->
{ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)),
{ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)),
?assert(is_list(Exprs)),
Exprs.
check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well.
Actual = binary:split(<<ErrorString/binary,$\n>>, <<"\n\n">>, [global,trim]),
case {Expect -- Actual, Actual -- Expect} of
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
{Missing, Extra} -> ?assertEqual(Missing, Extra)
end.
%% compilable_contracts() -> [ContractName].
%% The currently compilable contracts.
compilable_contracts() ->
[
{"identity", "init", []},
{"maps", "init", []},
{"funargs", "menot", ["false"]},
{"funargs", "append", ["[\"false\", \" is\", \" not\", \" true\"]"]},
%% TODO {"funargs", "bitsum", ["Bits.all"]},
{"funargs", "read", ["{label = \"question 1\", result = 4}"]},
{"funargs", "sjutton", ["#0011012003100011012003100011012003"]},
{"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940"
"414243444546474849505152535455565758596061626364656667"]},
{"funargs", "trettiotva", ["#0102030405060708091011121314151617181920212223242526272829303132"]},
{"funargs", "find_oracle", ["ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5"]},
{"funargs", "find_query", ["oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY"]},
{"funargs", "traffic_light", ["Green"]},
{"funargs", "traffic_light", ["Pantone(12)"]},
{"funargs", "tuples", ["()"]},
%% TODO {"funargs", "due", ["FixedTTL(1020)"]},
{"variant_types", "init", []},
{"basic_auth", "init", []},
{"address_literals", "init", []},
{"bytes_equality", "init", []},
{"address_chain", "init", []},
{"counter", "init",
["-3334353637383940202122232425262728293031323334353637"]},
{"dutch_auction", "init",
["ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt", "200000", "1000"]},
{"maps", "fromlist_i",
["[(1, {x = 1, y = 2}), (2, {x = 3, y = 4}), (3, {x = 4, y = 4})]"]},
{"maps", "get_i", ["1", "{}"]},
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}}"]},
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}, [2] = {x = 4, y = 5}}"]},
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}, [2] = {x = 4, y = 5}, [3] = {x = 5, y = 6}}"]},
{"strings", "str_concat", ["\"test\"","\"me\""]},
{"complex_types", "filter_some", ["[Some(11), Some(12), None]"]},
{"complex_types", "init", ["ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ"]},
{"__call" "init", []},
{"bitcoin_auth", "authorize", ["1", "#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f202122232425262728293031323334353637"]},
{"bitcoin_auth", "to_sign", ["#0102030405060708090a0b0c0d0e0f1017181920212223242526272829303132", "2"]},
{"stub", "foo", ["42"]},
{"stub", "foo", ["-42"]}
].
not_yet_compilable(fate) ->
[];
not_yet_compilable(aevm) ->
[].
+36 -223
View File
@@ -8,60 +8,31 @@
-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").
%% simple_compile_test_() -> ok.
%% Very simply test compile the given contracts. Only basic checks %% Very simply test compile the given contracts. Only basic checks
%% are made on the output, just that it is a binary which indicates %% are made on the output, just that it is a binary which indicates
%% that the compilation worked. %% that the compilation worked.
simple_compile_test_() -> simple_compile_test_() ->
[ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend", {setup,
fun () -> ok end, %Setup
fun (_) -> ok end, %Cleanup
[ {"Testing the " ++ ContractName ++ " contract",
fun() -> fun() ->
case compile(Backend, ContractName) of
#{byte_code := ByteCode, #{byte_code := ByteCode,
contract_source := _, contract_source := _,
type_info := _} when Backend == aevm -> type_info := _} = compile(ContractName),
?assertMatch(Code when is_binary(Code), ByteCode); ?assertMatch(Code when is_binary(Code), ByteCode)
#{fate_code := Code} when Backend == fate -> end} || ContractName <- compilable_contracts() ] ++
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
?assertMatch({X, X}, {Code1, Code});
ErrBin ->
io:format("\n~s", [ErrBin]),
error(ErrBin)
end
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
not lists:member(ContractName, not_yet_compilable(Backend))] ++
[ {"Testing error messages of " ++ ContractName, [ {"Testing error messages of " ++ ContractName,
fun() -> fun() ->
case compile(aevm, ContractName) of <<"Type errors\n",ErrorString/binary>> = compile(ContractName),
<<"Type errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString);
<<"Parse errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString) check_errors(lists:sort(ExpectedErrors), ErrorString)
end
end} || end} ||
{ContractName, ExpectedErrors} <- failing_contracts() ] ++ {ContractName, ExpectedErrors} <- failing_contracts() ]
[ {"Testing include with explicit files", }.
fun() ->
FileSystem = maps:from_list(
[ begin
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])),
{File, Bin}
end || File <- ["included.aes", "../contracts/included2.aes"] ]),
#{byte_code := Code1} = compile(aevm, "include", [{include, {explicit_files, FileSystem}}]),
#{byte_code := Code2} = compile(aevm, "include"),
?assertMatch(true, Code1 == Code2)
end} ] ++
[ {"Testing deadcode elimination",
fun() ->
#{ byte_code := NoDeadCode } = compile(aevm, "nodeadcode"),
#{ byte_code := DeadCode } = compile(aevm, "deadcode"),
SizeNoDeadCode = byte_size(NoDeadCode),
SizeDeadCode = byte_size(DeadCode),
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + 40 < SizeNoDeadCode}),
ok
end} ].
check_errors(Expect, ErrorString) -> check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well. %% This removes the final single \n as well.
@@ -72,14 +43,11 @@ check_errors(Expect, ErrorString) ->
{Missing, Extra} -> ?assertEqual(Missing, Extra) {Missing, Extra} -> ?assertEqual(Missing, Extra)
end. end.
compile(Backend, Name) -> compile(Name) ->
compile(Backend, Name, [{include, {file_system, [aeso_test_utils:contract_path()]}}]).
compile(Backend, Name, Options) ->
String = aeso_test_utils:read_contract(Name), String = aeso_test_utils:read_contract(Name),
case aeso_compiler:from_string(String, [{src_file, Name}, {backend, Backend} | Options]) of case aeso_compiler:from_string(String, []) of
{ok, Map} -> Map; {ok,Map} -> Map;
{error, ErrorString} -> ErrorString {error,ErrorString} -> ErrorString
end. end.
%% compilable_contracts() -> [ContractName]. %% compilable_contracts() -> [ContractName].
@@ -91,7 +59,6 @@ compilable_contracts() ->
"dutch_auction", "dutch_auction",
"environment", "environment",
"factorial", "factorial",
"functions",
"fundme", "fundme",
"identity", "identity",
"maps", "maps",
@@ -103,27 +70,9 @@ compilable_contracts() ->
"stack", "stack",
"test", "test",
"builtin_bug", "builtin_bug",
"builtin_map_get_bug", "builtin_map_get_bug"
"nodeadcode",
"deadcode",
"variant_types",
"state_handling",
"events",
"include",
"basic_auth",
"bitcoin_auth",
"address_literals",
"bytes_equality",
"address_chain",
"namespace_bug",
"bytes_to_x",
"aens",
"tuple_match"
]. ].
not_yet_compilable(fate) -> [];
not_yet_compilable(aevm) -> [].
%% Contracts that should produce type errors %% Contracts that should produce type errors
failing_contracts() -> failing_contracts() ->
@@ -131,9 +80,6 @@ failing_contracts() ->
[<<"Duplicate definitions of abort at\n" [<<"Duplicate definitions of abort at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 14, column 3">>, " - line 14, column 3">>,
<<"Duplicate definitions of require at\n"
" - (builtin location)\n"
" - line 15, column 3">>,
<<"Duplicate definitions of double_def at\n" <<"Duplicate definitions of double_def at\n"
" - line 10, column 3\n" " - line 10, column 3\n"
" - line 11, column 3">>, " - line 11, column 3">>,
@@ -145,12 +91,12 @@ failing_contracts() ->
" - line 8, column 3">>, " - line 8, column 3">>,
<<"Duplicate definitions of put at\n" <<"Duplicate definitions of put at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 16, column 3">>, " - line 15, column 3">>,
<<"Duplicate definitions of state at\n" <<"Duplicate definitions of state at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 17, column 3">>]} " - line 16, column 3">>]}
, {"type_errors", , {"type_errors",
[<<"Unbound variable zz at line 17, column 23">>, [<<"Unbound variable zz at line 17, column 21">>,
<<"Cannot unify int\n" <<"Cannot unify int\n"
" and list(int)\n" " and list(int)\n"
"when checking the application at line 26, column 9 of\n" "when checking the application at line 26, column 9 of\n"
@@ -161,18 +107,18 @@ failing_contracts() ->
<<"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the assignment of the field\n" "when checking the assignment of the field\n"
" x : map(string, string) (at line 9, column 48)\n" " x : map(string, string) (at line 9, column 46)\n"
"to the old value __x and the new value\n" "to the old value __x and the new value\n"
" __x {[\"foo\"] @ x = x + 1} : map(string, int)">>, " __x {[\"foo\"] @ x = x + 1} : map(string, int)">>,
<<"Cannot unify int\n" <<"Cannot unify int\n"
" and string\n" " and string\n"
"when checking the type of the expression at line 34, column 47\n" "when checking the type of the expression at line 34, column 45\n"
" 1 : int\n" " 1 : int\n"
"against the expected type\n" "against the expected type\n"
" string">>, " string">>,
<<"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 34, column 52\n" "when checking the type of the expression at line 34, column 50\n"
" \"bla\" : string\n" " \"bla\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
@@ -184,7 +130,7 @@ failing_contracts() ->
" int">>, " int">>,
<<"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 11, column 58\n" "when checking the type of the expression at line 11, column 56\n"
" \"foo\" : string\n" " \"foo\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
@@ -194,164 +140,31 @@ failing_contracts() ->
" - w : int (at line 38, column 13)\n" " - w : int (at line 38, column 13)\n"
" - z : string (at line 39, column 10)">>, " - z : string (at line 39, column 10)">>,
<<"Not a record type: string\n" <<"Not a record type: string\n"
"arising from the projection of the field y (at line 22, column 40)">>, "arising from the projection of the field y (at line 22, column 38)">>,
<<"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 21, column 44)">>, "arising from an assignment of the field y (at line 21, column 42)">>,
<<"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 20, column 40)">>, "arising from an assignment of the field y (at line 20, column 38)">>,
<<"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 19, column 37)">>, "arising from an assignment of the field y (at line 19, column 35)">>,
<<"Ambiguous record type with field y (at line 13, column 27) could be one of\n" <<"Ambiguous record type with field y (at line 13, column 25) could be one of\n"
" - r (at line 4, column 10)\n" " - r (at line 4, column 10)\n"
" - r' (at line 5, column 10)">>, " - r' (at line 5, column 10)">>,
<<"Record type r2 does not have field y (at line 15, column 22)">>,
<<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
<<"Repeated name x in pattern\n" <<"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 14).">>, <<"No record type with fields y, z (at line 14, column 22)">>]}
<<"Repeated argument y to function repeated_arg (at line 44, column 14).">>,
<<"No record type with fields y, z (at line 14, column 24)">>,
<<"The field z is missing when constructing an element of type r2 (at line 15, column 26)">>,
<<"Record type r2 does not have field y (at line 15, column 24)">>,
<<"Let binding at line 47, column 5 must be followed by an expression">>,
<<"Let binding at line 50, column 5 must be followed by an expression">>,
<<"Let binding at line 54, column 5 must be followed by an expression">>,
<<"Let binding at line 58, column 5 must be followed by an expression">>]}
, {"init_type_error", , {"init_type_error",
[<<"Cannot unify string\n" [<<"Cannot unify string\n"
" and map(int, int)\n" " and map(int, int)\n"
"when checking that 'init' returns a value of type 'state' at line 7, column 3">>]} "when checking that 'init' returns a value of type 'state' at line 7, column 3">>]}
, {"missing_state_type", , {"missing_state_type",
[<<"Cannot unify string\n" [<<"Cannot unify string\n"
" and unit\n" " and ()\n"
"when checking that 'init' returns a value of type 'state' at line 5, column 3">>]} "when checking that 'init' returns a value of type 'state' at line 5, column 3">>]}
, {"missing_fields_in_record_expression", , {"missing_fields_in_record_expression",
[<<"The field x is missing when constructing an element of type r('a) (at line 7, column 42)">>, [<<"The field x is missing when constructing an element of type r('a) (at line 7, column 40)">>,
<<"The field y is missing when constructing an element of type r(int) (at line 8, column 42)">>, <<"The field y is missing when constructing an element of type r(int) (at line 8, column 40)">>,
<<"The fields y, z are missing when constructing an element of type r('a) (at line 6, column 42)">>]} <<"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)">>]}
, {"namespace_clash",
[<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]}
, {"bad_events",
[<<"The indexed type string (at line 9, column 25) is not a word type">>,
<<"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>]}
, {"bad_events2",
[<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>,
<<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>]}
, {"type_clash",
[<<"Cannot unify int\n"
" and string\n"
"when checking the record projection at line 12, column 42\n"
" r.foo : (gas : int, value : int) => Remote.themap\n"
"against the expected type\n"
" (gas : int, value : int) => map(string, int)">>]}
, {"bad_include_and_ns",
[<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
<<"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]}
, {"bad_address_literals",
[<<"The type bytes(32) is not a contract type\n"
"when checking that the contract literal at line 32, column 5\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n"
" bytes(32)">>,
<<"The type oracle(int, bool) is not a contract type\n"
"when checking that the contract literal at line 30, column 5\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n"
" oracle(int, bool)">>,
<<"The type address is not a contract type\n"
"when checking that the contract literal at line 28, column 5\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n"
" address">>,
<<"Cannot unify oracle_query('a, 'b)\n"
" and Remote\n"
"when checking the type of the expression at line 25, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('a, 'b)\n"
"against the expected type\n"
" Remote">>,
<<"Cannot unify oracle_query('c, 'd)\n"
" and bytes(32)\n"
"when checking the type of the expression at line 23, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('c, 'd)\n"
"against the expected type\n"
" bytes(32)">>,
<<"Cannot unify oracle_query('e, 'f)\n"
" and oracle(int, bool)\n"
"when checking the type of the expression at line 21, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('e, 'f)\n"
"against the expected type\n"
" oracle(int, bool)">>,
<<"Cannot unify oracle('g, 'h)\n"
" and Remote\n"
"when checking the type of the expression at line 18, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('g, 'h)\n"
"against the expected type\n"
" Remote">>,
<<"Cannot unify oracle('i, 'j)\n"
" and bytes(32)\n"
"when checking the type of the expression at line 16, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('i, 'j)\n"
"against the expected type\n"
" bytes(32)">>,
<<"Cannot unify oracle('k, 'l)\n"
" and oracle_query(int, bool)\n"
"when checking the type of the expression at line 14, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('k, 'l)\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 35)\nin the definition of non-stateful function fail1.">>,
<<"Cannot reference stateful function local_spend (at line 14, column 35)\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 47)\nin the definition of non-stateful function fail5.">>,
<<"Cannot pass non-zero value argument 1000 (at line 48, column 57)\nin the definition of non-stateful function fail6.">>,
<<"Cannot pass non-zero value argument 1000 (at line 49, column 56)\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 38), which calls\n"
" - put (at line 7, column 39)">>,
<<"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 29)">>,
<<"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)">>]}
, {"field_parse_error",
[<<"line 6, column 1: In field_parse_error at 5:26:\n"
"Cannot use nested fields or keys in record construction: p.x\n">>]}
, {"modifier_checks",
[<<"The function all_the_things (at line 11, column 3) cannot be both public and private.">>,
<<"Namespaces cannot contain entrypoints (at line 3, column 3). Use 'function' instead.">>,
<<"The contract Remote (at line 5, column 10) has no entrypoints. Since Sophia version 3.2, public\ncontract functions must be declared with the 'entrypoint' keyword instead of\n'function'.">>,
<<"The entrypoint wha (at line 12, column 3) cannot be private. Use 'function' instead.">>,
<<"Use 'entrypoint' for declaration of foo (at line 6, column 3):\n entrypoint foo : () => unit">>,
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 10, column 3):\n entrypoint foo() = ()">>,
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>]}
]. ].
-2
View File
@@ -12,11 +12,9 @@ groups() ->
, aeso_parser_tests , aeso_parser_tests
, aeso_compiler_tests , aeso_compiler_tests
, aeso_abi_tests , aeso_abi_tests
, aeso_aci_tests
]}]. ]}].
aeso_scan_tests(_Config) -> ok = eunit:test(aeso_scan_tests). aeso_scan_tests(_Config) -> ok = eunit:test(aeso_scan_tests).
aeso_parser_tests(_Config) -> ok = eunit:test(aeso_parser_tests). aeso_parser_tests(_Config) -> ok = eunit:test(aeso_parser_tests).
aeso_compiler_tests(_Config) -> ok = eunit:test(aeso_compiler_tests). aeso_compiler_tests(_Config) -> ok = eunit:test(aeso_compiler_tests).
aeso_abi_tests(_Config) -> ok = eunit:test(aeso_abi_tests). aeso_abi_tests(_Config) -> ok = eunit:test(aeso_abi_tests).
aeso_aci_tests(_Config) -> ok = eunit:test(aeso_aci_tests).
+1 -1
View File
@@ -62,7 +62,7 @@ simple_contracts_test_() ->
%% Parse tests of example contracts %% Parse tests of example contracts
[ {lists:concat(["Parse the ", Contract, " contract."]), [ {lists:concat(["Parse the ", Contract, " contract."]),
fun() -> roundtrip_contract(Contract) end} fun() -> roundtrip_contract(Contract) end}
|| Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, fundme, dutch_auction] ] || Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, withdrawal, fundme, dutch_auction] ]
}. }.
parse_contract(Name) -> parse_contract(Name) ->
+3 -3
View File
@@ -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]) ++ lists:map(Lit, [contract, type, 'let', switch, rec, 'and']) ++
%% 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(bytes, Hash) , Tok(hash, 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({bytes, _, <<N:256>>}) -> fmt("#~64.16.0b", N); show_token({hash, _, <<N:256>>}) -> fmt("#~.16b", N);
show_token({comment, _, S}) -> S; show_token({comment, _, S}) -> S;
show_token({_, _, _}) -> "TODO". show_token({_, _, _}) -> "TODO".
+28
View File
@@ -0,0 +1,28 @@
-module(contract_tests).
-include_lib("eunit/include/eunit.hrl").
make_cmd() -> "make -C " ++ aeso_test_utils:contract_path().
contracts_test_() ->
{setup,
fun() -> os:cmd(make_cmd()) end,
fun(_) -> os:cmd(make_cmd() ++ " clean") end,
[ {"Testing the " ++ Contract ++ " contract",
fun() ->
?assertCmdOutput(Expected, filename:join(aeso_test_utils:contract_path(), Contract ++ "_test"))
end} || {Contract, Expected} <- contracts() ]}.
contracts() ->
[].
%% [{"voting",
%% "Delegate before vote\n"
%% "Cake: 1\n"
%% "Beer: 2\n"
%% "Winner: Beer\n"
%% "Delegate after vote\n"
%% "Cake: 1\n"
%% "Beer: 2\n"
%% "Winner: Beer\n"
%% }].
-5
View File
@@ -1,5 +0,0 @@
contract Identity =
function main (x:int) = x
function __call() = 12
+1 -1
View File
@@ -8,7 +8,7 @@ contract AbortTest =
{ value = v } { value = v }
// Aborting // Aborting
public function do_abort(v : int, s : string) : unit = public function do_abort(v : int, s : string) : () =
put_value(v) put_value(v)
revert_abort(s) revert_abort(s)
+3 -3
View File
@@ -1,9 +1,9 @@
contract Interface = contract Interface =
function do_abort : (int, string) => unit function do_abort : (int, string) => ()
function get_value : () => int function get_value : () => int
function put_value : (int) => unit function put_value : (int) => ()
function get_values : () => list(int) function get_values : () => list(int)
function put_values : (int) => unit function put_values : (int) => ()
contract AbortTestInt = contract AbortTestInt =
-33
View File
@@ -1,33 +0,0 @@
contract Remote =
entrypoint main : (int) => unit
contract AddrChain =
type o_type = oracle(string, map(string, int))
type oq_type = oracle_query(string, map(string, int))
entrypoint is_o(a : address) =
Address.is_oracle(a)
entrypoint is_c(a : address) =
Address.is_contract(a)
// entrypoint get_o(a : address) : option(o_type) =
// Address.get_oracle(a)
// entrypoint get_c(a : address) : option(Remote) =
// Address.get_contract(a)
entrypoint check_o(o : o_type) =
Oracle.check(o)
entrypoint check_oq(o : o_type, oq : oq_type) =
Oracle.check_query(o, oq)
// entrypoint h_to_i(h : hash) : int =
// Hash.to_int(h)
// entrypoint a_to_i(a : address) : int =
// Address.to_int(a) mod 10 ^ 16
entrypoint c_creator() : address =
Contract.creator
-14
View File
@@ -1,14 +0,0 @@
contract Remote =
entrypoint foo : () => unit
contract AddressLiterals =
entrypoint addr() : address =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint oracle() : oracle(int, bool) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint query() : oracle_query(int, bool) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint contr() : Remote =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
+27 -28
View File
@@ -3,54 +3,53 @@ contract AENSTest =
// Name resolution // Name resolution
stateful entrypoint resolve_word(name : string, key : string) : option(address) = function resolve_word(name : string, key : string) : option(address) =
AENS.resolve(name, key) AENS.resolve(name, key)
stateful entrypoint resolve_string(name : string, key : string) : option(string) = function resolve_string(name : string, key : string) : option(string) =
AENS.resolve(name, key) AENS.resolve(name, key)
// Transactions // Transactions
stateful entrypoint preclaim(addr : address, // Claim on behalf of this account (can be Contract.address) function preclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
chash : hash) : unit = // Commitment hash chash : hash) : () = // Commitment hash
AENS.preclaim(addr, chash) AENS.preclaim(addr, chash)
stateful entrypoint signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address) function signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
chash : hash, // Commitment hash chash : hash, // Commitment hash
sign : signature) : unit = // Signed by addr (if not Contract.address) sign : signature) : () = // Signed by addr (if not Contract.address)
AENS.preclaim(addr, chash, signature = sign) AENS.preclaim(addr, chash, signature = sign)
stateful entrypoint claim(addr : address, function claim(addr : address,
name : string, name : string,
salt : int, salt : int) : () =
name_fee : int) : unit = AENS.claim(addr, name, salt)
AENS.claim(addr, name, salt, name_fee)
stateful entrypoint signedClaim(addr : address, function signedClaim(addr : address,
name : string, name : string,
salt : int, salt : int,
name_fee : int, sign : signature) : () =
sign : signature) : unit = AENS.claim(addr, name, salt, signature = sign)
AENS.claim(addr, name, salt, name_fee, signature = sign)
// TODO: update() -- how to handle pointers? // TODO: update() -- how to handle pointers?
stateful entrypoint transfer(owner : address, function transfer(owner : address,
new_owner : address, new_owner : address,
name : string) : unit = name_hash : hash) : () =
AENS.transfer(owner, new_owner, name) AENS.transfer(owner, new_owner, name_hash)
stateful entrypoint signedTransfer(owner : address, function signedTransfer(owner : address,
new_owner : address, new_owner : address,
name : string, name_hash : hash,
sign : signature) : unit = sign : signature) : () =
AENS.transfer(owner, new_owner, name, signature = sign) AENS.transfer(owner, new_owner, name_hash, signature = sign)
stateful entrypoint revoke(owner : address, function revoke(owner : address,
name : string) : unit = name_hash : hash) : () =
AENS.revoke(owner, name) AENS.revoke(owner, name_hash)
function signedRevoke(owner : address,
name_hash : hash,
sign : signature) : () =
AENS.revoke(owner, name_hash, signature = sign)
stateful entrypoint signedRevoke(owner : address,
name : string,
sign : signature) : unit =
AENS.revoke(owner, name, signature = sign)
+6 -4
View File
@@ -104,10 +104,10 @@ contract AEProof =
proofsByOwner : map(address, array(uint)) } proofsByOwner : map(address, array(uint)) }
function notarize(document:string, comment:string, ipfsHash:hash) = function notarize(document:string, comment:string, ipfsHash:hash) =
let _ = require(aetoken.balanceOf(caller()) > 0, "false") let _ = require(aetoken.balanceOf(caller()) > 0)
let proofHash: uint = calculateHash(document) let proofHash: uint = calculateHash(document)
let proof : proof = Map.get_(proofHash, state().proofs) let proof : proof = Map.get_(proofHash, state().proofs)
let _ = require(proof.owner == #0, "false") let _ = require(proof.owner == #0)
let proof' : proof = proof { owner = caller() let proof' : proof = proof { owner = caller()
, timestamp = block().timestamp , timestamp = block().timestamp
, proofBlock = block().height , proofBlock = block().height
@@ -124,12 +124,12 @@ contract AEProof =
function getProof(document) : proof = function getProof(document) : proof =
let calcHash = calculateHash(document) let calcHash = calculateHash(document)
let proof = Map.get_(calcHash, state().proofs) let proof = Map.get_(calcHash, state().proofs)
let _ = require(proof.owner != #0, "false") let _ = require(proof.owner != #0)
proof proof
function getProofByHash(hash: uint) : proof = function getProofByHash(hash: uint) : proof =
let proof = Map.get_(hash, state().proofs) let proof = Map.get_(hash, state().proofs)
let _ = require(proof.owner != #0, "false") let _ = require(proof.owner != #0)
proof proof
@@ -141,3 +141,5 @@ contract AEProof =
function getProofsByOwner(owner: address): array(uint) = function getProofsByOwner(owner: address): array(uint) =
Map.get(owner, state()) Map.get(owner, state())
function require(x : bool) : unit = if(x) () else abort("false")
+8 -1
View File
@@ -24,7 +24,7 @@ contract AllSyntax =
if(valWithType(Map.empty) == None) if(valWithType(Map.empty) == None)
print(42 mod 10 * 5 / 3) print(42 mod 10 * 5 / 3)
function funWithType(x : int, y) : int * list(int) = (x, 0 :: [y] ++ []) function funWithType(x : int, y) : (int, list(int)) = (x, 0 :: [y] ++ [])
function funNoType() = function funNoType() =
let foo = (x, y : bool) => let foo = (x, y : bool) =>
if (! (y && x =< 0x0b || true)) [x] if (! (y && x =< 0x0b || true)) [x]
@@ -36,6 +36,13 @@ 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 bitOperations(x, y) = bnot (0xff00 band x bsl 4 bxor 0xa5a5a5 bsr 4 bor y)
function mutual() =
let rec recFun(x : int) = mutFun(x)
and mutFun(x) = if(x =< 0) 1 else x * recFun(x - 1)
recFun(0)
let hash : address = #01ab0fff11 let hash : address = #01ab0fff11
let b = false let b = false
let qcon = Mod.Con let qcon = Mod.Con
-33
View File
@@ -1,33 +0,0 @@
contract Remote =
entrypoint foo : () => unit
contract AddressLiterals =
entrypoint addr1() : bytes(32) =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint addr2() : Remote =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint addr3() : oracle(int, bool) =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint oracle1() : oracle_query(int, bool) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint oracle2() : bytes(32) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint oracle3() : Remote =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint query1() : oracle(int, bool) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint query2() : bytes(32) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint query3() : Remote =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint contr1() : address =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr2() : oracle(int, bool) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr3() : bytes(32) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
-23
View File
@@ -1,23 +0,0 @@
contract Events =
type alias_int = int
type alias_address = address
type alias_string = string
datatype event =
Event1(indexed alias_int, indexed int, string)
| Event2(alias_string, indexed alias_address)
| BadEvent1(indexed string)
| BadEvent2(indexed alias_string)
entrypoint f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y))
entrypoint f2(s : string) =
Chain.event(Event2(s, Call.caller))
entrypoint f3(x : int) =
Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
entrypoint i2s(i : int) = Int.to_str(i)
entrypoint a2s(a : address) = Address.to_str(a)
-23
View File
@@ -1,23 +0,0 @@
contract Events =
type alias_int = int
type alias_address = address
type alias_string = string
datatype event =
Event1(indexed alias_int, indexed int, string)
| Event2(alias_string, indexed alias_address)
| BadEvent1(string, string)
| BadEvent2(indexed int, indexed int, indexed int, indexed address)
entrypoint f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y))
entrypoint f2(s : string) =
Chain.event(Event2(s, Call.caller))
entrypoint f3(x : int) =
Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
entrypoint i2s(i : int) = Int.to_str(i)
entrypoint a2s(a : address) = Address.to_str(a)
-6
View File
@@ -1,6 +0,0 @@
contract Bad =
include "included.aes"
namespace Foo =
function foo() = 42
entrypoint foo() = 43
-13
View File
@@ -1,13 +0,0 @@
contract BadInit =
type state = int
entrypoint new_state(n) = state + n
stateful entrypoint roundabout(n) = put(n)
stateful entrypoint set_state(n) = roundabout(n)
stateful entrypoint init() =
set_state(4)
new_state(0)
state + state
-17
View File
@@ -1,17 +0,0 @@
// Contract replicating "normal" Aeternity authentication
contract BasicAuth =
record state = { nonce : int, owner : address }
entrypoint init() = { nonce = 1, owner = Call.caller }
stateful entrypoint 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)
entrypoint to_sign(h : hash, n : int) =
Crypto.blake2b((h, n))
-16
View File
@@ -1,16 +0,0 @@
contract BitcoinAuth =
record state = { nonce : int, owner : bytes(64) }
entrypoint init(owner' : bytes(64)) = { nonce = 1, owner = owner' }
stateful entrypoint 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)
entrypoint to_sign(h : hash, n : int) : hash =
Crypto.blake2b((h, n))
+2 -2
View File
@@ -5,8 +5,8 @@ contract BuiltinBug =
record state = {proofs : map(address, list(string))} record state = {proofs : map(address, list(string))}
entrypoint init() = {proofs = {}} public function init() = {proofs = {}}
stateful entrypoint createProof(hash : string) = public stateful function createProof(hash : string) =
put( state{ proofs[Call.caller] = hash :: state.proofs[Call.caller] } ) put( state{ proofs[Call.caller] = hash :: state.proofs[Call.caller] } )
+7 -3
View File
@@ -1,8 +1,12 @@
contract TestContract = contract TestContract =
record state = {_allowed : map(address, map(address, int))} record state = {
_allowed : map(address, map(address, int))}
entrypoint init() = {_allowed = {}} public stateful function init() = {
_allowed = {}}
public stateful function approve(spender: address, value: int) : bool =
stateful entrypoint approve(spender: address, value: int) : bool =
put(state{_allowed[Call.caller][spender] = value}) put(state{_allowed[Call.caller][spender] = value})
true true
-18
View File
@@ -1,18 +0,0 @@
contract BytesEquality =
entrypoint eq16(a : bytes(16), b) = a == b
entrypoint ne16(a : bytes(16), b) = a != b
entrypoint eq32(a : bytes(32), b) = a == b
entrypoint ne32(a : bytes(32), b) = a != b
entrypoint eq47(a : bytes(47), b) = a == b
entrypoint ne47(a : bytes(47), b) = a != b
entrypoint eq64(a : bytes(64), b) = a == b
entrypoint ne64(a : bytes(64), b) = a != b
entrypoint eq65(a : bytes(65), b) = a == b
entrypoint ne65(a : bytes(65), b) = a != b
-8
View File
@@ -1,8 +0,0 @@
contract BytesToX =
entrypoint to_int(b : bytes(42)) : int = Bytes.to_int(b)
entrypoint to_str(b : bytes(12)) : string =
String.concat(Bytes.to_str(b), Bytes.to_str(#ffff))
entrypoint to_str_big(b : bytes(65)) : string =
Bytes.to_str(b)
+1 -1
View File
@@ -1,6 +1,6 @@
// Test more advanced chain interactions // Test more advanced chain interactions
contract ChainTest = contract Chain =
record state = { last_bf : address } record state = { last_bf : address }
+25 -25
View File
@@ -1,78 +1,78 @@
contract Remote = contract Remote =
entrypoint up_to : (int) => list(int) function up_to : (int) => list(int)
entrypoint sum : (list(int)) => int function sum : (list(int)) => int
entrypoint some_string : () => string function some_string : () => string
entrypoint pair : (int, string) => int * string function pair : (int, string) => (int, string)
entrypoint squares : (int) => list(int * int) function squares : (int) => list((int, int))
entrypoint filter_some : (list(option(int))) => list(int) function filter_some : (list(option(int))) => list(int)
entrypoint all_some : (list(option(int))) => option(list(int)) function all_some : (list(option(int))) => option(list(int))
contract ComplexTypes = contract ComplexTypes =
record state = { worker : Remote } record state = { worker : Remote }
entrypoint init(worker) = {worker = worker} function init(worker) = {worker = worker}
entrypoint sum_acc(xs, n) = function sum_acc(xs, n) =
switch(xs) switch(xs)
[] => n [] => n
x :: xs => sum_acc(xs, x + n) x :: xs => sum_acc(xs, x + n)
// Sum a list of integers // Sum a list of integers
entrypoint sum(xs : list(int)) = function sum(xs : list(int)) =
sum_acc(xs, 0) sum_acc(xs, 0)
entrypoint up_to_acc(n, xs) = function up_to_acc(n, xs) =
switch(n) switch(n)
0 => xs 0 => xs
_ => up_to_acc(n - 1, n :: xs) _ => up_to_acc(n - 1, n :: xs)
entrypoint up_to(n) = up_to_acc(n, []) function up_to(n) = up_to_acc(n, [])
record answer('a) = {label : string, result : 'a} record answer('a) = {label : string, result : 'a}
entrypoint remote_triangle(worker, n) : answer(int) = function remote_triangle(worker, n) : answer(int) =
let xs = worker.up_to(gas = 100000, n) let xs = worker.up_to(gas = 100000, n)
let t = worker.sum(xs) let t = worker.sum(xs)
{ label = "answer:", result = t } { label = "answer:", result = t }
entrypoint remote_list(n) : list(int) = function remote_list(n) : list(int) =
state.worker.up_to(n) state.worker.up_to(n)
entrypoint some_string() = "string" function some_string() = "string"
entrypoint remote_string() : string = function remote_string() : string =
state.worker.some_string() state.worker.some_string()
entrypoint pair(x : int, y : string) = (x, y) function pair(x : int, y : string) = (x, y)
entrypoint remote_pair(n : int, s : string) : int * string = function remote_pair(n : int, s : string) : (int, string) =
state.worker.pair(gas = 10000, n, s) state.worker.pair(gas = 10000, n, s)
entrypoint map(f, xs) = function map(f, xs) =
switch(xs) switch(xs)
[] => [] [] => []
x :: xs => f(x) :: map(f, xs) x :: xs => f(x) :: map(f, xs)
entrypoint squares(n) = function squares(n) =
map((i) => (i, i * i), up_to(n)) map((i) => (i, i * i), up_to(n))
entrypoint remote_squares(n) : list(int * int) = function remote_squares(n) : list((int, int)) =
state.worker.squares(n) state.worker.squares(n)
// option types // option types
entrypoint filter_some(xs : list(option(int))) : list(int) = function filter_some(xs : list(option(int))) : list(int) =
switch(xs) switch(xs)
[] => [] [] => []
None :: ys => filter_some(ys) None :: ys => filter_some(ys)
Some(x) :: ys => x :: filter_some(ys) Some(x) :: ys => x :: filter_some(ys)
entrypoint remote_filter_some(xs : list(option(int))) : list(int) = function remote_filter_some(xs : list(option(int))) : list(int) =
state.worker.filter_some(xs) state.worker.filter_some(xs)
entrypoint all_some(xs : list(option(int))) : option(list(int)) = function all_some(xs : list(option(int))) : option(list(int)) =
switch(xs) switch(xs)
[] => Some([]) [] => Some([])
None :: ys => None None :: ys => None
@@ -81,6 +81,6 @@ contract ComplexTypes =
Some(xs) => Some(x :: xs) Some(xs) => Some(x :: xs)
None => None None => None
entrypoint remote_all_some(xs : list(option(int))) : option(list(int)) = function remote_all_some(xs : list(option(int))) : option(list(int)) =
state.worker.all_some(gas = 10000, xs) state.worker.all_some(gas = 10000, xs)
+3 -3
View File
@@ -3,7 +3,7 @@ contract Counter =
record state = { value : int } record state = { value : int }
entrypoint init(val) = { value = val } function init(val) = { value = val }
entrypoint get() = state.value function get() = state.value
stateful entrypoint tick() = put(state{ value = state.value + 1 }) function tick() = put(state{ value = state.value + 1 })
-21
View File
@@ -1,21 +0,0 @@
namespace List =
function map1(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map1(f, xs)
function map2(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map2(f, xs)
contract Deadcode =
entrypoint inc1(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs)
entrypoint inc2(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs)
+6 -3
View File
@@ -10,13 +10,16 @@ contract DutchAuction =
sold : bool } sold : bool }
// Add to work around current lack of predefined functions // Add to work around current lack of predefined functions
stateful function spend(to, amount) = private function spend(to, amount) =
let total = Contract.balance let total = Contract.balance
Chain.spend(to, amount) Chain.spend(to, amount)
total - amount total - amount
private function require(b : bool, err : string) =
if(!b) abort(err)
// TTL set by user on posting contract, typically (start - end ) div dec // TTL set by user on posting contract, typically (start - end ) div dec
entrypoint init(beneficiary, start, decrease) : state = public function init(beneficiary, start, decrease) : state =
require(start > 0 && decrease > 0, "bad args") require(start > 0 && decrease > 0, "bad args")
{ start_amount = start, { start_amount = start,
start_height = Chain.block_height, start_height = Chain.block_height,
@@ -27,7 +30,7 @@ contract DutchAuction =
// -- API // -- API
// We are the buyer... interesting case to buy for someone else and keep 10% // We are the buyer... interesting case to buy for someone else and keep 10%
stateful entrypoint bid() = public stateful function bid() =
require( !(state.sold), "sold") require( !(state.sold), "sold")
let cost = let cost =
state.start_amount - (Chain.block_height - state.start_height) * state.dec state.start_amount - (Chain.block_height - state.start_height) * state.dec
+23 -23
View File
@@ -1,69 +1,69 @@
// Testing primitives for accessing the block chain environment // Testing primitives for accessing the block chain environment
contract Interface = contract Interface =
entrypoint contract_address : () => address function contract_address : () => address
entrypoint call_origin : () => address function call_origin : () => address
entrypoint call_caller : () => address function call_caller : () => address
entrypoint call_value : () => int function call_value : () => int
contract Environment = contract Environment =
record state = {remote : Interface} record state = {remote : Interface}
entrypoint init(remote) = {remote = remote} function init(remote) = {remote = remote}
stateful entrypoint set_remote(remote) = put({remote = remote}) function set_remote(remote) = put({remote = remote})
// -- Information about the this contract --- // -- Information about the this contract ---
// Address // Address
entrypoint contract_address() : address = Contract.address function contract_address() : address = Contract.address
entrypoint nested_address(who) : address = function nested_address(who) : address =
who.contract_address(gas = 1000) who.contract_address(gas = 1000)
// Balance // Balance
entrypoint contract_balance() : int = Contract.balance function contract_balance() : int = Contract.balance
// -- Information about the current call --- // -- Information about the current call ---
// Origin // Origin
entrypoint call_origin() : address = Call.origin function call_origin() : address = Call.origin
entrypoint nested_origin() : address = function nested_origin() : address =
state.remote.call_origin() state.remote.call_origin()
// Caller // Caller
entrypoint call_caller() : address = Call.caller function call_caller() : address = Call.caller
entrypoint nested_caller() : address = function nested_caller() : address =
state.remote.call_caller() state.remote.call_caller()
// Value // Value
entrypoint call_value() : int = Call.value function call_value() : int = Call.value
stateful entrypoint nested_value(value : int) : int = function nested_value(value : int) : int =
state.remote.call_value(value = value / 2) state.remote.call_value(value = value / 2)
// Gas price // Gas price
entrypoint call_gas_price() : int = Call.gas_price function call_gas_price() : int = Call.gas_price
// -- Information about the chain --- // -- Information about the chain ---
// Account balances // Account balances
entrypoint get_balance(acct : address) : int = Chain.balance(acct) function get_balance(acct : address) : int = Chain.balance(acct)
// Block hash // Block hash
entrypoint block_hash(height : int) : option(hash) = Chain.block_hash(height) function block_hash(height : int) : int = Chain.block_hash(height)
// Coinbase // Coinbase
entrypoint coinbase() : address = Chain.coinbase function coinbase() : address = Chain.coinbase
// Block timestamp // Block timestamp
entrypoint timestamp() : int = Chain.timestamp function timestamp() : int = Chain.timestamp
// Block height // Block height
entrypoint block_height() : int = Chain.block_height function block_height() : int = Chain.block_height
// Difficulty // Difficulty
entrypoint difficulty() : int = Chain.difficulty function difficulty() : int = Chain.difficulty
// Gas limit // Gas limit
entrypoint gas_limit() : int = Chain.gas_limit function gas_limit() : int = Chain.gas_limit
+3
View File
@@ -77,6 +77,9 @@ contract ERC20Token =
put( state{approval_log = e :: state.approval_log }) put( state{approval_log = e :: state.approval_log })
e e
private function require(b : bool, err : string) =
if(!b) abort(err)
private function sub(_a : int, _b : int) : int = private function sub(_a : int, _b : int) : int =
require(_b =< _a, "Error") require(_b =< _a, "Error")
_a - _b _a - _b
+16 -34
View File
@@ -1,40 +1,22 @@
contract Remote =
entrypoint dummy : () => unit
contract Events = contract Events =
type alias_int = int
type alias_address = address
type alias_string = string
// Valid index types datatype event =
type ix1 = int Event1(indexed alias_int, indexed int, string)
type ix2 = bool | Event2(alias_string, indexed alias_address)
type ix3 = bits // | BadEvent1(indexed string, string)
type ix4 = bytes(12) // | BadEvent2(indexed int, int)
type ix5 = hash // bytes(32)
type ix6 = address
type ix7 = Remote
type ix8 = oracle(int, int)
type ix9 = oracle_query(int, int)
// Valid payload types function f1(x : int, y : string) =
type data1 = string Chain.event(Event1(x, x+1, y))
type data2 = signature // bytes(64)
type data3 = bytes(65)
datatype event function f2(s : string) =
= Nodata0 Chain.event(Event2(s, Call.caller))
| Nodata1(ix1)
| Nodata2(ix2, ix3)
| Nodata3(ix4, ix5, ix6)
| Data0(data1)
| Data1(data2, ix7)
| Data2(ix8, data3, ix9)
| Data3(ix1, ix2, ix5, data1)
entrypoint nodata0() = Chain.event(Nodata0) function f3(x : int) =
entrypoint nodata1(ix1) = Chain.event(Nodata1(ix1)) Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
entrypoint nodata2(ix2, ix3) = Chain.event(Nodata2(ix2, ix3))
entrypoint nodata3(ix4, ix5, ix6) = Chain.event(Nodata3(ix4, ix5, ix6))
entrypoint data0(data1) = Chain.event(Data0(data1))
entrypoint data1(data2, ix7) = Chain.event(Data1(data2, ix7))
entrypoint data2(ix8, data3, ix9) = Chain.event(Data2(ix8, data3, ix9))
entrypoint data3(ix1, ix2, ix5, data1) = Chain.event(Data3(ix1, ix2, ix5, data1))
function i2s(i : int) = Int.to_str(i)
function a2s(a : address) = Address.to_str(a)
+4 -4
View File
@@ -1,17 +1,17 @@
// An implementation of the factorial function where each recursive // An implementation of the factorial function where each recursive
// call is to another contract. Not the cheapest way to compute factorial. // call is to another contract. Not the cheapest way to compute factorial.
contract FactorialServer = contract FactorialServer =
entrypoint fac : (int) => int function fac : (int) => int
contract Factorial = contract Factorial =
record state = {worker : FactorialServer} record state = {worker : FactorialServer}
entrypoint init(worker) = {worker = worker} function init(worker) = {worker = worker}
stateful entrypoint set_worker(worker) = put(state{worker = worker}) function set_worker(worker) = put(state{worker = worker})
entrypoint fac(x : int) : int = function fac(x : int) : int =
if(x == 0) 1 if(x == 0) 1
else x * state.worker.fac(x - 1) else x * state.worker.fac(x - 1)
-5
View File
@@ -1,5 +0,0 @@
contract Fail =
record pt = {x : int, y : int}
record r = {p : pt}
function fail() = {p.x = 0, p.y = 0}
-47
View File
@@ -1,47 +0,0 @@
contract FunctionArguments =
entrypoint sum(n : int, m: int) =
n + m
entrypoint append(xs : list(string)) =
switch(xs)
[] => ""
y :: ys => String.concat(y, append(ys))
entrypoint menot(b) =
!b
entrypoint bitsum(b : bits) =
Bits.sum(b)
record answer('a) = {label : string, result : 'a}
entrypoint read(a : answer(int)) =
a.result
entrypoint sjutton(b : bytes(17)) =
b
entrypoint sextiosju(b : bytes(67)) =
b
entrypoint trettiotva(b : bytes(32)) =
b
entrypoint find_oracle(o : oracle(int, bool)) =
true
entrypoint find_query(q : oracle_query(int, bool)) =
true
datatype colour() = Green | Yellow | Red | Pantone(int)
entrypoint traffic_light(c : colour) =
Red
entrypoint tuples(t : unit) =
t
entrypoint due(t : Chain.ttl) =
true
-15
View File
@@ -1,15 +0,0 @@
contract Functions =
function curry(f : ('a, 'b) => 'c) =
(x) => (y) => f(x, y)
function map(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map(f, xs)
function map'() = map
function plus(x, y) = x + y
entrypoint test1(xs : list(int)) = map(curry(plus)(5), xs)
entrypoint test2(xs : list(int)) = map'()(((x) => (y) => ((x, y) => x + y)(x, y))(100), xs)
entrypoint test3(xs : list(int)) =
let m(f, xs) = map(f, xs)
m((x) => x + 1, xs)
+11 -8
View File
@@ -12,20 +12,23 @@ contract FundMe =
deadline : int, deadline : int,
goal : int } goal : int }
stateful function spend(args : spend_args) = private function require(b : bool, err : string) =
if(!b) abort(err)
private function spend(args : spend_args) =
Chain.spend(args.recipient, args.amount) Chain.spend(args.recipient, args.amount)
entrypoint init(beneficiary, deadline, goal) : state = public function init(beneficiary, deadline, goal) : state =
{ contributions = {}, { contributions = {},
beneficiary = beneficiary, beneficiary = beneficiary,
deadline = deadline, deadline = deadline,
total = 0, total = 0,
goal = goal } goal = goal }
function is_contributor(addr) = private function is_contributor(addr) =
Map.member(addr, state.contributions) Map.member(addr, state.contributions)
stateful entrypoint contribute() = public stateful function contribute() =
if(Chain.block_height >= state.deadline) if(Chain.block_height >= state.deadline)
spend({ recipient = Call.caller, amount = Call.value }) // Refund money spend({ recipient = Call.caller, amount = Call.value }) // Refund money
false false
@@ -36,7 +39,7 @@ contract FundMe =
total @ tot = tot + Call.value }) total @ tot = tot + Call.value })
true true
stateful entrypoint withdraw() = public stateful function withdraw() =
if(Chain.block_height < state.deadline) if(Chain.block_height < state.deadline)
abort("Cannot withdraw before deadline") abort("Cannot withdraw before deadline")
if(Call.caller == state.beneficiary) if(Call.caller == state.beneficiary)
@@ -46,13 +49,13 @@ contract FundMe =
else else
abort("Not a contributor or beneficiary") abort("Not a contributor or beneficiary")
stateful function withdraw_beneficiary() = private stateful function withdraw_beneficiary() =
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 = ak_11111111111111111111111111111111273Yts }) put(state{ beneficiary = #0 })
stateful function withdraw_contributor() = private stateful function withdraw_contributor() =
if(state.total >= state.goal) if(state.total >= state.goal)
abort("Project was funded") abort("Project was funded")
let to = Call.caller let to = Call.caller
+1 -1
View File
@@ -1,3 +1,3 @@
contract Identity = contract Identity =
entrypoint main (x:int) = x function main (x:int) = x
-9
View File
@@ -1,9 +0,0 @@
include "included.aes"
include "../contracts/included2.aes"
contract Include =
entrypoint foo() =
Included.foo() < Included2a.bar()
entrypoint bar() =
Included2b.foo() > Included.foo()
-2
View File
@@ -1,2 +0,0 @@
namespace Included =
function foo() = 42
-5
View File
@@ -1,5 +0,0 @@
namespace Included2a =
function bar() = 43
namespace Included2b =
function foo() = 44
+2 -2
View File
@@ -3,6 +3,6 @@ contract InitTypeError =
type state = map(int, int) type state = map(int, int)
// Check that the compiler catches ill-typed init entrypoint // Check that the compiler catches ill-typed init function
entrypoint init() = "not the right type!" function init() = "not the right type!"
+56 -56
View File
@@ -4,97 +4,97 @@ contract Maps =
record state = { map_i : map(int, pt), record state = { map_i : map(int, pt),
map_s : map(string, pt) } map_s : map(string, pt) }
entrypoint init() = { map_i = {}, map_s = {} } function init() = { map_i = {}, map_s = {} }
entrypoint get_state() = state function get_state() = state
// {[k] = v} // {[k] = v}
entrypoint map_i() = function map_i() =
{ [1] = {x = 1, y = 2}, { [1] = {x = 1, y = 2},
[2] = {x = 3, y = 4}, [2] = {x = 3, y = 4},
[3] = {x = 5, y = 6} } [3] = {x = 5, y = 6} }
entrypoint map_s() = function map_s() =
{ ["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} }
stateful entrypoint map_state_i() = put(state{ map_i = map_i() }) function map_state_i() = put(state{ map_i = map_i() })
stateful entrypoint map_state_s() = put(state{ map_s = map_s() }) function map_state_s() = put(state{ map_s = map_s() })
// m[k] // m[k]
entrypoint get_i(k, m : map(int, pt)) = m[k] function get_i(k, m : map(int, pt)) = m[k]
entrypoint get_s(k, m : map(string, pt)) = m[k] function get_s(k, m : map(string, pt)) = m[k]
entrypoint get_state_i(k) = get_i(k, state.map_i) function get_state_i(k) = get_i(k, state.map_i)
entrypoint get_state_s(k) = get_s(k, state.map_s) function get_state_s(k) = get_s(k, state.map_s)
// m[k = v] // m[k = v]
entrypoint get_def_i(k, v, m : map(int, pt)) = m[k = v] function get_def_i(k, v, m : map(int, pt)) = m[k = v]
entrypoint get_def_s(k, v, m : map(string, pt)) = m[k = v] function get_def_s(k, v, m : map(string, pt)) = m[k = v]
entrypoint get_def_state_i(k, v) = get_def_i(k, v, state.map_i) function get_def_state_i(k, v) = get_def_i(k, v, state.map_i)
entrypoint get_def_state_s(k, v) = get_def_s(k, v, state.map_s) function get_def_state_s(k, v) = get_def_s(k, v, state.map_s)
// m{[k] = v} // m{[k] = v}
entrypoint set_i(k, p, m : map(int, pt)) = m{ [k] = p } function set_i(k, p, m : map(int, pt)) = m{ [k] = p }
entrypoint set_s(k, p, m : map(string, pt)) = m{ [k] = p } function set_s(k, p, m : map(string, pt)) = m{ [k] = p }
stateful entrypoint set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) }) function set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) })
stateful entrypoint set_state_s(k, p) = put(state{ map_s = set_s(k, p, state.map_s) }) 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}
entrypoint 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 }
entrypoint 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 }
stateful entrypoint setx_state_i(k, x) = put(state{ map_i[k].x = x }) function setx_state_i(k, x) = put(state{ map_i[k].x = x })
stateful entrypoint setx_state_s(k, x) = put(state{ map_s[k].x = x }) function setx_state_s(k, x) = put(state{ map_s[k].x = x })
// m{[k] @ x = v } // m{[k] @ x = v }
entrypoint 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 }
entrypoint 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 }
stateful entrypoint addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d }) function addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d })
stateful entrypoint addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d }) function addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d })
// m{[k = def] @ x = v } // m{[k = def] @ x = v }
entrypoint 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 }
entrypoint addx_def_s(k, v, d, m : map(string, pt)) = m{ [k = v].x @ x = x + d } function addx_def_s(k, v, d, m : map(string, pt)) = m{ [k = v].x @ x = x + d }
// Map.member // Map.member
entrypoint member_i(k, m : map(int, pt)) = Map.member(k, m) function member_i(k, m : map(int, pt)) = Map.member(k, m)
entrypoint member_s(k, m : map(string, pt)) = Map.member(k, m) function member_s(k, m : map(string, pt)) = Map.member(k, m)
entrypoint member_state_i(k) = member_i(k, state.map_i) function member_state_i(k) = member_i(k, state.map_i)
entrypoint member_state_s(k) = member_s(k, state.map_s) function member_state_s(k) = member_s(k, state.map_s)
// Map.lookup // Map.lookup
entrypoint lookup_i(k, m : map(int, pt)) = Map.lookup(k, m) function lookup_i(k, m : map(int, pt)) = Map.lookup(k, m)
entrypoint lookup_s(k, m : map(string, pt)) = Map.lookup(k, m) function lookup_s(k, m : map(string, pt)) = Map.lookup(k, m)
entrypoint lookup_state_i(k) = lookup_i(k, state.map_i) function lookup_state_i(k) = lookup_i(k, state.map_i)
entrypoint lookup_state_s(k) = lookup_s(k, state.map_s) function lookup_state_s(k) = lookup_s(k, state.map_s)
// Map.lookup_default // Map.lookup_default
entrypoint lookup_def_i(k, m : map(int, pt), def : pt) = function lookup_def_i(k, m : map(int, pt), def : pt) =
Map.lookup_default(k, m, def) Map.lookup_default(k, m, def)
entrypoint lookup_def_s(k, m : map(string, pt), def : pt) = function lookup_def_s(k, m : map(string, pt), def : pt) =
Map.lookup_default(k, m, def) Map.lookup_default(k, m, def)
entrypoint lookup_def_state_i(k, def) = lookup_def_i(k, state.map_i, def) function lookup_def_state_i(k, def) = lookup_def_i(k, state.map_i, def)
entrypoint lookup_def_state_s(k, def) = lookup_def_s(k, state.map_s, def) function lookup_def_state_s(k, def) = lookup_def_s(k, state.map_s, def)
// Map.delete // Map.delete
entrypoint delete_i(k, m : map(int, pt)) = Map.delete(k, m) function delete_i(k, m : map(int, pt)) = Map.delete(k, m)
entrypoint delete_s(k, m : map(string, pt)) = Map.delete(k, m) function delete_s(k, m : map(string, pt)) = Map.delete(k, m)
stateful entrypoint delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) }) function delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) })
stateful entrypoint delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) }) function delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) })
// Map.size // Map.size
entrypoint size_i(m : map(int, pt)) = Map.size(m) function size_i(m : map(int, pt)) = Map.size(m)
entrypoint size_s(m : map(string, pt)) = Map.size(m) function size_s(m : map(string, pt)) = Map.size(m)
entrypoint size_state_i() = size_i(state.map_i) function size_state_i() = size_i(state.map_i)
entrypoint size_state_s() = size_s(state.map_s) function size_state_s() = size_s(state.map_s)
// Map.to_list // Map.to_list
entrypoint tolist_i(m : map(int, pt)) = Map.to_list(m) function tolist_i(m : map(int, pt)) = Map.to_list(m)
entrypoint tolist_s(m : map(string, pt)) = Map.to_list(m) function tolist_s(m : map(string, pt)) = Map.to_list(m)
entrypoint tolist_state_i() = tolist_i(state.map_i) function tolist_state_i() = tolist_i(state.map_i)
entrypoint tolist_state_s() = tolist_s(state.map_s) function tolist_state_s() = tolist_s(state.map_s)
// Map.from_list // Map.from_list
entrypoint fromlist_i(xs : list(int * pt)) = Map.from_list(xs) function fromlist_i(xs : list((int, pt))) = Map.from_list(xs)
entrypoint fromlist_s(xs : list(string * pt)) = Map.from_list(xs) function fromlist_s(xs : list((string, pt))) = Map.from_list(xs)
stateful entrypoint fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) }) function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) })
stateful entrypoint fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) }) function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) })
@@ -3,6 +3,6 @@ contract MissingFieldsInRecordExpr =
record r('a) = {x : int, y : string, z : 'a} record r('a) = {x : int, y : string, z : 'a}
type alias('a) = r('a) type alias('a) = r('a)
entrypoint fail1() = { x = 0 } function fail1() = { x = 0 }
entrypoint fail2(z : 'a) : r('a) = { y = "string", z = z } function fail2(z : 'a) : r('a) = { y = "string", z = z }
entrypoint fail3() : alias(int) = { x = 0, z = 1 } function fail3() : alias(int) = { x = 0, z = 1 }
+1 -1
View File
@@ -2,5 +2,5 @@
contract MissingStateType = contract MissingStateType =
// Check that we get a type error also for implicit state // Check that we get a type error also for implicit state
entrypoint init() = "should be ()" function init() = "should be ()"
-12
View File
@@ -1,12 +0,0 @@
namespace Lib =
entrypoint foo() = ()
contract Remote =
public function foo : () => unit
function bla() = ()
contract Contract =
public function foo() = ()
public private stateful function all_the_things() = ()
private entrypoint wha() = ()
+15 -13
View File
@@ -5,7 +5,7 @@
contract MultiSig = contract MultiSig =
record pending_state = { yetNeeded : int, ownersDone : bits, index : int } record pending_state = { yetNeeded : uint, ownersDone : uint, index : uint }
datatype event = datatype event =
Confirmation (address, hash) // of { .owner : Address, .operation : Hash } Confirmation (address, hash) // of { .owner : Address, .operation : Hash }
@@ -13,18 +13,18 @@ contract MultiSig =
| OwnerChanged (address, address) // of { .oldOwner : Address, .newOwner : Address } | OwnerChanged (address, address) // of { .oldOwner : Address, .newOwner : Address }
| OwnerAdded (address) // of { .newOwner : Address } | OwnerAdded (address) // of { .newOwner : Address }
| OwnerRemoved (address) // of { .removedOwner : Address } | OwnerRemoved (address) // of { .removedOwner : Address }
| ReqChanged (int) // of { .newReq : int } | ReqChanged (uint) // of { .newReq : uint }
let maxOwners : int = 250 let maxOwners : uint = 250
record state = { nRequired : int record state = { nRequired : uint
, nOwners : int , nOwners : uint
, owners : map(int, address) , owners : map(uint, address)
, ownerIndex : map(address, int) , ownerIndex : map(address, uint)
, pending : map(hash, pending_state) , pending : map(hash, pending_state)
, pendingIndex : list(address) } , pendingIndex : list(address) }
function init (owners : list(address), nRequired : int) : state = function init (owners : list(address), nRequired : uint) : state =
let n = length(owners) + 1 let n = length(owners) + 1
{ nRequired = nRequired, { nRequired = nRequired,
nOwners = n, nOwners = n,
@@ -39,9 +39,10 @@ contract MultiSig =
function revoke(operation : hash) = function revoke(operation : hash) =
let ownerIx = lookup(state.ownerIndex, caller()) let ownerIx = lookup(state.ownerIndex, caller())
let pending = lookup(state.pendingIndex, operation) let pending = lookup(state.pendingIndex, operation)
let _ = require(Bits.test(pending.ownersDone, ownerIx)) let ownerIxBit = 1 bsl (ownerIx - 1)
let _ = require(pending.ownersDone band ownerIxBit > 0)
let pending' = pending { yetNeeded = pending.yetNeeded + 1 let pending' = pending { yetNeeded = pending.yetNeeded + 1
, ownersDone = Bits.clear(pending.ownersDone, ownerIx - 1) } , ownersDone = pending.ownersDone - ownerIxBit }
put(state{ pendingIndex.operator = pending' }) put(state{ pendingIndex.operator = pending' })
event(Revoke(caller, operation)) event(Revoke(caller, operation))
@@ -90,7 +91,7 @@ contract MultiSig =
, pendingIx = [] }, , pendingIx = [] },
event = [OwnerRemoved(oldOwner)] } event = [OwnerRemoved(oldOwner)] }
function changeRequirement(newReq : int) = function changeRequirement(newReq : uint) =
let _ = require(newReq =< state.nOwners) let _ = require(newReq =< state.nOwners)
switch(check_pending(callhash())) switch(check_pending(callhash()))
CheckFail(state') => { state = state' } CheckFail(state') => { state = state' }
@@ -101,7 +102,7 @@ contract MultiSig =
event = [ReqChanged(newReq)] } event = [ReqChanged(newReq)] }
function getOwner(ownerIx0 : int) = function getOwner(ownerIx0 : uint) =
lookup(state.owners, ownerIx0 + 1) lookup(state.owners, ownerIx0 + 1)
function isOwner(owner : address) = function isOwner(owner : address) =
@@ -115,7 +116,8 @@ contract MultiSig =
Some(pending) => Some(pending) =>
let _ = require(isOwner(owner)) let _ = require(isOwner(owner))
let ownerIx = lookup(state.ownerIndex, owner) let ownerIx = lookup(state.ownerIndex, owner)
Bits.test(pending.ownersDone, ownerIx - 1) let ownerIxBit = 1 bsl (ownerIx - 1)
(pending.ownersDone band ownerIxBit) != 0
/* Leave the rest for now... */ /* Leave the rest for now... */
+10 -11
View File
@@ -1,17 +1,16 @@
contract NameClash = contract NameClash =
entrypoint double_proto : () => int function double_proto : () => int
entrypoint double_proto : () => int function double_proto : () => int
entrypoint proto_and_def : int => int function proto_and_def : int => int
entrypoint proto_and_def(n) = n + 1 function proto_and_def(n) = n + 1
entrypoint double_def(x) = x function double_def(x) = x
entrypoint double_def(y) = 0 function double_def(y) = 0
// abort, require, put and state are builtin // abort, put and state are builtin
entrypoint abort() : int = 0 function abort() : int = 0
entrypoint require(b, err) = if(b) abort(err) function put(x) = x
entrypoint put(x) = x function state(x, y) = x + y
entrypoint state(x, y) = x + y
-18
View File
@@ -1,18 +0,0 @@
namespace Foo =
record bla = {x : int, y : bool}
function bar() : Foo.bla = {x = 17, y = true}
contract Bug =
// Crashed the type checker
entrypoint foo() = Foo.bar()
// Also crashed the type checker
type t = Foo.bla
entrypoint test() =
let x : t = Foo.bar()
x
-5
View File
@@ -1,5 +0,0 @@
// You can't shadow existing contracts or namespaces.
contract Call =
entrypoint whatever() = ()
-21
View File
@@ -1,21 +0,0 @@
namespace Lib =
private function rev(xs, ys) =
switch(xs)
[] => ys
x :: xs => rev(xs, x :: ys)
function reverse(xs : list('a)) : list('a) = rev(xs, [])
function eqlist(xs : list(int), ys : list(int)) =
switch((xs, ys))
([], []) => true
(x :: xs, y :: ys) => x == y && eqlist(xs, ys)
_ => false
contract TestNamespaces =
function palindrome(xs : list(int)) : bool =
Lib.eqlist(xs, Lib.reverse(xs))
-21
View File
@@ -1,21 +0,0 @@
namespace List =
function map1(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map1(f, xs)
function map2(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map2(f, xs)
contract Deadcode =
entrypoint inc1(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs)
entrypoint inc2(xs : list(int)) : list(int) =
List.map2((x) => x + 1, xs)
+7
View File
@@ -1,4 +1,5 @@
// - + * / mod arithmetic operators // - + * / mod arithmetic operators
// bnot band bor bxor bsl bsr bitwise operators
// ! && || logical operators // ! && || logical operators
// == != < > =< >= comparison operators // == != < > =< >= comparison operators
// :: ++ list operators // :: ++ list operators
@@ -12,6 +13,12 @@ contract Operators =
"/" => a / b "/" => a / b
"mod" => a mod b "mod" => a mod b
"^" => a ^ b "^" => a ^ b
"bnot" => bnot a
"band" => a band b
"bor" => a bor b
"bxor" => a bxor b
"bsl" => a bsl b
"bsr" => a bsr b
function bool_op(a : bool, b : bool, op : string) = function bool_op(a : bool, b : bool, op : string) =
switch(op) switch(op)
+23 -21
View File
@@ -9,31 +9,31 @@ 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)
stateful entrypoint registerOracle(acct : address, 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)
stateful entrypoint registerIntIntOracle(acct : address, 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)
stateful entrypoint registerStringStringOracle(acct : address, 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)
stateful entrypoint signedRegisterOracle(acct : address, function signedRegisterOracle(acct : address,
sign : signature, sign : signature,
qfee : fee, qfee : fee,
ttl : ttl) : oracle_id = ttl : ttl) : oracle_id =
Oracle.register(acct, qfee, ttl, signature = sign) Oracle.register(acct, qfee, ttl, signature = sign)
entrypoint queryFee(o : oracle_id) : fee = function queryFee(o : oracle_id) : fee =
Oracle.query_fee(o) Oracle.query_fee(o)
stateful entrypoint createQuery(o : oracle_id, 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!
stateful entrypoint unsafeCreateQuery(o : oracle_id, 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!
stateful entrypoint unsafeCreateQueryThenErr(o : oracle_id, function unsafeCreateQueryThenErr(o : oracle_id,
q : query_t, q : query_t,
qfee : fee, qfee : fee,
qttl : ttl, qttl : ttl,
@@ -59,52 +59,54 @@ contract Oracles =
require(qfee >= 100000000000000000, "causing a late error") require(qfee >= 100000000000000000, "causing a late error")
res res
stateful entrypoint extendOracle(o : oracle_id, function extendOracle(o : oracle_id,
ttl : ttl) : unit = ttl : ttl) : () =
Oracle.extend(o, ttl) Oracle.extend(o, ttl)
stateful entrypoint signedExtendOracle(o : oracle_id, function signedExtendOracle(o : oracle_id,
sign : signature, // Signed oracle address sign : signature, // Signed oracle address
ttl : ttl) : unit = ttl : ttl) : () =
Oracle.extend(o, signature = sign, ttl) Oracle.extend(o, signature = sign, ttl)
stateful entrypoint respond(o : oracle_id, function respond(o : oracle_id,
q : query_id, q : query_id,
r : answer_t) : unit = r : answer_t) : () =
Oracle.respond(o, q, r) Oracle.respond(o, q, r)
stateful entrypoint signedRespond(o : oracle_id, function signedRespond(o : oracle_id,
q : query_id, q : query_id,
sign : signature, sign : signature,
r : answer_t) : unit = r : answer_t) : () =
Oracle.respond(o, q, signature = sign, r) Oracle.respond(o, q, signature = sign, r)
entrypoint getQuestion(o : oracle_id, function getQuestion(o : oracle_id,
q : query_id) : query_t = q : query_id) : query_t =
Oracle.get_question(o, q) Oracle.get_question(o, q)
entrypoint hasAnswer(o : oracle_id, function hasAnswer(o : oracle_id,
q : query_id) = q : query_id) =
switch(Oracle.get_answer(o, q)) switch(Oracle.get_answer(o, q))
None => false None => false
Some(_) => true Some(_) => true
entrypoint getAnswer(o : oracle_id, function getAnswer(o : oracle_id,
q : query_id) : option(answer_t) = q : query_id) : option(answer_t) =
Oracle.get_answer(o, q) Oracle.get_answer(o, q)
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)
stateful entrypoint complexOracle(question) = 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)
stateful entrypoint signedComplexOracle(question, sig) = 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)
Oracle.get_answer(o, q) Oracle.get_answer(o, q)
private function require(b : bool, err : string) =
if(!b) abort(err)
+2
View File
@@ -20,3 +20,5 @@ contract OraclesGas =
Oracle.respond(o, q, answer) Oracle.respond(o, q, answer)
() ()
private function require(b : bool, err : string) =
if(!b) abort(err)
+3 -1
View File
@@ -21,7 +21,7 @@ contract Oracles =
function respond(o : oracle_id, function respond(o : oracle_id,
q : query_id, q : query_id,
sign : signature, sign : signature,
r : answer_t) : unit = r : answer_t) : () =
Oracle.respond(o, q, signature = sign, r) Oracle.respond(o, q, signature = sign, r)
@@ -33,3 +33,5 @@ contract Oracles =
q : query_id) : option(answer_t) = q : query_id) : option(answer_t) =
Oracle.get_answer(o, q) Oracle.get_answer(o, q)
private function require(b : bool, err : string) =
if(!b) abort(err)
+9 -9
View File
@@ -1,27 +1,27 @@
contract Remote1 = contract Remote1 =
entrypoint main : (int) => int function main : (int) => int
contract Remote2 = contract Remote2 =
entrypoint call : (Remote1, int) => int function call : (Remote1, int) => int
contract Remote3 = contract Remote3 =
entrypoint get : () => int function get : () => int
entrypoint tick : () => unit function tick : () => ()
contract RemoteCall = contract RemoteCall =
stateful entrypoint call(r : Remote1, x : int) : int = function call(r : Remote1, x : int) : int =
r.main(gas = 10000, value = 10, x) r.main(gas = 10000, value = 10, x)
entrypoint staged_call(r1 : Remote1, r2 : Remote2, x : int) = function staged_call(r1 : Remote1, r2 : Remote2, x : int) =
r2.call(r1, x) r2.call(r1, x)
entrypoint increment(r3 : Remote3) = function increment(r3 : Remote3) =
r3.tick() r3.tick()
entrypoint get(r3 : Remote3) = function get(r3 : Remote3) =
r3.get() r3.get()
entrypoint plus(x, y) = x + y function plus(x, y) = x + y
+99
View File
@@ -0,0 +1,99 @@
contract Oracles =
function registerOracle :
(address,
int,
Chain.ttl) => oracle(string, int)
function createQuery :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
function unsafeCreateQuery :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
function respond :
(oracle(string, int),
oracle_query(string, int),
int) => ()
contract OraclesErr =
function unsafeCreateQueryThenErr :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
contract RemoteOracles =
public function callRegisterOracle(
r : Oracles,
acct : address,
qfee : int,
ttl : Chain.ttl) : oracle(string, int) =
r.registerOracle(acct, qfee, ttl)
public function callCreateQuery(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
require(value =< Call.value, "insufficient value")
r.createQuery(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQuery(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
r.unsafeCreateQuery(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQueryThenErr(
r : OraclesErr,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
r.unsafeCreateQueryThenErr(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQueryAndThenErr(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
let x = r.unsafeCreateQuery(value = value, o, q, qfee, qttl, rttl)
switch(0) 1 => ()
x // Never reached.
public function callRespond(
r : Oracles,
o : oracle(string, int),
q : oracle_query(string, int),
qr : int) =
r.respond(o, q, qr)
private function require(b : bool, err : string) =
if(!b) abort(err)
-1
View File
@@ -1,4 +1,3 @@
contract Simple = contract Simple =
type t = int => int type t = int => int
entrypoint dummy() = ()
+6 -5
View File
@@ -6,11 +6,11 @@
contract SimpleStorage { contract SimpleStorage {
uint storedData uint storedData
entrypoint set(uint x) { function set(uint x) {
storedData = x storedData = x
} }
entrypoint get() constant returns (uint) { function get() constant returns (uint) {
return storedData return storedData
} }
} }
@@ -18,11 +18,12 @@
contract SimpleStorage = contract SimpleStorage =
type event = int
record state = { data : int } record state = { data : int }
entrypoint init(value : int) : state = { data = value } function init(value : int) : state = { data = value }
entrypoint get() : int = state.data function get() : int = state.data
stateful entrypoint set(value : int) = function set(value : int) =
put(state{data = value}) put(state{data = value})
+7 -7
View File
@@ -1,26 +1,26 @@
contract SpendContract = contract SpendContract =
entrypoint withdraw : (int) => int function withdraw : (int) => int
contract SpendTest = contract SpendTest =
stateful entrypoint spend(to, amount) = function spend(to, amount) =
let total = Contract.balance let total = Contract.balance
Chain.spend(to, amount) Chain.spend(to, amount)
total - amount total - amount
stateful entrypoint withdraw(amount) : int = function withdraw(amount) : int =
spend(Call.caller, amount) spend(Call.caller, amount)
stateful entrypoint withdraw_from(account, amount) = function withdraw_from(account, amount) =
account.withdraw(amount) account.withdraw(amount)
withdraw(amount) withdraw(amount)
stateful entrypoint spend_from(from, to, amount) = 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)
entrypoint get_balance() = Contract.balance function get_balance() = Contract.balance
entrypoint get_balance_of(a) = Chain.balance(a) function get_balance_of(a) = Chain.balance(a)

Some files were not shown because too many files have changed in this diff Show More