Contract factories and bytecode introspection (#305)

* Support for CREATE, CLONE and BYTECODE_HASH

* Add missing files

* Pushed the clone example through the typechecker

* CLONE compiles

* Fix dependent type in CLONE

* Bytecode hash fixes

* Refactor

* Refactor 2

* move some logic away

* Fixed some error messages. Type inference of child contract still does some random shit\n(mistakes arguments with result type)

* CREATE sometimes compiles and sometimes not

* Fix some scoping/constraint issues

* works, needs cleanup

* cleanup

* Fix some tests. Remove optimization of singleton tuples

* Fix default argument for clone

* Cleanup

* CHANGELOG

* Mention void type

* Address review, fix some dialyzer errors

* Please dialyzer

* Fix failing tests

* Write negative tests

* Docs

* TOC

* missing 'the'

* missing 'the'

* missing 'the'

* missing 'the'

* mention pre-fund

* format

* pre-fund clarification

* format

* Grammar in docs
This commit is contained in:
Radosław Rowicki 2021-05-18 12:21:57 +02:00 committed by GitHub
parent d82b42518e
commit 1d9f59fec3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 1113 additions and 494 deletions

View File

@ -6,7 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- Child contracts
- `Chain.clone`
- `Chain.create`
- `Chain.bytecode_hash`
- Minor support for variadic functions
- `void` type that represents an empty type
### Changed ### Changed
- Contract interfaces must be now invocated by `contract interface` keywords
- `main` keyword to indicate the main contract in case there are child contracts around
- `List.sum` and `List.product` no longer use `List.foldl`
### Removed ### Removed
## [5.0.0] 2021-04-30 ## [5.0.0] 2021-04-30

View File

@ -1,12 +1,23 @@
<!-- IMPORTANT: REMEMBER TO UPDATE THE TABLE OF CONTENTS AFTER YOUR EDIT --> <!-- IMPORTANT: REMEMBER TO UPDATE THE TABLE OF CONTENTS AFTER YOUR EDIT -->
# The Sophia Language
An Æternity BlockChain Language
The Sophia is a language in the ML family. It is strongly typed and has
restricted mutable state.
Sophia is customized for smart contracts, which can be published
to a blockchain (the Æternity BlockChain). Thus some features of conventional
languages, such as floating point arithmetic, are not present in Sophia, and
some blockchain specific primitives, constructions and types have been added.
**Table of Contents** **Table of Contents**
- [-](#-) - [Language Features](#language-features)
- [Language Features](#language-features)
- [Contracts](#contracts) - [Contracts](#contracts)
- [Calling other contracts](#calling-other-contracts) - [Calling other contracts](#calling-other-contracts)
- [Protected contract calls](#protected-contract-calls) - [Protected contract calls](#protected-contract-calls)
- [Contract factories and child contracts](#contract-factories-and-child-contracts)
- [Mutable state](#mutable-state) - [Mutable state](#mutable-state)
- [Stateful functions](#stateful-functions) - [Stateful functions](#stateful-functions)
- [Payable](#payable) - [Payable](#payable)
@ -37,7 +48,7 @@
- [Example](#example) - [Example](#example)
- [Sanity checks](#sanity-checks) - [Sanity checks](#sanity-checks)
- [AENS interface](#aens-interface) - [AENS interface](#aens-interface)
- [Example](#example-1) - [Example](#example)
- [Events](#events) - [Events](#events)
- [Argument order](#argument-order) - [Argument order](#argument-order)
- [Compiler pragmas](#compiler-pragmas) - [Compiler pragmas](#compiler-pragmas)
@ -50,7 +61,7 @@
- [Layout blocks](#layout-blocks) - [Layout blocks](#layout-blocks)
- [Notation](#notation) - [Notation](#notation)
- [Declarations](#declarations) - [Declarations](#declarations)
- [Types](#types-1) - [Types](#types)
- [Statements](#statements) - [Statements](#statements)
- [Expressions](#expressions) - [Expressions](#expressions)
- [Operators types](#operators-types) - [Operators types](#operators-types)
@ -58,17 +69,6 @@
- [Examples](#examples) - [Examples](#examples)
- [Delegation signature](#delegation-signature) - [Delegation signature](#delegation-signature)
## The Sophia Language
An Æternity BlockChain Language
The Sophia is a language in the ML family. It is strongly typed and has
restricted mutable state.
Sophia is customized for smart contracts, which can be published
to a blockchain (the Æternity BlockChain). Thus some features of conventional
languages, such as floating point arithmetic, are not present in Sophia, and
some blockchain specific primitives, constructions and types have been added.
## Language Features ## Language Features
### Contracts ### Contracts
@ -94,16 +94,16 @@ To call a function in another contract you need the address to an instance of
the contract. The type of the address must be a contract type, which consists the contract. The type of the address must be a contract type, which consists
of a number of type definitions and entrypoint declarations. For instance, of a number of type definitions and entrypoint declarations. For instance,
```javascript ```sophia
// A contract type // A contract type
contract VotingType = contract interface VotingType =
entrypoint vote : string => unit entrypoint vote : string => unit
``` ```
Now given contract address of type `VotingType` you can call the `vote` Now given contract address of type `VotingType` you can call the `vote`
entrypoint of that contract: entrypoint of that contract:
```javascript ```sophia
contract VoteTwice = contract VoteTwice =
entrypoint voteTwice(v : VotingType, alt : string) = entrypoint voteTwice(v : VotingType, alt : string) =
v.vote(alt) v.vote(alt)
@ -114,7 +114,7 @@ Contract calls take two optional named arguments `gas : int` and `value : int`
that lets you set a gas limit and provide tokens to a contract call. If omitted that lets you set a gas limit and provide tokens to a contract call. If omitted
the defaults are no gas limit and no tokens. Suppose there is a fee for voting: the defaults are no gas limit and no tokens. Suppose there is a fee for voting:
```javascript ```sophia
entrypoint voteTwice(v : VotingType, fee : int, alt : string) = entrypoint voteTwice(v : VotingType, fee : int, alt : string) =
v.vote(value = fee, alt) v.vote(value = fee, alt)
v.vote(value = fee, alt) v.vote(value = fee, alt)
@ -136,7 +136,7 @@ To recover the underlying `address` of a contract instance there is a field
`address : address`. For instance, to send tokens to the voting contract (given that it is payable) `address : address`. For instance, to send tokens to the voting contract (given that it is payable)
without calling it you can write without calling it you can write
```javascript ```sophia
entrypoint pay(v : VotingType, amount : int) = entrypoint pay(v : VotingType, amount : int) =
Chain.spend(v.address, amount) Chain.spend(v.address, amount)
``` ```
@ -154,7 +154,7 @@ If the call fails the result is `None`, otherwise it's `Some(r)` where `r` is
the return value of the call. the return value of the call.
```sophia ```sophia
contract VotingType = contract interface VotingType =
entrypoint : vote : string => unit entrypoint : vote : string => unit
contract Voter = contract Voter =
@ -171,10 +171,42 @@ However, note that errors that would normally consume all the gas in the
transaction still only uses up the gas spent running the contract. transaction still only uses up the gas spent running the contract.
#### Contract factories and child contracts
Since the version 5.0.0 Sophia supports deploying contracts by other
contracts. This can be done in two ways:
- Contract cloning via [`Chain.clone`](sophia_stdlib.md#clone)
- Direct deploy via [`Chain.create`](sophia_stdlib.md#create)
These functions take variable number of arguments that must match the created
contract's `init` function. Beside that they take some additional named
arguments please refer to their documentation for the details.
While `Chain.clone` requires only a `contract interface` and a living instance
of a given contract on the chain, `Chain.create` needs a full definition of a
to-create contract defined by the standard `contract` syntax, for example
```
contract IntHolder =
type state = int
entrypoint init(x) = x
entrypoint get() = state
main contract IntHolderFactory =
entrypoint new(x : int) : IntHolder =
let ih = Chain.create(x) : IntHolder
ih
```
In case of a presence of child contracts (`IntHolder` in this case), the main
contract must be pointed out with the `main` keyword as shown in the example.
### Mutable state ### Mutable state
Sophia does not have arbitrary mutable state, but only a limited form of Sophia does not have arbitrary mutable state, but only a limited form of state
state associated with each contract instance. associated with each contract instance.
- Each contract defines a type `state` encapsulating its mutable state. - Each contract defines a type `state` encapsulating its mutable state.
The type `state` defaults to the `unit`. The type `state` defaults to the `unit`.
@ -200,7 +232,7 @@ Top-level functions and entrypoints must be annotated with the
`stateful` keyword to be allowed to affect the state of the running contract. `stateful` keyword to be allowed to affect the state of the running contract.
For instance, For instance,
```javascript ```sophia
stateful entrypoint set_state(s : state) = stateful entrypoint set_state(s : state) =
put(s) put(s)
``` ```
@ -237,7 +269,7 @@ A concrete contract is by default *not* payable. Any attempt at spending to such
a contract (either a `Chain.spend` or a normal spend transaction) will fail. If a a contract (either a `Chain.spend` or a normal spend transaction) will fail. If a
contract shall be able to receive funds in this way it has to be declared `payable`: contract shall be able to receive funds in this way it has to be declared `payable`:
```javascript ```sophia
// A payable contract // A payable contract
payable contract ExampleContract = payable contract ExampleContract =
stateful entrypoint do_stuff() = ... stateful entrypoint do_stuff() = ...
@ -253,7 +285,7 @@ A contract entrypoint is by default *not* payable. Any call to such a function
that has a non-zero `value` will fail. Contract entrypoints that should be called that has a non-zero `value` will fail. Contract entrypoints that should be called
with a non-zero value should be declared `payable`. with a non-zero value should be declared `payable`.
```javascript ```sophia
payable stateful entrypoint buy(to : address) = payable stateful entrypoint buy(to : address) =
if(Call.value > 42) if(Call.value > 42)
transfer_item(to) transfer_item(to)
@ -414,7 +446,7 @@ corresponding integer, so setting very high bits can be expensive).
Type aliases can be introduced with the `type` keyword and can be Type aliases can be introduced with the `type` keyword and can be
parameterized. For instance parameterized. For instance
``` ```sophia
type number = int type number = int
type string_map('a) = map(string, 'a) type string_map('a) = map(string, 'a)
``` ```
@ -434,7 +466,7 @@ datatype one_or_both('a, 'b) = Left('a) | Right('b) | Both('a, 'b)
Elements of data types can be pattern matched against, using the `switch` construct: Elements of data types can be pattern matched against, using the `switch` construct:
``` ```sophia
function get_left(x : one_or_both('a, 'b)) : option('a) = function get_left(x : one_or_both('a, 'b)) : option('a) =
switch(x) switch(x)
Left(x) => Some(x) Left(x) => Some(x)
@ -443,7 +475,7 @@ function get_left(x : one_or_both('a, 'b)) : option('a) =
``` ```
or directly in the left-hand side: or directly in the left-hand side:
``` ```sophia
function function
get_left : one_or_both('a, 'b) => option('a) get_left : one_or_both('a, 'b) => option('a)
get_left(Left(x)) = Some(x) get_left(Left(x)) = Some(x)
@ -461,7 +493,7 @@ elements of a list can be any of datatype but they must have the same
type. The type of lists with elements of type `'e` is written type. The type of lists with elements of type `'e` is written
`list('e)`. For example we can have the following lists: `list('e)`. For example we can have the following lists:
``` ```sophia
[1, 33, 2, 666] : list(int) [1, 33, 2, 666] : list(int)
[(1, "aaa"), (10, "jjj"), (666, "the beast")] : list(int * string) [(1, "aaa"), (10, "jjj"), (666, "the beast")] : list(int * string)
[{[1] = "aaa", [10] = "jjj"}, {[5] = "eee", [666] = "the beast"}] : list(map(int, string)) [{[1] = "aaa", [10] = "jjj"}, {[5] = "eee", [666] = "the beast"}] : list(map(int, string))
@ -475,13 +507,13 @@ and returns the resulting list. So concatenating two lists
Sophia supports list comprehensions known from languages like Python, Haskell or Erlang. Sophia supports list comprehensions known from languages like Python, Haskell or Erlang.
Example syntax: Example syntax:
``` ```sophia
[x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]] [x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]]
// yields [12,13,14,20,21,22,30,31,32] // yields [12,13,14,20,21,22,30,31,32]
``` ```
Lists can be constructed using the range syntax using special `..` operator: Lists can be constructed using the range syntax using special `..` operator:
``` ```sophia
[1..4] == [1,2,3,4] [1..4] == [1,2,3,4]
``` ```
The ranges are always ascending and have step equal to 1. The ranges are always ascending and have step equal to 1.
@ -493,7 +525,7 @@ Please refer to the [standard library](sophia_stdlib.md#List) for the predefined
A Sophia record type is given by a fixed set of fields with associated, A Sophia record type is given by a fixed set of fields with associated,
possibly different, types. For instance possibly different, types. For instance
``` ```sophia
record account = { name : string, record account = { name : string,
balance : int, balance : int,
history : list(transaction) } history : list(transaction) }
@ -510,12 +542,12 @@ Please refer to the [standard library](sophia_stdlib.md#Map) for the predefined
A value of record type is constructed by giving a value for each of the fields. A value of record type is constructed by giving a value for each of the fields.
For the example above, For the example above,
``` ```sophia
function new_account(name) = function new_account(name) =
{name = name, balance = 0, history = []} {name = name, balance = 0, history = []}
``` ```
Maps are constructed similarly, with keys enclosed in square brackets Maps are constructed similarly, with keys enclosed in square brackets
``` ```sophia
function example_map() : map(string, int) = function example_map() : map(string, int) =
{["key1"] = 1, ["key2"] = 2} {["key1"] = 1, ["key2"] = 2}
``` ```
@ -524,7 +556,7 @@ The empty map is written `{}`.
#### Accessing values #### Accessing values
Record fields access is written `r.f` and map lookup `m[k]`. For instance, Record fields access is written `r.f` and map lookup `m[k]`. For instance,
``` ```sophia
function get_balance(a : address, accounts : map(address, account)) = function get_balance(a : address, accounts : map(address, account)) =
accounts[a].balance accounts[a].balance
``` ```
@ -549,14 +581,14 @@ in the map or execution fails, but a default value can be provided:
`k` is not in the map. `k` is not in the map.
Updates can be nested: Updates can be nested:
``` ```sophia
function clear_history(a : address, accounts : map(address, account)) : map(address, account) = function clear_history(a : address, accounts : map(address, account)) : map(address, account) =
accounts{ [a].history = [] } accounts{ [a].history = [] }
``` ```
This is equivalent to `accounts{ [a] @ acc = acc{ history = [] } }` and thus This is equivalent to `accounts{ [a] @ acc = acc{ history = [] } }` and thus
requires `a` to be present in the accounts map. To have `clear_history` create requires `a` to be present in the accounts map. To have `clear_history` create
an account if `a` is not in the map you can write (given a function `empty_account`): an account if `a` is not in the map you can write (given a function `empty_account`):
``` ```sophia
accounts{ [a = empty_account()].history = [] } accounts{ [a = empty_account()].history = [] }
``` ```
@ -627,7 +659,7 @@ For a functionality documentation refer to the [standard library](sophia_stdlib.
#### Example #### Example
Example for an oracle answering questions of type `string` with answers of type `int`: Example for an oracle answering questions of type `string` with answers of type `int`:
``` ```sophia
contract Oracles = contract Oracles =
stateful entrypoint registerOracle(acct : address, stateful entrypoint registerOracle(acct : address,
@ -698,7 +730,7 @@ an account with address `addr`. In order to allow a contract `ct` to handle
Armed with this information we can for example write a function that extends Armed with this information we can for example write a function that extends
the name if it expires within 1000 blocks: the name if it expires within 1000 blocks:
``` ```sophia
stateful entrypoint extend_if_necessary(addr : address, name : string, sig : signature) = stateful entrypoint extend_if_necessary(addr : address, name : string, sig : signature) =
switch(AENS.lookup(name)) switch(AENS.lookup(name))
None => () None => ()
@ -709,7 +741,7 @@ the name if it expires within 1000 blocks:
And we can write functions that adds and removes keys from the pointers of the And we can write functions that adds and removes keys from the pointers of the
name: name:
``` ```sophia
stateful entrypoint add_key(addr : address, name : string, key : string, stateful entrypoint add_key(addr : address, name : string, key : string,
pt : AENS.pointee, sig : signature) = pt : AENS.pointee, sig : signature) =
switch(AENS.lookup(name)) switch(AENS.lookup(name))
@ -768,7 +800,7 @@ The fields can appear in any order.
Events are emitted by using the `Chain.event` function. The following function Events are emitted by using the `Chain.event` function. The following function
will emit one Event of each kind in the example. will emit one Event of each kind in the example.
``` ```sophia
entrypoint emit_events() : () = entrypoint emit_events() : () =
Chain.event(TheFirstEvent(42)) Chain.event(TheFirstEvent(42))
Chain.event(AnotherEvent(Contract.address, "This is not indexed")) Chain.event(AnotherEvent(Contract.address, "This is not indexed"))
@ -778,7 +810,7 @@ will emit one Event of each kind in the example.
It is only possible to have one (1) `string` parameter in the event, but it can It is only possible to have one (1) `string` parameter in the event, but it can
be placed in any position (and its value will end up in the `data` field), i.e. be placed in any position (and its value will end up in the `data` field), i.e.
``` ```sophia
AnotherEvent(string, indexed address) AnotherEvent(string, indexed address)
... ...
@ -837,7 +869,7 @@ and `*/` and can be nested.
``` ```
contract elif else entrypoint false function if import include let mod namespace contract elif else entrypoint false function if import include let mod namespace
private payable stateful switch true type record datatype private payable stateful switch true type record datatype main interface
``` ```
#### Tokens #### Tokens
@ -941,7 +973,7 @@ Args ::= '(' Sep(Pattern, ',') ')'
Contract declarations must appear at the top-level. Contract declarations must appear at the top-level.
For example, For example,
``` ```sophia
contract Test = contract Test =
type t = int type t = int
entrypoint add (x : t, y : t) = x + y entrypoint add (x : t, y : t) = x + y
@ -1089,7 +1121,7 @@ In order of highest to lowest precedence.
## Examples ## Examples
``` ```sophia
/* /*
* A simple crowd-funding example * A simple crowd-funding example
*/ */

View File

@ -765,6 +765,127 @@ Chain.gas_limit : int
The gas limit of the current block. The gas limit of the current block.
#### bytecode_hash
```
Chain.bytecode_hash : 'c => option(hash)
```
Returns the hash of the contract's bytecode (or `None` if it is
nonexistent or deployed before FATE2). The type `'c` must be
instantiated with a contract. The charged gas increases linearly to
the size of the serialized bytecode of the deployed contract.
#### create
```
Chain.create(value : int, ...) => 'c
```
Creates and deploys a new instance of a contract `'c`. All of the
unnamed arguments will be passed to the `init` function. The charged
gas increases linearly with the size of the compiled child contract's
bytecode. The `source_hash` on-chain entry of the newly created
contract will be the SHA256 hash over concatenation of
- whole contract source code
- single null byte
- name of the child contract
The resulting contract's public key can be predicted and in case it happens to
have some funds before its creation, its balance will be increased by
the `value` parameter.
The `value` argument (default `0`) is equivalent to the value in the contract
creation transaction it sets the initial value of the newly created contract
charging the calling contract. Note that this won't be visible in `Call.value`
in the `init` call of the new contract. It will be included in
`Contract.balance`, however.
The type `'c` must be instantiated with a contract.
Example usage:
```
payable contract Auction =
record state = {supply: int, name: string}
entrypoint init(supply, name) = {supply: supply, name: name}
stateful payable entrypoint buy(amount) =
require(Call.value == amount, "amount_value_mismatch")
...
stateful entrypoint sell(amount) =
require(amount >= 0, "negative_amount")
...
main contract Market =
type state = list(Auction)
entrypoint init() = []
stateful entrypoint new(name : string) =
let auction = Chain.create(0, name) : Auction
put(new_auction::state)
```
The typechecker must be certain about the created contract's type, so it is
worth writing it explicitly as shown in the example.
#### clone
```
Chain.clone : ( ref : 'c, gas : int, value : int, protected : bool, ...
) => if(protected) option('c) else 'c
```
Clones the contract under the mandatory named argument `ref`. That means a new
contract of the same bytecode and the same `payable` parameter shall be created.
**NOTE:** the `state` won't be copied and the contract will be initialized with
a regular call to the `init` function with the remaining unnamed arguments. The
resulting contract's public key can be predicted and in case it happens to have
some funds before its creation, its balance will be increased by the `value`
parameter. This operation is significantly cheaper than `Chain.create` as it
costs a fixed amount of gas.
The `gas` argument (default `Call.gas_left`) limits the gas supply for the
`init` call of the cloned contract.
The `value` argument (default `0`) is equivalent to the value in the contract
creation transaction it sets the initial value of the newly created contract
charging the calling contract. Note that this won't be visible in `Call.value`
in the `init` call of the new contract. It will be included in
`Contract.balance`, however.
The `protected` argument (default `false`) works identically as in remote calls.
If set to `true` it will change the return type to `option('c)` and will catch
all errors such as `abort`, out of gas and wrong arguments. Note that it can
only take a boolean *literal*, so other expressions such as variables will be
rejected by the compiler.
The type `'c` must be instantiated with a contract.
Example usage:
```
payable contract interface Auction =
entrypoint init : (int, string) => void
stateful payable entrypoint buy : (int) => ()
stateful entrypoint sell : (int) => ()
main contract Market =
type state = list(Auction)
entrypoint init() = []
stateful entrypoint new_of(template : Auction, name : string) =
switch(Chain.clone(ref=template, protected=true, 0, name))
None => abort("Bad auction!")
Some(new_auction) =>
put(new_auction::state)
```
When cloning by an interface, `init` entrypoint declaration is required. It is a
good practice to set its return type to `void` in order to indicate that this
function is not supposed to be called and is state agnostic. Trivia: internal
implementation of the `init` function does not actually return `state`, but
calls `put` instead. Moreover, FATE prevents even handcrafted calls to `init`.
#### event #### event
``` ```
Chain.event(e : event) : unit Chain.event(e : event) : unit

View File

@ -208,10 +208,13 @@ namespace List =
[] => false [] => false
h::t => if(p(h)) true else any(p, t) h::t => if(p(h)) true else any(p, t)
function sum(l : list(int)) : int = foldl ((a, b) => a + b, 0, l) function sum(l : list(int)) : int = switch(l)
[] => 0
function product(l : list(int)) : int = foldl((a, b) => a * b, 1, l) h::t => h + sum(t)
function product(l : list(int)) : int = switch(l)
[] => 1
h::t => h * sum(t)
/** Zips two list by applying bimapping function on respective elements. /** Zips two list by applying bimapping function on respective elements.
* Drops the tail of the longer list. * Drops the tail of the longer list.

View File

@ -2,7 +2,7 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"7f0d309"}}} {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"951db9f"}}}
, {getopt, "1.0.1"} , {getopt, "1.0.1"}
, {eblake2, "1.0.0"} , {eblake2, "1.0.0"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", , {jsx, {git, "https://github.com/talentdeficit/jsx.git",

View File

@ -1,7 +1,7 @@
{"1.1.0", {"1.1.0",
[{<<"aebytecode">>, [{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git", {git,"https://github.com/aeternity/aebytecode.git",
{ref,"7f0d3090d4dc6c4d5fca7645b0c21eb0e65ad208"}}, {ref,"951db9f38412a1f798987403d24c512d82fec8c7"}},
0}, 0},
{<<"aeserialization">>, {<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git", {git,"https://github.com/aeternity/aeserialization.git",

View File

@ -21,6 +21,8 @@
, json_encode_expr/1 , json_encode_expr/1
, json_encode_type/1]). , json_encode_type/1]).
-include("aeso_utils.hrl").
-type aci_type() :: json | string. -type aci_type() :: json | string.
-type json() :: jsx:json_term(). -type json() :: jsx:json_term().
-type json_text() :: binary(). -type json_text() :: binary().
@ -68,9 +70,7 @@ do_contract_interface(Type, Contract, Options) when is_binary(Contract) ->
do_contract_interface(Type, ContractString, Options) -> do_contract_interface(Type, ContractString, Options) ->
try try
Ast = aeso_compiler:parse(ContractString, Options), Ast = aeso_compiler:parse(ContractString, Options),
%% io:format("~p\n", [Ast]),
{TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]), {TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
%% io:format("~p\n", [TypedAst]),
from_typed_ast(Type, TypedAst) from_typed_ast(Type, TypedAst)
catch catch
throw:{error, Errors} -> {error, Errors} throw:{error, Errors} -> {error, Errors}
@ -83,7 +83,7 @@ from_typed_ast(Type, TypedAst) ->
string -> do_render_aci_json(JArray) string -> do_render_aci_json(JArray)
end. end.
encode_contract(Contract = {contract, _, {con, _, Name}, _}) -> encode_contract(Contract = {Head, _, {con, _, Name}, _}) when ?IS_CONTRACT_HEAD(Head) ->
C0 = #{name => encode_name(Name)}, C0 = #{name => encode_name(Name)},
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ], Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ],
@ -107,7 +107,7 @@ encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
|| F <- sort_decls(contract_funcs(Contract)), || F <- sort_decls(contract_funcs(Contract)),
is_entrypoint(F) ], is_entrypoint(F) ],
#{contract => C3#{functions => Fdefs, payable => is_payable(Contract)}}; #{contract => C3#{kind => Head, functions => Fdefs, payable => is_payable(Contract)}};
encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) -> encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) ->
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ], Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ],
#{namespace => #{name => encode_name(Name), #{namespace => #{name => encode_name(Name),
@ -232,13 +232,19 @@ do_render_aci_json(Json) ->
{ok, list_to_binary(string:join(DecodedContracts, "\n"))}. {ok, list_to_binary(string:join(DecodedContracts, "\n"))}.
decode_contract(#{contract := #{name := Name, decode_contract(#{contract := #{name := Name,
kind := Kind,
payable := Payable, payable := Payable,
type_defs := Ts0, type_defs := Ts0,
functions := Fs} = C}) -> functions := Fs} = C}) ->
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end, MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++ Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
[ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0, [ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0,
[payable(Payable), "contract ", io_lib:format("~s", [Name])," =\n", [payable(Payable), case Kind of
contract_main -> "main contract ";
contract_child -> "contract ";
contract_interface -> "contract interface "
end,
io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts), decode_funcs(Fs)]; decode_tdefs(Ts), decode_funcs(Fs)];
decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] -> decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] ->
["namespace ", io_lib:format("~s", [Name])," =\n", ["namespace ", io_lib:format("~s", [Name])," =\n",
@ -332,10 +338,10 @@ payable(false) -> "".
%% #contract{Ann, Con, [Declarations]}. %% #contract{Ann, Con, [Declarations]}.
contract_funcs({C, _, _, Decls}) when C == contract; C == namespace -> contract_funcs({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace ->
[ D || D <- Decls, is_fun(D)]. [ D || D <- Decls, is_fun(D)].
contract_types({C, _, _, Decls}) when C == contract; C == namespace -> contract_types({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace ->
[ D || D <- Decls, is_type(D) ]. [ D || D <- Decls, is_type(D) ].
is_fun({letfun, _, _, _, _, _}) -> true; is_fun({letfun, _, _, _, _, _}) -> true;

View File

@ -18,7 +18,9 @@
, pp_type/2 , pp_type/2
]). ]).
-type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()], utype()} -include("aeso_utils.hrl").
-type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()] | var_args, utype()}
| {app_t, aeso_syntax:ann(), utype(), [utype()]} | {app_t, aeso_syntax:ann(), utype(), [utype()]}
| {tuple_t, aeso_syntax:ann(), [utype()]} | {tuple_t, aeso_syntax:ann(), [utype()]}
| aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:id() | aeso_syntax:qid()
@ -39,6 +41,7 @@
element(1, T) =:= qcon). element(1, T) =:= qcon).
-type why_record() :: aeso_syntax:field(aeso_syntax:expr()) -type why_record() :: aeso_syntax:field(aeso_syntax:expr())
| {var_args, aeso_syntax:ann(), aeso_syntax:expr()}
| {proj, aeso_syntax:ann(), aeso_syntax:expr(), aeso_syntax:id()}. | {proj, aeso_syntax:ann(), aeso_syntax:expr(), aeso_syntax:id()}.
-type pos() :: aeso_errors:pos(). -type pos() :: aeso_errors:pos().
@ -73,7 +76,10 @@
-record(is_contract_constraint, -record(is_contract_constraint,
{ contract_t :: utype(), { contract_t :: utype(),
context :: {contract_literal, aeso_syntax:expr()} | context :: {contract_literal, aeso_syntax:expr()} |
{address_to_contract, aeso_syntax:ann()} {address_to_contract, aeso_syntax:ann()} |
{bytecode_hash, aeso_syntax:ann()} |
{var_args, aeso_syntax:ann(), aeso_syntax:expr()},
force_def = false :: boolean()
}). }).
-type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}. -type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}.
@ -96,7 +102,7 @@
-type qname() :: [string()]. -type qname() :: [string()].
-type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [type()], type()}. -type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [type()], type()}.
-type type_constraints() :: none | bytes_concat | bytes_split | address_to_contract. -type type_constraints() :: none | bytes_concat | bytes_split | address_to_contract | bytecode_hash.
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}. -type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
-type type_info() :: {aeso_syntax:ann(), typedef()}. -type type_info() :: {aeso_syntax:ann(), typedef()}.
@ -123,13 +129,14 @@
, in_pattern = false :: boolean() , in_pattern = false :: boolean()
, stateful = false :: boolean() , stateful = false :: boolean()
, current_function = none :: none | aeso_syntax:id() , current_function = none :: none | aeso_syntax:id()
, what = top :: top | namespace | contract | main_contract , what = top :: top | namespace | contract | contract_interface
}). }).
-type env() :: #env{}. -type env() :: #env{}.
-define(PRINT_TYPES(Fmt, Args), -define(PRINT_TYPES(Fmt, Args),
when_option(pp_types, fun () -> io:format(Fmt, Args) end)). when_option(pp_types, fun () -> io:format(Fmt, Args) end)).
-define(CONSTRUCTOR_MOCK_NAME, "#__constructor__#").
%% -- Environment manipulation ----------------------------------------------- %% -- Environment manipulation -----------------------------------------------
@ -191,9 +198,9 @@ bind_fun(X, Type, Env) ->
force_bind_fun(X, Type, Env = #env{ what = What }) -> force_bind_fun(X, Type, Env = #env{ what = What }) ->
Ann = aeso_syntax:get_ann(Type), Ann = aeso_syntax:get_ann(Type),
NoCode = get_option(no_code, false), NoCode = get_option(no_code, false),
Entry = if X == "init", What == main_contract, not NoCode -> Entry = if X == "init", What == contract, not NoCode ->
{reserved_init, Ann, Type}; {reserved_init, Ann, Type};
What == contract -> {contract_fun, Ann, Type}; What == contract_interface -> {contract_fun, Ann, Type};
true -> {Ann, Type} true -> {Ann, Type}
end, end,
on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) -> on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) ->
@ -261,13 +268,40 @@ contract_call_type({fun_t, Ann, [], Args, Ret}) ->
Args, {if_t, Ann, Id("protected"), {app_t, Ann, {id, Ann, "option"}, [Ret]}, Ret}}. Args, {if_t, Ann, Id("protected"), {app_t, Ann, {id, Ann, "option"}, [Ret]}, Ret}}.
-spec bind_contract(aeso_syntax:decl(), env()) -> env(). -spec bind_contract(aeso_syntax:decl(), env()) -> env().
bind_contract({contract, Ann, Id, Contents}, Env) -> bind_contract({Contract, Ann, Id, Contents}, Env)
when ?IS_CONTRACT_HEAD(Contract) ->
Key = name(Id), Key = name(Id),
Sys = [{origin, system}], Sys = [{origin, system}],
Fields = [ {field_t, AnnF, Entrypoint, contract_call_type(Type)} Fields =
[ {field_t, AnnF, Entrypoint, contract_call_type(Type)}
|| {fun_decl, AnnF, Entrypoint, Type} <- Contents ] ++ || {fun_decl, AnnF, Entrypoint, Type} <- Contents ] ++
[ {field_t, AnnF, Entrypoint,
contract_call_type(
{fun_t, AnnF, [], [ArgT || {typed, _, _, ArgT} <- Args], RetT})
}
|| {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, {typed, _, _, RetT}} <- Contents,
Name =/= "init"
] ++
%% Predefined fields %% Predefined fields
[ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ], [ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ] ++
[ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME},
contract_call_type(
case [ [ArgT || {typed, _, _, ArgT} <- Args]
|| {letfun, AnnF, {id, _, "init"}, Args, _, _} <- Contents,
aeso_syntax:get_ann(entrypoint, AnnF, false)]
++ [ Args
|| {fun_decl, AnnF, {id, _, "init"}, {fun_t, _, _, Args, _}} <- Contents,
aeso_syntax:get_ann(entrypoint, AnnF, false)]
++ [ Args
|| {fun_decl, AnnF, {id, _, "init"}, {type_sig, _, _, _, Args, _}} <- Contents,
aeso_syntax:get_ann(entrypoint, AnnF, false)]
of
[] -> {fun_t, [stateful,payable|Sys], [], [], {id, Sys, "void"}};
[Args] -> {fun_t, [stateful,payable|Sys], [], Args, {id, Sys, "void"}}
end
)
}
],
FieldInfo = [ {Entrypoint, #field_info{ ann = FieldAnn, FieldInfo = [ {Entrypoint, #field_info{ ann = FieldAnn,
kind = contract, kind = contract,
field_t = Type, field_t = Type,
@ -396,8 +430,11 @@ global_env() ->
Map = fun(A, B) -> {app_t, Ann, {id, Ann, "map"}, [A, B]} end, Map = fun(A, B) -> {app_t, Ann, {id, Ann, "map"}, [A, B]} end,
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end, Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
FunC = fun(C, Ts, T) -> {type_sig, Ann, C, [], Ts, T} end, FunC = fun(C, Ts, T) -> {type_sig, Ann, C, [], Ts, T} end,
FunC1 = fun(C, S, T) -> {type_sig, Ann, C, [], [S], T} end,
Fun = fun(Ts, T) -> FunC(none, Ts, T) end, Fun = fun(Ts, T) -> FunC(none, Ts, T) end,
Fun1 = fun(S, T) -> Fun([S], T) end, Fun1 = fun(S, T) -> Fun([S], T) end,
FunCN = fun(C, Named, Normal, Ret) -> {type_sig, Ann, C, Named, Normal, Ret} end,
FunN = fun(Named, Normal, Ret) -> FunCN(none, Named, Normal, Ret) end,
%% Lambda = fun(Ts, T) -> {fun_t, Ann, [], Ts, T} end, %% Lambda = fun(Ts, T) -> {fun_t, Ann, [], Ts, T} end,
%% Lambda1 = fun(S, T) -> Lambda([S], T) end, %% Lambda1 = fun(S, T) -> Lambda([S], T) end,
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], none, [], Ts, T} end, StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], none, [], Ts, T} end,
@ -424,6 +461,7 @@ global_env() ->
TxFlds = [{"paying_for", Option(PayForTx)}, {"ga_metas", List(GAMetaTx)}, TxFlds = [{"paying_for", Option(PayForTx)}, {"ga_metas", List(GAMetaTx)},
{"actor", Address}, {"fee", Int}, {"ttl", Int}, {"tx", BaseTx}], {"actor", Address}, {"fee", Int}, {"ttl", Int}, {"tx", BaseTx}],
TxType = {record_t, [FldT(N, T) || {N, T} <- TxFlds ]}, TxType = {record_t, [FldT(N, T) || {N, T} <- TxFlds ]},
Stateful = fun(T) -> setelement(2, T, [stateful|element(2, T)]) end,
Fee = Int, Fee = Int,
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]), [A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
@ -443,6 +481,7 @@ global_env() ->
{"require", Fun([Bool, String], Unit)}]) {"require", Fun([Bool, String], Unit)}])
, types = MkDefs( , types = MkDefs(
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0}, [{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
{"void", 0},
{"unit", {[], {alias_t, Unit}}}, {"unit", {[], {alias_t, Unit}}},
{"hash", {[], {alias_t, Bytes(32)}}}, {"hash", {[], {alias_t, Bytes(32)}}},
{"signature", {[], {alias_t, Bytes(64)}}}, {"signature", {[], {alias_t, Bytes(64)}}},
@ -463,6 +502,23 @@ global_env() ->
{"block_height", Int}, {"block_height", Int},
{"difficulty", Int}, {"difficulty", Int},
{"gas_limit", Int}, {"gas_limit", Int},
{"bytecode_hash",FunC1(bytecode_hash, A, Option(Hash))},
{"create", Stateful(
FunN([ {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}}
], var_args, A))},
{"clone", Stateful(
FunN([ {named_arg_t, Ann, {id, Ann, "gas"}, Int,
{typed, Ann,
{app, Ann,
{typed, Ann, {qid, Ann, ["Call","gas_left"]},
typesig_to_fun_t(Fun([], Int))
},
[]}, Int
}}
, {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}}
, {named_arg_t, Ann, {id, Ann, "protected"}, Bool, {typed, Ann, {bool, Ann, false}, Bool}}
, {named_arg_t, Ann, {id, Ann, "ref"}, A, undefined}
], var_args, A))},
%% Tx constructors %% Tx constructors
{"GAMetaTx", Fun([Address, Int], GAMetaTx)}, {"GAMetaTx", Fun([Address, Int], GAMetaTx)},
{"PayingForTx", Fun([Address, Int], PayForTx)}, {"PayingForTx", Fun([Address, Int], PayForTx)},
@ -699,9 +755,13 @@ infer(Contracts, Options) ->
try try
Env = init_env(Options), Env = init_env(Options),
create_options(Options), create_options(Options),
ets_new(defined_contracts, [bag]),
ets_new(type_vars, [set]), ets_new(type_vars, [set]),
check_modifiers(Env, Contracts), check_modifiers(Env, Contracts),
{Env1, Decls} = infer1(Env, Contracts, [], Options), create_type_errors(),
Contracts1 = identify_main_contract(Contracts, Options),
destroy_and_report_type_errors(Env),
{Env1, Decls} = infer1(Env, Contracts1, [], Options),
{Env2, DeclsFolded, DeclsUnfolded} = {Env2, DeclsFolded, DeclsUnfolded} =
case proplists:get_value(dont_unfold, Options, false) of case proplists:get_value(dont_unfold, Options, false) of
true -> {Env1, Decls, Decls}; true -> {Env1, Decls, Decls};
@ -719,12 +779,21 @@ infer(Contracts, Options) ->
-spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) -> -spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) ->
{env(), [aeso_syntax:decl()]}. {env(), [aeso_syntax:decl()]}.
infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)}; infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)};
infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc, Options) -> infer1(Env, [{Contract, Ann, ConName, Code} | Rest], Acc, Options)
when ?IS_CONTRACT_HEAD(Contract) ->
%% do type inference on each contract independently. %% do type inference on each contract independently.
check_scope_name_clash(Env, contract, ConName), check_scope_name_clash(Env, contract, ConName),
What = if Rest == [] -> main_contract; true -> contract end, What = case Contract of
contract_main -> contract;
contract_child -> contract;
contract_interface -> contract_interface
end,
case What of
contract -> ets_insert(defined_contracts, {qname(ConName)});
contract_interface -> ok
end,
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), What, Code, Options), {Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), What, Code, Options),
Contract1 = {contract, Ann, ConName, Code1}, Contract1 = {Contract, Ann, ConName, Code1},
Env2 = pop_scope(Env1), Env2 = pop_scope(Env1),
Env3 = bind_contract(Contract1, Env2), Env3 = bind_contract(Contract1, Env2),
infer1(Env3, Rest, [Contract1 | Acc], Options); infer1(Env3, Rest, [Contract1 | Acc], Options);
@ -737,6 +806,25 @@ infer1(Env, [{pragma, _, _} | Rest], Acc, Options) ->
%% Pragmas are checked in check_modifiers %% Pragmas are checked in check_modifiers
infer1(Env, Rest, Acc, Options). infer1(Env, Rest, Acc, Options).
%% Asserts that the main contract is somehow defined.
identify_main_contract(Contracts, Options) ->
Children = [C || C = {contract_child, _, _, _} <- Contracts],
Mains = [C || C = {contract_main, _, _, _} <- Contracts],
case Mains of
[] -> case Children of
[] -> type_error(
{main_contract_undefined,
[{file, File} || {src_file, File} <- Options]});
[{contract_child, Ann, Con, Body}] ->
(Contracts -- Children) ++ [{contract_main, Ann, Con, Body}];
[H|_] -> type_error({ambiguous_main_contract,
aeso_syntax:get_ann(H)})
end;
[_] -> (Contracts -- Mains) ++ Mains; %% Move to the end
[H|_] -> type_error({multiple_main_contracts,
aeso_syntax:get_ann(H)})
end.
check_scope_name_clash(Env, Kind, Name) -> check_scope_name_clash(Env, Kind, Name) ->
case get_scope(Env, qname(Name)) of case get_scope(Env, qname(Name)) of
false -> ok; false -> ok;
@ -746,7 +834,7 @@ check_scope_name_clash(Env, Kind, Name) ->
destroy_and_report_type_errors(Env) destroy_and_report_type_errors(Env)
end. end.
-spec infer_contract_top(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) -> -spec infer_contract_top(env(), contract_interface | contract | namespace, [aeso_syntax:decl()], list(option())) ->
{env(), [aeso_syntax:decl()]}. {env(), [aeso_syntax:decl()]}.
infer_contract_top(Env, Kind, Defs0, Options) -> infer_contract_top(Env, Kind, Defs0, Options) ->
create_type_errors(), create_type_errors(),
@ -756,7 +844,7 @@ infer_contract_top(Env, Kind, Defs0, Options) ->
%% infer_contract takes a proplist mapping global names to types, and %% infer_contract takes a proplist mapping global names to types, and
%% a list of definitions. %% a list of definitions.
-spec infer_contract(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) -> {env(), [aeso_syntax:decl()]}. -spec infer_contract(env(), contract_interface | contract | namespace, [aeso_syntax:decl()], list(option())) -> {env(), [aeso_syntax:decl()]}.
infer_contract(Env0, What, Defs0, Options) -> infer_contract(Env0, What, Defs0, Options) ->
create_type_errors(), create_type_errors(),
Defs01 = process_blocks(Defs0), Defs01 = process_blocks(Defs0),
@ -772,19 +860,19 @@ infer_contract(Env0, What, Defs0, Options) ->
({fun_decl, _, _, _}) -> prototype; ({fun_decl, _, _, _}) -> prototype;
(_) -> unexpected (_) -> unexpected
end, end,
Get = fun(K) -> [ Def || Def <- Defs, Kind(Def) == K ] end, Get = fun(K, In) -> [ Def || Def <- In, Kind(Def) == K ] end,
{Env1, TypeDefs} = check_typedefs(Env, Get(type)), {Env1, TypeDefs} = check_typedefs(Env, Get(type, Defs)),
create_type_errors(), create_type_errors(),
check_unexpected(Get(unexpected)), check_unexpected(Get(unexpected, Defs)),
Env2 = Env2 =
case What of case What of
namespace -> Env1; namespace -> Env1;
contract -> Env1; contract_interface -> Env1;
main_contract -> bind_state(Env1) %% bind state and put contract -> bind_state(Env1) %% bind state and put
end, end,
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype) ]), {ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype, Defs) ]),
Env3 = bind_funs(ProtoSigs, Env2), Env3 = bind_funs(ProtoSigs, Env2),
Functions = Get(function), Functions = Get(function, Defs),
%% Check for duplicates in Functions (we turn it into a map below) %% Check for duplicates in Functions (we turn it into a map below)
FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}}; FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}};
({fun_clauses, Ann, {id, _, Fun}, _, _}) -> {Fun, {tuple_t, Ann, []}} end, ({fun_clauses, Ann, {id, _, Fun}, _, _}) -> {Fun, {tuple_t, Ann, []}} end,
@ -794,11 +882,11 @@ infer_contract(Env0, What, Defs0, Options) ->
check_reserved_entrypoints(FunMap), check_reserved_entrypoints(FunMap),
DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_ids(Def) end, FunMap), DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_ids(Def) end, FunMap),
SCCs = aeso_utils:scc(DepGraph), SCCs = aeso_utils:scc(DepGraph),
%% io:format("Dependency sorted functions:\n ~p\n", [SCCs]),
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []), {Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
%% Check that `init` doesn't read or write the state %% Check that `init` doesn't read or write the state
check_state_dependencies(Env4, Defs1), check_state_dependencies(Env4, Defs1),
destroy_and_report_type_errors(Env4), destroy_and_report_type_errors(Env4),
%% Add inferred types of definitions
{Env4, TypeDefs ++ Decls ++ Defs1}. {Env4, TypeDefs ++ Decls ++ Defs1}.
%% Restructure blocks into multi-clause fundefs (`fun_clauses`). %% Restructure blocks into multi-clause fundefs (`fun_clauses`).
@ -831,8 +919,8 @@ expose_internals(Defs, What) ->
Ann = element(2, Def), Ann = element(2, Def),
NewAnn = case What of NewAnn = case What of
namespace -> [A ||A <- Ann, A /= {private, true}, A /= private]; namespace -> [A ||A <- Ann, A /= {private, true}, A /= private];
main_contract -> [{entrypoint, true}|Ann]; % minor duplication contract -> [{entrypoint, true}|Ann]; % minor duplication
contract -> Ann contract_interface -> Ann
end, end,
Def1 = setelement(2, Def, NewAnn), Def1 = setelement(2, Def, NewAnn),
case Def1 of % fix inner clauses case Def1 of % fix inner clauses
@ -907,15 +995,16 @@ check_modifiers(Env, Contracts) ->
check_modifiers_(Env, Contracts), check_modifiers_(Env, Contracts),
destroy_and_report_type_errors(Env). destroy_and_report_type_errors(Env).
check_modifiers_(Env, [{contract, _, Con, Decls} | Rest]) -> check_modifiers_(Env, [{Contract, _, Con, Decls} | Rest])
IsMain = Rest == [], when ?IS_CONTRACT_HEAD(Contract) ->
IsInterface = Contract =:= contract_interface,
check_modifiers1(contract, Decls), check_modifiers1(contract, Decls),
case {lists:keymember(letfun, 1, Decls), case {lists:keymember(letfun, 1, Decls),
[ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of [ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of
{true, []} -> type_error({contract_has_no_entrypoints, Con}); {true, []} -> type_error({contract_has_no_entrypoints, Con});
_ when not IsMain -> _ when IsInterface ->
case [ {Ann, Id} || {letfun, Ann, Id, _, _, _} <- Decls ] of case [ {AnnF, Id} || {letfun, AnnF, Id, _, _, _} <- Decls ] of
[{Ann, Id} | _] -> type_error({definition_in_non_main_contract, Ann, Id}); [{AnnF, Id} | _] -> type_error({definition_in_contract_interface, AnnF, Id});
[] -> ok [] -> ok
end; end;
_ -> ok _ -> ok
@ -1408,8 +1497,7 @@ infer_expr(Env, {typed, As, Body, Type}) ->
{typed, _, NewBody, NewType} = check_expr(Env, Body, Type1), {typed, _, NewBody, NewType} = check_expr(Env, Body, Type1),
{typed, As, NewBody, NewType}; {typed, As, NewBody, NewType};
infer_expr(Env, {app, Ann, Fun, Args0} = App) -> infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ], {NamedArgs, Args} = split_args(Args0),
Args = Args0 -- NamedArgs,
case aeso_syntax:get_ann(format, Ann) of case aeso_syntax:get_ann(format, Ann) of
infix -> infix ->
infer_op(Env, Ann, Fun, Args, fun infer_infix/1); infer_op(Env, Ann, Fun, Args, fun infer_infix/1);
@ -1418,13 +1506,13 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
_ -> _ ->
NamedArgsVar = fresh_uvar(Ann), NamedArgsVar = fresh_uvar(Ann),
NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ], NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ],
%% TODO: named args constraints NewFun0 = infer_expr(Env, Fun),
NewFun={typed, _, _, FunType} = infer_expr(Env, Fun),
NewArgs = [infer_expr(Env, A) || A <- Args], NewArgs = [infer_expr(Env, A) || A <- Args],
ArgTypes = [T || {typed, _, _, T} <- NewArgs], ArgTypes = [T || {typed, _, _, T} <- NewArgs],
NewFun1 = {typed, _, _, FunType} = infer_var_args_fun(Env, NewFun0, NamedArgs1, ArgTypes),
When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes},
GeneralResultType = fresh_uvar(Ann), GeneralResultType = fresh_uvar(Ann),
ResultType = fresh_uvar(Ann), ResultType = fresh_uvar(Ann),
When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes},
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When), unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When),
add_named_argument_constraint( add_named_argument_constraint(
#dependent_type_constraint{ named_args_t = NamedArgsVar, #dependent_type_constraint{ named_args_t = NamedArgsVar,
@ -1432,7 +1520,7 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
general_type = GeneralResultType, general_type = GeneralResultType,
specialized_type = ResultType, specialized_type = ResultType,
context = {check_return, App} }), context = {check_return, App} }),
{typed, Ann, {app, Ann, NewFun, NamedArgs1 ++ NewArgs}, dereference(ResultType)} {typed, Ann, {app, Ann, NewFun1, NamedArgs1 ++ NewArgs}, dereference(ResultType)}
end; end;
infer_expr(Env, {'if', Attrs, Cond, Then, Else}) -> infer_expr(Env, {'if', Attrs, Cond, Then, Else}) ->
NewCond = check_expr(Env, Cond, {id, Attrs, "bool"}), NewCond = check_expr(Env, Cond, {id, Attrs, "bool"}),
@ -1537,6 +1625,62 @@ infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) ->
type_error({missing_body_for_let, Attrs}), type_error({missing_body_for_let, Attrs}),
infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}). infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}).
infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) ->
FunType =
case Fun of
{qid, _, ["Chain", "create"]} ->
{fun_t, _, NamedArgsT, var_args, RetT} = FunType0,
GasCapMock = {named_arg_t, Ann, {id, Ann, "gas"}, {id, Ann, "int"}, {int, Ann, 0}},
ProtectedMock = {named_arg_t, Ann, {id, Ann, "protected"}, {id, Ann, "bool"}, {bool, Ann, false}},
NamedArgsT1 = case NamedArgsT of
[Value|Rest] -> [GasCapMock, Value, ProtectedMock|Rest];
% generally type error, but will be caught
_ -> [GasCapMock, ProtectedMock|NamedArgsT]
end,
check_contract_construction(Env, true, RetT, Fun, NamedArgsT1, ArgTypes, RetT),
{fun_t, Ann, NamedArgsT, ArgTypes, RetT};
{qid, _, ["Chain", "clone"]} ->
{fun_t, _, NamedArgsT, var_args, RetT} = FunType0,
ContractT =
case [ContractT || {named_arg, _, {id, _, "ref"}, {typed, _, _, ContractT}} <- NamedArgs] of
[C] -> C;
_ -> type_error({clone_no_contract, Ann}),
fresh_uvar(Ann)
end,
NamedArgsTNoRef =
lists:filter(fun({named_arg_t, _, {id, _, "ref"}, _, _}) -> false; (_) -> true end, NamedArgsT),
check_contract_construction(Env, false, ContractT, Fun, NamedArgsTNoRef, ArgTypes, RetT),
{fun_t, Ann, NamedArgsT, ArgTypes,
{if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}};
_ -> FunType0
end,
{typed, Ann, Fun, FunType}.
-spec check_contract_construction(env(), boolean(), utype(), utype(), named_args_t(), [utype()], utype()) -> ok.
check_contract_construction(Env, ForceDef, ContractT, Fun, NamedArgsT, ArgTypes, RetT) ->
Ann = aeso_syntax:get_ann(Fun),
InitT = fresh_uvar(Ann),
unify(Env, InitT, {fun_t, Ann, NamedArgsT, ArgTypes, fresh_uvar(Ann)}, {checking_init_args, Ann, ContractT, ArgTypes}),
unify(Env, RetT, ContractT, {return_contract, Fun, ContractT}),
constrain(
[ #field_constraint{
record_t = unfold_types_in_type(Env, ContractT),
field = {id, Ann, ?CONSTRUCTOR_MOCK_NAME},
field_t = InitT,
kind = project,
context = {var_args, Ann, Fun} }
, #is_contract_constraint{ contract_t = ContractT,
context = {var_args, Ann, Fun},
force_def = ForceDef
}
]),
ok.
split_args(Args0) ->
NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ],
Args = Args0 -- NamedArgs,
{NamedArgs, Args}.
infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) -> infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) ->
CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E), CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E),
check_stateful_named_arg(Env, Id, E), check_stateful_named_arg(Env, Id, E),
@ -1704,7 +1848,7 @@ next_count() ->
ets_tables() -> ets_tables() ->
[options, type_vars, type_defs, record_fields, named_argument_constraints, [options, type_vars, type_defs, record_fields, named_argument_constraints,
field_constraints, freshen_tvars, type_errors]. field_constraints, freshen_tvars, type_errors, defined_contracts].
clean_up_ets() -> clean_up_ets() ->
[ catch ets_delete(Tab) || Tab <- ets_tables() ], [ catch ets_delete(Tab) || Tab <- ets_tables() ],
@ -1814,7 +1958,7 @@ solve_named_argument_constraints(Env, Constraints0) ->
[ C || C <- dereference_deep(Constraints0), [ C || C <- dereference_deep(Constraints0),
unsolved == check_named_argument_constraint(Env, C) ]. unsolved == check_named_argument_constraint(Env, C) ].
%% If false, a type error have been emitted, so it's safe to drop the constraint. %% If false, a type error has been emitted, so it's safe to drop the constraint.
-spec check_named_argument_constraint(env(), named_argument_constraint()) -> true | false | unsolved. -spec check_named_argument_constraint(env(), named_argument_constraint()) -> true | false | unsolved.
check_named_argument_constraint(_Env, #named_argument_constraint{ args = {uvar, _, _} }) -> check_named_argument_constraint(_Env, #named_argument_constraint{ args = {uvar, _, _} }) ->
unsolved; unsolved;
@ -1978,12 +2122,20 @@ check_record_create_constraints(Env, [C | Cs]) ->
end, end,
check_record_create_constraints(Env, Cs). check_record_create_constraints(Env, Cs).
is_contract_defined(C) ->
ets_lookup(defined_contracts, qname(C)) =/= [].
check_is_contract_constraints(_Env, []) -> ok; check_is_contract_constraints(_Env, []) -> ok;
check_is_contract_constraints(Env, [C | Cs]) -> check_is_contract_constraints(Env, [C | Cs]) ->
#is_contract_constraint{ contract_t = Type, context = Cxt } = C, #is_contract_constraint{ contract_t = Type, context = Cxt, force_def = ForceDef } = C,
Type1 = unfold_types_in_type(Env, instantiate(Type)), Type1 = unfold_types_in_type(Env, instantiate(Type)),
case lookup_type(Env, record_type_name(Type1)) of TypeName = record_type_name(Type1),
{_, {_Ann, {[], {contract_t, _}}}} -> ok; case lookup_type(Env, TypeName) of
{_, {_Ann, {[], {contract_t, _}}}} ->
case not ForceDef orelse is_contract_defined(TypeName) of
true -> ok;
false -> type_error({contract_lacks_definition, Type1, Cxt})
end;
_ -> type_error({not_a_contract_type, Type1, Cxt}) _ -> type_error({not_a_contract_type, Type1, Cxt})
end, end,
check_is_contract_constraints(Env, Cs). check_is_contract_constraints(Env, Cs).
@ -2309,6 +2461,11 @@ unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) ->
unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, Else2}, When) -> unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, Else2}, When) ->
unify(Env, Then1, Then2, When) andalso unify(Env, Then1, Then2, When) andalso
unify(Env, Else1, Else2, When); unify(Env, Else1, Else2, When);
unify1(_Env, {fun_t, _, _, _, _}, {fun_t, _, _, var_args, _}, When) ->
type_error({unify_varargs, When});
unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, When) ->
type_error({unify_varargs, When});
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When)
when length(Args1) == length(Args2) -> when length(Args1) == length(Args2) ->
unify(Env, Named1, Named2, When) andalso unify(Env, Named1, Named2, When) andalso
@ -2319,6 +2476,9 @@ unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When
unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, When) unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, When)
when length(As) == length(Bs) -> when length(As) == length(Bs) ->
unify(Env, As, Bs, When); unify(Env, As, Bs, When);
unify1(Env, {named_arg_t, _, Id1, Type1, _}, {named_arg_t, _, Id2, Type2, _}, When) ->
unify1(Env, Id1, Id2, {arg_name, Id1, Id2, When}),
unify1(Env, Type1, Type2, When);
%% The grammar is a bit inconsistent about whether types without %% The grammar is a bit inconsistent about whether types without
%% arguments are represented as applications to an empty list of %% arguments are represented as applications to an empty list of
%% parameters or not. We therefore allow them to unify. %% parameters or not. We therefore allow them to unify.
@ -2376,8 +2536,6 @@ occurs_check1(R, [H | T]) ->
occurs_check(R, H) orelse occurs_check(R, T); occurs_check(R, H) orelse occurs_check(R, T);
occurs_check1(_, []) -> false. occurs_check1(_, []) -> false.
fresh_uvar([{origin, system}]) ->
error(oh_no_you_dont);
fresh_uvar(Attrs) -> fresh_uvar(Attrs) ->
{uvar, Attrs, make_ref()}. {uvar, Attrs, make_ref()}.
@ -2426,7 +2584,11 @@ apply_typesig_constraint(Ann, address_to_contract, {fun_t, _, [], [_], Type}) ->
apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) -> apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) ->
add_bytes_constraint({add_bytes, Ann, concat, A, B, C}); add_bytes_constraint({add_bytes, Ann, concat, A, B, C});
apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) -> apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) ->
add_bytes_constraint({add_bytes, Ann, split, A, B, C}). add_bytes_constraint({add_bytes, Ann, split, A, B, C});
apply_typesig_constraint(Ann, bytecode_hash, {fun_t, _, _, [Con], _}) ->
constrain([#is_contract_constraint{ contract_t = Con,
context = {bytecode_hash, Ann} }]).
%% Dereferences all uvars and replaces the uninstantiated ones with a %% Dereferences all uvars and replaces the uninstantiated ones with a
%% succession of tvars. %% succession of tvars.
@ -2555,6 +2717,9 @@ mk_error({not_a_contract_type, Type, Cxt}) ->
end, end,
{Pos, Cxt1} = {Pos, Cxt1} =
case Cxt of case Cxt of
{var_args, Ann, Fun} ->
{pos(Ann),
io_lib:format("when calling variadic function\n~s\n", [pp_expr(" ", Fun)])};
{contract_literal, Lit} -> {contract_literal, Lit} ->
{pos(Lit), {pos(Lit),
io_lib:format("when checking that the contract literal\n~s\n" io_lib:format("when checking that the contract literal\n~s\n"
@ -2653,7 +2818,7 @@ mk_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
Msg = io_lib:format("Nested namespaces are not allowed\nNamespace '~s' at ~s not defined at top level.\n", Msg = io_lib:format("Nested namespaces are not allowed\nNamespace '~s' at ~s not defined at top level.\n",
[Name, pp_loc(Pos)]), [Name, pp_loc(Pos)]),
mk_t_err(pos(Pos), Msg); mk_t_err(pos(Pos), Msg);
mk_error({contract, _Pos, {con, Pos, Name}, _Def}) -> mk_error({Contract, _Pos, {con, Pos, Name}, _Def}) when ?IS_CONTRACT_HEAD(Contract) ->
Msg = io_lib:format("Nested contracts are not allowed\nContract '~s' at ~s not defined at top level.\n", Msg = io_lib:format("Nested contracts are not allowed\nContract '~s' at ~s not defined at top level.\n",
[Name, pp_loc(Pos)]), [Name, pp_loc(Pos)]),
mk_t_err(pos(Pos), Msg); mk_t_err(pos(Pos), Msg);
@ -2728,8 +2893,8 @@ mk_error({contract_has_no_entrypoints, Con}) ->
"contract functions must be declared with the 'entrypoint' keyword instead of\n" "contract functions must be declared with the 'entrypoint' keyword instead of\n"
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]), "'function'.\n", [pp_expr("", Con), pp_loc(Con)]),
mk_t_err(pos(Con), Msg); mk_t_err(pos(Con), Msg);
mk_error({definition_in_non_main_contract, Ann, {id, _, Id}}) -> mk_error({definition_in_contract_interface, Ann, {id, _, Id}}) ->
Msg = "Only the main contract can contain defined functions or entrypoints.\n", Msg = "Contract interfaces cannot contain defined functions or entrypoints.\n",
Cxt = io_lib:format("Fix: replace the definition of '~s' by a type signature.\n", [Id]), Cxt = io_lib:format("Fix: replace the definition of '~s' by a type signature.\n", [Id]),
mk_t_err(pos(Ann), Msg, Cxt); mk_t_err(pos(Ann), Msg, Cxt);
mk_error({unbound_type, Type}) -> mk_error({unbound_type, Type}) ->
@ -2798,6 +2963,29 @@ mk_error({named_argument_must_be_literal_bool, Name, Arg}) ->
mk_error({conflicting_updates_for_field, Upd, Key}) -> mk_error({conflicting_updates_for_field, Upd, Key}) ->
Msg = io_lib:format("Conflicting updates for field '~s'\n", [Key]), Msg = io_lib:format("Conflicting updates for field '~s'\n", [Key]),
mk_t_err(pos(Upd), Msg); mk_t_err(pos(Upd), Msg);
mk_error({ambiguous_main_contract, Ann}) ->
Msg = "Could not deduce the main contract. You can point it out manually with the `main` keyword.",
mk_t_err(pos(Ann), Msg);
mk_error({main_contract_undefined, Ann}) ->
Msg = "No contract defined.\n",
mk_t_err(pos(Ann), Msg);
mk_error({multiple_main_contracts, Ann}) ->
Msg = "Only one main contract can be defined.\n",
mk_t_err(pos(Ann), Msg);
mk_error({unify_varargs, When}) ->
Msg = "Cannot unify variable argument list.\n",
{Pos, Ctxt} = pp_when(When),
mk_t_err(Pos, Msg, Ctxt);
mk_error({clone_no_contract, Ann}) ->
Msg = "Chain.clone requires `ref` named argument of contract type.\n",
mk_t_err(pos(Ann), Msg);
mk_error({contract_lacks_definition, Type, When}) ->
Msg = io_lib:format(
"~s is not implemented.\n",
[pp_type(Type)]
),
{Pos, Ctxt} = pp_when(When),
mk_t_err(Pos, Msg, Ctxt);
mk_error(Err) -> mk_error(Err) ->
Msg = io_lib:format("Unknown error: ~p\n", [Err]), Msg = io_lib:format("Unknown error: ~p\n", [Err]),
mk_t_err(pos(0, 0), Msg). mk_t_err(pos(0, 0), Msg).
@ -2833,6 +3021,12 @@ pp_when({field_constraint, FieldType0, InferredType0, Fld}) ->
InferredType = instantiate(InferredType0), InferredType = instantiate(InferredType0),
{pos(Fld), {pos(Fld),
case Fld of case Fld of
{var_args, _Ann, _Fun} ->
io_lib:format("when checking contract construction of type\n~s (at ~s)\nagainst the expected type\n~s\n",
[pp_type(" ", FieldType),
pp_loc(Fld),
pp_type(" ", InferredType)
]);
{field, _Ann, LV, Id, E} -> {field, _Ann, LV, Id, E} ->
io_lib:format("when checking the assignment of the field\n~s (at ~s)\nto the old value ~s and the new value\n~s\n", io_lib:format("when checking the assignment of the field\n~s (at ~s)\nto the old value ~s and the new value\n~s\n",
[pp_typed(" ", {lvalue, [], LV}, FieldType), [pp_typed(" ", {lvalue, [], LV}, FieldType),
@ -2855,6 +3049,13 @@ pp_when({record_constraint, RecType0, InferredType0, Fld}) ->
InferredType = instantiate(InferredType0), InferredType = instantiate(InferredType0),
{Pos, WhyRec} = pp_why_record(Fld), {Pos, WhyRec} = pp_why_record(Fld),
case Fld of case Fld of
{var_args, _Ann, _Fun} ->
{Pos,
io_lib:format("when checking that contract construction of type\n~s\n~s\n"
"matches the expected type\n~s\n",
[pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)]
)
};
{field, _Ann, _LV, _Id, _E} -> {field, _Ann, _LV, _Id, _E} ->
{Pos, {Pos,
io_lib:format("when checking that the record type\n~s\n~s\n" io_lib:format("when checking that the record type\n~s\n~s\n"
@ -2908,17 +3109,42 @@ pp_when({check_named_arg_constraint, C}) ->
Err = io_lib:format("when checking named argument\n~s\nagainst inferred type\n~s", Err = io_lib:format("when checking named argument\n~s\nagainst inferred type\n~s",
[pp_typed(" ", Arg, Type), pp_type(" ", C#named_argument_constraint.type)]), [pp_typed(" ", Arg, Type), pp_type(" ", C#named_argument_constraint.type)]),
{pos(Arg), Err}; {pos(Arg), Err};
pp_when({checking_init_args, Ann, Con0, ArgTypes0}) ->
Con = instantiate(Con0),
ArgTypes = instantiate(ArgTypes0),
{pos(Ann),
io_lib:format("when checking arguments of ~s's init entrypoint to match\n(~s)",
[pp_type(Con), string:join([pp_type(A) || A <- ArgTypes], ", ")])
};
pp_when({return_contract, App, Con0}) ->
Con = instantiate(Con0),
{pos(App)
, io_lib:format("when checking that expression returns contract of type\n~s", [pp_type(" ", Con)])
};
pp_when({arg_name, Id1, Id2, When}) ->
{Pos, Ctx} = pp_when(When),
{Pos
, io_lib:format("when unifying names of named arguments: ~s and ~s\n~s", [pp_expr(Id1), pp_expr(Id2), Ctx])
};
pp_when({var_args, Ann, Fun}) ->
{pos(Ann)
, io_lib:format("when resolving arguments of variadic function\n~s\n", [pp_expr(" ", Fun)])
};
pp_when(unknown) -> {pos(0,0), ""}. pp_when(unknown) -> {pos(0,0), ""}.
-spec pp_why_record(why_record()) -> {pos(), iolist()}. -spec pp_why_record(why_record()) -> {pos(), iolist()}.
pp_why_record(Fld = {field, _Ann, LV, _Id, _E}) -> pp_why_record({var_args, Ann, Fun}) ->
{pos(Fld), {pos(Ann),
io_lib:format("arising from an assignment of the field ~s (at ~s)", io_lib:format("arising from resolution of variadic function ~s (at ~s)",
[pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])}; [pp_expr(Fun), pp_loc(Fun)])};
pp_why_record(Fld = {field, _Ann, LV, _E}) -> pp_why_record(Fld = {field, _Ann, LV, _E}) ->
{pos(Fld), {pos(Fld),
io_lib:format("arising from an assignment of the field ~s (at ~s)", io_lib:format("arising from an assignment of the field ~s (at ~s)",
[pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])}; [pp_expr({lvalue, [], LV}), pp_loc(Fld)])};
pp_why_record(Fld = {field, _Ann, LV, _Alias, _E}) ->
{pos(Fld),
io_lib:format("arising from an assignment of the field ~s (at ~s)",
[pp_expr({lvalue, [], LV}), pp_loc(Fld)])};
pp_why_record({proj, _Ann, Rec, FldName}) -> pp_why_record({proj, _Ann, Rec, FldName}) ->
{pos(Rec), {pos(Rec),
io_lib:format("arising from the projection of the field ~s (at ~s)", io_lib:format("arising from the projection of the field ~s (at ~s)",
@ -2938,9 +3164,13 @@ pp_typed(Label, {typed, _, Expr, _}, Type) ->
pp_typed(Label, Expr, Type) -> pp_typed(Label, Expr, Type) ->
pp_expr(Label, {typed, [], Expr, Type}). pp_expr(Label, {typed, [], Expr, Type}).
pp_expr(Expr) ->
pp_expr("", Expr).
pp_expr(Label, Expr) -> pp_expr(Label, Expr) ->
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:expr(Expr, [show_generated]))). prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:expr(Expr, [show_generated]))).
pp_type(Type) ->
pp_type("", Type).
pp_type(Label, Type) -> pp_type(Label, Type) ->
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated]))). prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated]))).

View File

@ -12,6 +12,8 @@
-export([ast_to_fcode/2, format_fexpr/1]). -export([ast_to_fcode/2, format_fexpr/1]).
-export_type([fcode/0, fexpr/0, fun_def/0]). -export_type([fcode/0, fexpr/0, fun_def/0]).
-include("aeso_utils.hrl").
%% -- Type definitions ------------------------------------------------------- %% -- Type definitions -------------------------------------------------------
-type option() :: term(). -type option() :: term().
@ -53,6 +55,7 @@
| {oracle_pubkey, binary()} | {oracle_pubkey, binary()}
| {oracle_query_id, binary()} | {oracle_query_id, binary()}
| {bool, false | true} | {bool, false | true}
| {contract_code, string()} %% for CREATE, by name
| {typerep, ftype()}. | {typerep, ftype()}.
-type fexpr() :: {lit, flit()} -type fexpr() :: {lit, flit()}
@ -136,9 +139,10 @@
-type type_env() :: #{ sophia_name() => type_def() }. -type type_env() :: #{ sophia_name() => type_def() }.
-type fun_env() :: #{ sophia_name() => {fun_name(), non_neg_integer()} }. -type fun_env() :: #{ sophia_name() => {fun_name(), non_neg_integer()} }.
-type con_env() :: #{ sophia_name() => con_tag() }. -type con_env() :: #{ sophia_name() => con_tag() }.
-type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none} }. -type child_con_env() :: #{sophia_name() => fcode()}.
-type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none | variable} }.
-type context() :: {main_contract, string()} -type context() :: {contract_def, string()}
| {namespace, string()} | {namespace, string()}
| {abstract_contract, string()}. | {abstract_contract, string()}.
@ -147,13 +151,15 @@
-type env() :: #{ type_env := type_env(), -type env() :: #{ type_env := type_env(),
fun_env := fun_env(), fun_env := fun_env(),
con_env := con_env(), con_env := con_env(),
child_con_env := child_con_env(),
event_type => aeso_syntax:typedef(), event_type => aeso_syntax:typedef(),
builtins := builtins(), builtins := builtins(),
options := [option()], options := [option()],
state_layout => state_layout(), state_layout => state_layout(),
context => context(), context => context(),
vars => [var_name()], vars => [var_name()],
functions := #{ fun_name() => fun_def() } }. functions := #{ fun_name() => fun_def() }
}.
-define(HASH_BYTES, 32). -define(HASH_BYTES, 32).
@ -161,17 +167,26 @@
%% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2 %% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2
%% and produces Fate intermediate code. %% and produces Fate intermediate code.
-spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> fcode(). -spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> {env(), fcode()}.
ast_to_fcode(Code, Options) -> ast_to_fcode(Code, Options) ->
Verbose = lists:member(pp_fcode, Options),
init_fresh_names(), init_fresh_names(),
FCode1 = to_fcode(init_env(Options), Code), {Env1, FCode1} = to_fcode(init_env(Options), Code),
FCode2 = optimize(FCode1, Options),
Env2 = Env1#{ child_con_env :=
maps:map(
fun (_, FC) -> optimize(FC, Options) end,
maps:get(child_con_env, Env1)
)},
clear_fresh_names(),
{Env2, FCode2}.
optimize(FCode1, Options) ->
Verbose = lists:member(pp_fcode, Options),
[io:format("-- Before lambda lifting --\n~s\n\n", [format_fcode(FCode1)]) || Verbose], [io:format("-- Before lambda lifting --\n~s\n\n", [format_fcode(FCode1)]) || Verbose],
FCode2 = optimize_fcode(FCode1), FCode2 = optimize_fcode(FCode1),
[ io:format("-- After optimization --\n~s\n\n", [format_fcode(FCode2)]) || Verbose, FCode2 /= FCode1 ], [ io:format("-- After optimization --\n~s\n\n", [format_fcode(FCode2)]) || Verbose, FCode2 /= FCode1 ],
FCode3 = lambda_lift(FCode2), FCode3 = lambda_lift(FCode2),
[ io:format("-- After lambda lifting --\n~s\n\n", [format_fcode(FCode3)]) || Verbose, FCode3 /= FCode2 ], [ io:format("-- After lambda lifting --\n~s\n\n", [format_fcode(FCode3)]) || Verbose, FCode3 /= FCode2 ],
clear_fresh_names(),
FCode3. FCode3.
%% -- Environment ------------------------------------------------------------ %% -- Environment ------------------------------------------------------------
@ -182,6 +197,7 @@ init_env(Options) ->
#{ type_env => init_type_env(), #{ type_env => init_type_env(),
fun_env => #{}, fun_env => #{},
builtins => builtins(), builtins => builtins(),
child_con_env => #{},
con_env => #{["None"] => #con_tag{ tag = 0, arities = [0, 1] }, con_env => #{["None"] => #con_tag{ tag = 0, arities = [0, 1] },
["Some"] => #con_tag{ tag = 1, arities = [0, 1] }, ["Some"] => #con_tag{ tag = 1, arities = [0, 1] },
["RelativeTTL"] => #con_tag{ tag = 0, arities = [1, 1] }, ["RelativeTTL"] => #con_tag{ tag = 0, arities = [1, 1] },
@ -217,7 +233,8 @@ init_env(Options) ->
["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities } ["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities }
}, },
options => Options, options => Options,
functions => #{} }. functions => #{}
}.
-spec builtins() -> builtins(). -spec builtins() -> builtins().
builtins() -> builtins() ->
@ -227,7 +244,7 @@ builtins() ->
Scopes = [{[], [{"abort", 1}, {"require", 2}]}, Scopes = [{[], [{"abort", 1}, {"require", 2}]},
{["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none}, {["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none},
{"timestamp", none}, {"block_height", none}, {"difficulty", none}, {"timestamp", none}, {"block_height", none}, {"difficulty", none},
{"gas_limit", none}]}, {"gas_limit", none}, {"bytecode_hash", 1}, {"create", variable}, {"clone", variable}]},
{["Contract"], [{"address", none}, {"balance", none}, {"creator", none}]}, {["Contract"], [{"address", none}, {"balance", none}, {"creator", none}]},
{["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none}, {["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none},
{"gas_left", 0}]}, {"gas_left", 0}]},
@ -307,31 +324,44 @@ get_option(Opt, Env, Default) ->
%% -- Compilation ------------------------------------------------------------ %% -- Compilation ------------------------------------------------------------
-spec to_fcode(env(), aeso_syntax:ast()) -> fcode(). -spec to_fcode(env(), aeso_syntax:ast()) -> {env(), fcode()}.
to_fcode(Env, [{contract, Attrs, MainCon = {con, _, Main}, Decls}]) -> to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest])
when ?IS_CONTRACT_HEAD(Contract) ->
case Contract =:= contract_interface of
false ->
#{ builtins := Builtins } = Env, #{ builtins := Builtins } = Env,
MainEnv = Env#{ context => {main_contract, Main}, ConEnv = Env#{ context => {contract_def, Name},
builtins => Builtins#{[Main, "state"] => {get_state, none}, builtins => Builtins#{[Name, "state"] => {get_state, none},
[Main, "put"] => {set_state, 1}, [Name, "put"] => {set_state, 1},
[Main, "Chain", "event"] => {chain_event, 1}} }, [Name, "Chain", "event"] => {chain_event, 1}} },
#{ functions := PrevFuns } = ConEnv,
#{ functions := Funs } = Env1 = #{ functions := Funs } = Env1 =
decls_to_fcode(MainEnv, Decls), decls_to_fcode(ConEnv, Decls),
StateType = lookup_type(Env1, [Main, "state"], [], {tuple, []}), StateType = lookup_type(Env1, [Name, "state"], [], {tuple, []}),
EventType = lookup_type(Env1, [Main, "event"], [], none), EventType = lookup_type(Env1, [Name, "event"], [], none),
StateLayout = state_layout(Env1), StateLayout = state_layout(Env1),
Payable = proplists:get_value(payable, Attrs, false), Payable = proplists:get_value(payable, Attrs, false),
#{ contract_name => Main, ConFcode = #{ contract_name => Name,
state_type => StateType, state_type => StateType,
state_layout => StateLayout, state_layout => StateLayout,
event_type => EventType, event_type => EventType,
payable => Payable, payable => Payable,
functions => add_init_function(Env1, MainCon, StateType, functions => add_init_function(
add_event_function(Env1, EventType, Funs)) }; Env1, Con, StateType,
to_fcode(_Env, [NotContract]) -> add_event_function(Env1, EventType, Funs)) },
fcode_error({last_declaration_must_be_contract, NotContract}); case Contract of
to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) -> contract_main -> [] = Rest, {Env1, ConFcode};
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls), contract_child ->
to_fcode(Env1, Code); Env2 = add_child_con(Env1, Name, ConFcode),
Env3 = Env2#{ functions := PrevFuns },
to_fcode(Env3, Rest)
end;
true ->
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Name} }, Decls),
to_fcode(Env1, Rest)
end;
to_fcode(_Env, [NotMain = {NotMainHead, _ ,_ , _}]) when NotMainHead =/= contract_def ->
fcode_error({last_declaration_must_be_contract_def, NotMain});
to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) -> to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) ->
Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls), Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls),
to_fcode(Env1, Code). to_fcode(Env1, Code).
@ -341,13 +371,11 @@ decls_to_fcode(Env, Decls) ->
%% First compute mapping from Sophia names to fun_names and add it to the %% First compute mapping from Sophia names to fun_names and add it to the
%% environment. %% environment.
Env1 = add_fun_env(Env, Decls), Env1 = add_fun_env(Env, Decls),
lists:foldl(fun(D, E) -> lists:foldl(fun(D, E) -> decl_to_fcode(E, D)
R = decl_to_fcode(E, D),
R
end, Env1, Decls). end, Env1, Decls).
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env(). -spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, _, Id, _}) -> decl_to_fcode(Env = #{context := {contract_def, _}}, {fun_decl, _, Id, _}) ->
case is_no_code(Env) of case is_no_code(Env) of
false -> fcode_error({missing_definition, Id}); false -> fcode_error({missing_definition, Id});
true -> Env true -> Env
@ -410,7 +438,7 @@ typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) ->
Env3 = compute_state_layout(Env2, Name, FDef), Env3 = compute_state_layout(Env2, Name, FDef),
bind_type(Env3, Q, FDef). bind_type(Env3, Q, FDef).
compute_state_layout(Env = #{ context := {main_contract, _} }, "state", Type) -> compute_state_layout(Env = #{ context := {contract_def, _} }, "state", Type) ->
NoLayout = get_option(no_flatten_state, Env), NoLayout = get_option(no_flatten_state, Env),
Layout = Layout =
case Type([]) of case Type([]) of
@ -436,7 +464,7 @@ compute_state_layout(R, [H | T]) ->
compute_state_layout(R, _) -> compute_state_layout(R, _) ->
{R + 1, {reg, R}}. {R + 1, {reg, R}}.
check_state_and_event_types(#{ context := {main_contract, _} }, Id, [_ | _]) -> check_state_and_event_types(#{ context := {contract_def, _} }, Id, [_ | _]) ->
case Id of case Id of
{id, _, "state"} -> fcode_error({parameterized_state, Id}); {id, _, "state"} -> fcode_error({parameterized_state, Id});
{id, _, "event"} -> fcode_error({parameterized_event, Id}); {id, _, "event"} -> fcode_error({parameterized_event, Id});
@ -461,8 +489,12 @@ type_to_fcode(Env, Sub, {record_t, Fields}) ->
type_to_fcode(Env, Sub, {tuple_t, [], lists:map(FieldType, Fields)}); type_to_fcode(Env, Sub, {tuple_t, [], lists:map(FieldType, Fields)});
type_to_fcode(_Env, _Sub, {bytes_t, _, N}) -> type_to_fcode(_Env, _Sub, {bytes_t, _, N}) ->
{bytes, N}; {bytes, N};
type_to_fcode(_Env, _Sub, {tvar, Ann, "void"}) ->
fcode_error({found_void, Ann});
type_to_fcode(_Env, Sub, {tvar, _, X}) -> type_to_fcode(_Env, Sub, {tvar, _, X}) ->
maps:get(X, Sub, {tvar, X}); maps:get(X, Sub, {tvar, X});
type_to_fcode(_Env, _Sub, {fun_t, Ann, _, var_args, _}) ->
fcode_error({var_args_not_set, {id, Ann, "a very suspicious function"}});
type_to_fcode(Env, Sub, {fun_t, _, Named, Args, Res}) -> type_to_fcode(Env, Sub, {fun_t, _, Named, Args, Res}) ->
FNamed = [type_to_fcode(Env, Sub, Arg) || {named_arg_t, _, _, Arg, _} <- Named], FNamed = [type_to_fcode(Env, Sub, Arg) || {named_arg_t, _, _, Arg, _} <- Named],
FArgs = [type_to_fcode(Env, Sub, Arg) || Arg <- Args], FArgs = [type_to_fcode(Env, Sub, Arg) || Arg <- Args],
@ -678,14 +710,31 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
end; end;
%% Function calls %% Function calls
expr_to_fcode(Env, _Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) -> expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, _}}, Args}) ->
Args1 = get_named_args(NamedArgsT, Args), Args1 = get_named_args(NamedArgsT, Args),
FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1], FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1],
case expr_to_fcode(Env, Fun) of case expr_to_fcode(Env, Fun) of
{builtin_u, B, _Ar, TypeArgs} -> builtin_to_fcode(state_layout(Env), B, FArgs ++ TypeArgs); {builtin_u, B, _Ar, TypeArgs} -> builtin_to_fcode(state_layout(Env), B, FArgs ++ TypeArgs);
{builtin_u, chain_clone, _Ar} ->
case ArgsT of
var_args -> fcode_error({var_args_not_set, FunE});
_ ->
%% Here we little cheat on the typechecker, but this inconsistency
%% is to be solved in `aeso_fcode_to_fate:type_to_scode/1`
FInitArgsT = aeb_fate_data:make_typerep([type_to_fcode(Env, T) || T <- ArgsT]),
builtin_to_fcode(state_layout(Env), chain_clone, [{lit, FInitArgsT}|FArgs])
end;
{builtin_u, chain_create, _Ar} ->
case {ArgsT, Type} of
{var_args, _} -> fcode_error({var_args_not_set, FunE});
{_, {con, _, Contract}} ->
FInitArgsT = aeb_fate_data:make_typerep([type_to_fcode(Env, T) || T <- ArgsT]),
builtin_to_fcode(state_layout(Env), chain_create, [{lit, {contract_code, Contract}}, {lit, FInitArgsT}|FArgs]);
{_, _} -> fcode_error({not_a_contract_type, Type})
end;
{builtin_u, B, _Ar} -> builtin_to_fcode(state_layout(Env), B, FArgs); {builtin_u, B, _Ar} -> builtin_to_fcode(state_layout(Env), B, FArgs);
{def_u, F, _Ar} -> {def, F, FArgs}; {def_u, F, _Ar} -> {def, F, FArgs};
{remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs}; {remote_u, RArgsT, RRetT, Ct, RFun} -> {remote, RArgsT, RRetT, Ct, RFun, FArgs};
FFun -> FFun ->
%% FFun is a closure, with first component the function name and %% FFun is a closure, with first component the function name and
%% second component the environment %% second component the environment
@ -1614,6 +1663,10 @@ bind_constructors(Env = #{ con_env := ConEnv }, NewCons) ->
%% -- Names -- %% -- Names --
-spec add_child_con(env(), sophia_name(), fcode()) -> env().
add_child_con(Env = #{child_con_env := CEnv}, Name, Fcode) ->
Env#{ child_con_env := CEnv#{Name => Fcode} }.
-spec add_fun_env(env(), [aeso_syntax:decl()]) -> env(). -spec add_fun_env(env(), [aeso_syntax:decl()]) -> env().
add_fun_env(Env = #{ context := {abstract_contract, _} }, _) -> Env; %% no functions from abstract contracts add_fun_env(Env = #{ context := {abstract_contract, _} }, _) -> Env; %% no functions from abstract contracts
add_fun_env(Env = #{ fun_env := FunEnv }, Decls) -> add_fun_env(Env = #{ fun_env := FunEnv }, Decls) ->
@ -1628,7 +1681,7 @@ add_fun_env(Env = #{ fun_env := FunEnv }, Decls) ->
make_fun_name(#{ context := Context }, Ann, Name) -> make_fun_name(#{ context := Context }, Ann, Name) ->
Entrypoint = proplists:get_value(entrypoint, Ann, false), Entrypoint = proplists:get_value(entrypoint, Ann, false),
case Context of case Context of
{main_contract, Main} -> {contract_def, Main} ->
if Entrypoint -> {entrypoint, list_to_binary(Name)}; if Entrypoint -> {entrypoint, list_to_binary(Name)};
true -> {local_fun, [Main, Name]} true -> {local_fun, [Main, Name]}
end; end;
@ -1640,7 +1693,7 @@ make_fun_name(#{ context := Context }, Ann, Name) ->
current_namespace(#{ context := Cxt }) -> current_namespace(#{ context := Cxt }) ->
case Cxt of case Cxt of
{abstract_contract, Con} -> Con; {abstract_contract, Con} -> Con;
{main_contract, Con} -> Con; {contract_def, Con} -> Con;
{namespace, NS} -> NS {namespace, NS} -> NS
end. end.
@ -1987,8 +2040,11 @@ internal_error(Error) ->
%% -- Pretty printing -------------------------------------------------------- %% -- Pretty printing --------------------------------------------------------
format_fcode(#{ functions := Funs }) -> format_fcode(#{ functions := Funs }) ->
prettypr:format(pp_above( prettypr:format(format_funs(Funs)).
[ pp_fun(Name, Def) || {Name, Def} <- maps:to_list(Funs) ])).
format_funs(Funs) ->
pp_above(
[ pp_fun(Name, Def) || {Name, Def} <- maps:to_list(Funs) ]).
format_fexpr(E) -> format_fexpr(E) ->
prettypr:format(pp_fexpr(E)). prettypr:format(pp_fexpr(E)).
@ -2105,7 +2161,9 @@ pp_fexpr({set_state, R, A}) ->
pp_call(pp_text("set_state"), [{lit, {int, R}}, A]); pp_call(pp_text("set_state"), [{lit, {int, R}}, A]);
pp_fexpr({get_state, R}) -> pp_fexpr({get_state, R}) ->
pp_call(pp_text("get_state"), [{lit, {int, R}}]); pp_call(pp_text("get_state"), [{lit, {int, R}}]);
pp_fexpr({switch, Split}) -> pp_split(Split). pp_fexpr({switch, Split}) -> pp_split(Split);
pp_fexpr({contract_code, Contract}) ->
pp_beside(pp_text("contract "), pp_text(Contract)).
pp_call(Fun, Args) -> pp_call(Fun, Args) ->
pp_beside(Fun, pp_fexpr({tuple, Args})). pp_beside(Fun, pp_fexpr({tuple, Args})).

View File

@ -14,12 +14,13 @@
-include_lib("aebytecode/include/aeb_opcodes.hrl"). -include_lib("aebytecode/include/aeb_opcodes.hrl").
-include("aeso_icode.hrl"). -include("aeso_icode.hrl").
-include("aeso_utils.hrl").
-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) ->
{Payable, Name} = {Payable, Name} =
case lists:last(TypedTree) of case lists:last(TypedTree) of
{contract, Attrs, {con, _, Con}, _} -> {Contr, Attrs, {con, _, Con}, _} when ?IS_CONTRACT_HEAD(Contr) ->
{proplists:get_value(payable, Attrs, false), Con}; {proplists:get_value(payable, Attrs, false), Con};
Decl -> Decl ->
gen_error({last_declaration_must_be_contract, Decl}) gen_error({last_declaration_must_be_contract, Decl})
@ -29,7 +30,8 @@ convert_typed(TypedTree, Options) ->
Icode = code(TypedTree, NewIcode, Options), Icode = code(TypedTree, NewIcode, Options),
deadcode_elimination(Icode). deadcode_elimination(Icode).
code([{contract, _Attribs, Con, Code}|Rest], Icode, Options) -> code([{Contract, _Attribs, Con, Code}|Rest], Icode, Options)
when ?IS_CONTRACT_HEAD(Contract) ->
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)), NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)),
code(Rest, NewIcode, Options); code(Rest, NewIcode, Options);
code([{namespace, _Ann, Name, Code}|Rest], Icode, Options) -> code([{namespace, _Ann, Name, Code}|Rest], Icode, Options) ->

View File

@ -10,9 +10,9 @@
-export([format/1, pos/1]). -export([format/1, pos/1]).
format({last_declaration_must_be_contract, Decl = {namespace, _, {con, _, C}, _}}) -> format({last_declaration_must_be_main_contract, Decl = {Kind, _, {con, _, C}, _}}) ->
Msg = io_lib:format("Expected a contract as the last declaration instead of the namespace '~s'\n", Msg = io_lib:format("Expected a main contract as the last declaration instead of the ~p '~s'\n",
[C]), [Kind, C]),
mk_err(pos(Decl), Msg); mk_err(pos(Decl), Msg);
format({missing_init_function, Con}) -> format({missing_init_function, Con}) ->
Msg = io_lib:format("Missing init function for the contract '~s'.\n", [pp_expr(Con)]), Msg = io_lib:format("Missing init function for the contract '~s'.\n", [pp_expr(Con)]),
@ -87,6 +87,12 @@ format({higher_order_state, {type_def, Ann, _, _, State}}) ->
Msg = io_lib:format("Invalid state type\n~s\n", [pp_type(2, State)]), Msg = io_lib:format("Invalid state type\n~s\n", [pp_type(2, State)]),
Cxt = "The state cannot contain functions in the AEVM. Use FATE if you need this.\n", Cxt = "The state cannot contain functions in the AEVM. Use FATE if you need this.\n",
mk_err(pos(Ann), Msg, Cxt); mk_err(pos(Ann), Msg, Cxt);
format({var_args_not_set, Expr}) ->
mk_err( pos(Expr), "Could not deduce type of variable arguments list"
, "When compiling " ++ pp_expr(Expr)
);
format({found_void, Ann}) ->
mk_err(pos(Ann), "Found a void-typed value.", "`void` is a restricted, uninhabited type. Did you mean `unit`?");
format(Err) -> format(Err) ->
mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])). mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])).

View File

@ -28,6 +28,7 @@
-include_lib("aebytecode/include/aeb_opcodes.hrl"). -include_lib("aebytecode/include/aeb_opcodes.hrl").
-include("aeso_icode.hrl"). -include("aeso_icode.hrl").
-include("aeso_utils.hrl").
-type option() :: pp_sophia_code -type option() :: pp_sophia_code
@ -137,8 +138,9 @@ from_string1(aevm, ContractString, Options) ->
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}; {ok, maybe_generate_aci(Res, FoldedTypedAst, Options)};
from_string1(fate, ContractString, Options) -> from_string1(fate, ContractString, Options) ->
#{ fcode := FCode #{ fcode := FCode
, fcode_env := #{child_con_env := ChildContracts}
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options), , folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
FateCode = aeso_fcode_to_fate:compile(FCode, Options), FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, Options),
pp_assembler(fate, FateCode, Options), pp_assembler(fate, FateCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []), ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(), {ok, Version} = version(),
@ -178,8 +180,9 @@ string_to_code(ContractString, Options) ->
, type_env => TypeEnv , type_env => TypeEnv
, ast => Ast }; , ast => Ast };
fate -> fate ->
Fcode = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, Options), {Env, Fcode} = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, [{original_src, ContractString}|Options]),
#{ fcode => Fcode #{ fcode => Fcode
, fcode_env => Env
, unfolded_typed_ast => UnfoldedTypedAst , unfolded_typed_ast => UnfoldedTypedAst
, folded_typed_ast => FoldedTypedAst , folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv , type_env => TypeEnv
@ -468,7 +471,7 @@ error_missing_call_function() ->
Msg = "Internal error: missing '__call'-function", Msg = "Internal error: missing '__call'-function",
aeso_errors:throw(aeso_errors:new(internal_error, Msg)). aeso_errors:throw(aeso_errors:new(internal_error, Msg)).
get_call_type([{contract, _, _, Defs}]) -> get_call_type([{Contract, _, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
case [ {lists:last(QFunName), FunType} case [ {lists:last(QFunName), FunType}
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret, || {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
{typed, _, {typed, _,
@ -482,7 +485,7 @@ get_call_type([_ | Contracts]) ->
get_call_type(Contracts). get_call_type(Contracts).
-dialyzer({nowarn_function, get_decode_type/2}). -dialyzer({nowarn_function, get_decode_type/2}).
get_decode_type(FunName, [{contract, Ann, _, Defs}]) -> get_decode_type(FunName, [{Contract, Ann, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}]; 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}]; ({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
(_) -> [] end, (_) -> [] end,

View File

@ -9,7 +9,7 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeso_fcode_to_fate). -module(aeso_fcode_to_fate).
-export([compile/2, term_to_fate/1]). -export([compile/2, compile/3, term_to_fate/1, term_to_fate/2]).
-ifdef(TEST). -ifdef(TEST).
-export([optimize_fun/4, to_basic_blocks/1]). -export([optimize_fun/4, to_basic_blocks/1]).
@ -45,7 +45,7 @@
-define(s(N), {store, N}). -define(s(N), {store, N}).
-define(void, {var, 9999}). -define(void, {var, 9999}).
-record(env, { contract, vars = [], locals = [], current_function, tailpos = true }). -record(env, { contract, vars = [], locals = [], current_function, tailpos = true, child_contracts = #{}, options = []}).
%% -- Debugging -------------------------------------------------------------- %% -- Debugging --------------------------------------------------------------
@ -70,9 +70,11 @@ code_error(Err) ->
%% @doc Main entry point. %% @doc Main entry point.
compile(FCode, Options) -> compile(FCode, Options) ->
compile(#{}, FCode, Options).
compile(ChildContracts, FCode, Options) ->
#{ contract_name := ContractName, #{ contract_name := ContractName,
functions := Functions } = FCode, functions := Functions } = FCode,
SFuns = functions_to_scode(ContractName, Functions, Options), SFuns = functions_to_scode(ChildContracts, ContractName, Functions, Options),
SFuns1 = optimize_scode(SFuns, Options), SFuns1 = optimize_scode(SFuns, Options),
FateCode = to_basic_blocks(SFuns1), FateCode = to_basic_blocks(SFuns1),
?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]), ?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]),
@ -85,19 +87,20 @@ make_function_name(event) -> <<"Chain.event">>;
make_function_name({entrypoint, Name}) -> Name; make_function_name({entrypoint, Name}) -> Name;
make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, ".")). make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, ".")).
functions_to_scode(ContractName, Functions, Options) -> functions_to_scode(ChildContracts, ContractName, Functions, Options) ->
FunNames = maps:keys(Functions), FunNames = maps:keys(Functions),
maps:from_list( maps:from_list(
[ {make_function_name(Name), function_to_scode(ContractName, FunNames, Name, Attrs, Args, Body, Type, Options)} [ {make_function_name(Name), function_to_scode(ChildContracts, ContractName, FunNames, Name, Attrs, Args, Body, Type, Options)}
|| {Name, #{args := Args, || {Name, #{args := Args,
body := Body, body := Body,
attrs := Attrs, attrs := Attrs,
return := Type}} <- maps:to_list(Functions)]). return := Type}} <- maps:to_list(Functions)]).
function_to_scode(ContractName, Functions, Name, Attrs0, Args, Body, ResType, _Options) -> function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, Options) ->
{ArgTypes, ResType1} = typesig_to_scode(Args, ResType), {ArgTypes, ResType1} = typesig_to_scode(Args, ResType),
Attrs = Attrs0 -- [stateful], %% Only track private and payable from here. Attrs = Attrs0 -- [stateful], %% Only track private and payable from here.
SCode = to_scode(init_env(ContractName, Functions, Name, Args), Body), Env = init_env(ChildContracts, ContractName, Functions, Name, Args, Options),
SCode = to_scode(Env, Body),
{Attrs, {ArgTypes, ResType1}, SCode}. {Attrs, {ArgTypes, ResType1}, SCode}.
-define(tvars, '$tvars'). -define(tvars, '$tvars').
@ -133,7 +136,9 @@ type_to_scode({tvar, X}) ->
put(?tvars, {I + 1, Vars#{ X => I }}), put(?tvars, {I + 1, Vars#{ X => I }}),
{tvar, I}; {tvar, I};
J -> {tvar, J} J -> {tvar, J}
end. end;
type_to_scode(L) when is_list(L) -> {tuple, types_to_scode(L)}.
types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts). types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
@ -142,11 +147,13 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
%% -- Environment functions -- %% -- Environment functions --
init_env(ContractName, FunNames, Name, Args) -> init_env(ChildContracts, ContractName, FunNames, Name, Args, Options) ->
#env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ], #env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ],
contract = ContractName, contract = ContractName,
child_contracts = ChildContracts,
locals = FunNames, locals = FunNames,
current_function = Name, current_function = Name,
options = Options,
tailpos = true }. tailpos = true }.
next_var(#env{ vars = Vars }) -> next_var(#env{ vars = Vars }) ->
@ -169,7 +176,7 @@ lookup_var(#env{vars = Vars}, X) ->
%% -- The compiler -- %% -- The compiler --
lit_to_fate(L) -> lit_to_fate(Env, L) ->
case L of case L of
{int, N} -> aeb_fate_data:make_integer(N); {int, N} -> aeb_fate_data:make_integer(N);
{string, S} -> aeb_fate_data:make_string(S); {string, S} -> aeb_fate_data:make_string(S);
@ -179,63 +186,80 @@ lit_to_fate(L) ->
{contract_pubkey, K} -> aeb_fate_data:make_contract(K); {contract_pubkey, K} -> aeb_fate_data:make_contract(K);
{oracle_pubkey, K} -> aeb_fate_data:make_oracle(K); {oracle_pubkey, K} -> aeb_fate_data:make_oracle(K);
{oracle_query_id, H} -> aeb_fate_data:make_oracle_query(H); {oracle_query_id, H} -> aeb_fate_data:make_oracle_query(H);
{contract_code, C} ->
Options = Env#env.options,
FCode = maps:get(C, Env#env.child_contracts),
FateCode = compile(Env#env.child_contracts, FCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = aeso_compiler:version(),
OriginalSourceCode = proplists:get_value(original_src, Options, ""),
Code = #{byte_code => ByteCode,
compiler_version => Version,
source_hash => crypto:hash(sha256, OriginalSourceCode ++ [0] ++ C),
type_info => [],
abi_version => aeb_fate_abi:abi_version(),
payable => maps:get(payable, FCode)
},
Serialized = aeser_contract_code:serialize(Code),
aeb_fate_data:make_contract_bytearray(Serialized);
{typerep, T} -> aeb_fate_data:make_typerep(type_to_scode(T)) {typerep, T} -> aeb_fate_data:make_typerep(type_to_scode(T))
end. end.
term_to_fate(E) -> term_to_fate(#{}, E). term_to_fate(E) -> term_to_fate(#env{}, #{}, E).
term_to_fate(GlobEnv, E) -> term_to_fate(GlobEnv, #{}, E).
term_to_fate(_Env, {lit, L}) -> term_to_fate(GlobEnv, _Env, {lit, L}) ->
lit_to_fate(L); lit_to_fate(GlobEnv, L);
%% negative literals are parsed as 0 - N %% negative literals are parsed as 0 - N
term_to_fate(_Env, {op, '-', [{lit, {int, 0}}, {lit, {int, N}}]}) -> term_to_fate(_GlobEnv, _Env, {op, '-', [{lit, {int, 0}}, {lit, {int, N}}]}) ->
aeb_fate_data:make_integer(-N); aeb_fate_data:make_integer(-N);
term_to_fate(_Env, nil) -> term_to_fate(_GlobEnv, _Env, nil) ->
aeb_fate_data:make_list([]); aeb_fate_data:make_list([]);
term_to_fate(Env, {op, '::', [Hd, Tl]}) -> term_to_fate(GlobEnv, Env, {op, '::', [Hd, Tl]}) ->
%% The Tl will translate into a list, because FATE lists are just lists %% The Tl will translate into a list, because FATE lists are just lists
[term_to_fate(Env, Hd) | term_to_fate(Env, Tl)]; [term_to_fate(GlobEnv, Env, Hd) | term_to_fate(GlobEnv, Env, Tl)];
term_to_fate(Env, {tuple, As}) -> term_to_fate(GlobEnv, Env, {tuple, As}) ->
aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(Env, A) || A<-As])); aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(GlobEnv, Env, A) || A<-As]));
term_to_fate(Env, {con, Ar, I, As}) -> term_to_fate(GlobEnv, Env, {con, Ar, I, As}) ->
FateAs = [ term_to_fate(Env, A) || A <- As ], FateAs = [ term_to_fate(GlobEnv, Env, A) || A <- As ],
aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs)); aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs));
term_to_fate(_Env, {builtin, bits_all, []}) -> term_to_fate(_GlobEnv, _Env, {builtin, bits_all, []}) ->
aeb_fate_data:make_bits(-1); aeb_fate_data:make_bits(-1);
term_to_fate(_Env, {builtin, bits_none, []}) -> term_to_fate(_GlobEnv, _Env, {builtin, bits_none, []}) ->
aeb_fate_data:make_bits(0); aeb_fate_data:make_bits(0);
term_to_fate(_Env, {op, bits_set, [B, I]}) -> term_to_fate(GlobEnv, _Env, {op, bits_set, [B, I]}) ->
{bits, N} = term_to_fate(B), {bits, N} = term_to_fate(GlobEnv, B),
J = term_to_fate(I), J = term_to_fate(GlobEnv, I),
{bits, N bor (1 bsl J)}; {bits, N bor (1 bsl J)};
term_to_fate(_Env, {op, bits_clear, [B, I]}) -> term_to_fate(GlobEnv, _Env, {op, bits_clear, [B, I]}) ->
{bits, N} = term_to_fate(B), {bits, N} = term_to_fate(GlobEnv, B),
J = term_to_fate(I), J = term_to_fate(GlobEnv, I),
{bits, N band bnot (1 bsl J)}; {bits, N band bnot (1 bsl J)};
term_to_fate(Env, {'let', X, E, Body}) -> term_to_fate(GlobEnv, Env, {'let', X, E, Body}) ->
Env1 = Env#{ X => term_to_fate(Env, E) }, Env1 = Env#{ X => term_to_fate(GlobEnv, Env, E) },
term_to_fate(Env1, Body); term_to_fate(GlobEnv, Env1, Body);
term_to_fate(Env, {var, X}) -> term_to_fate(_GlobEnv, Env, {var, X}) ->
case maps:get(X, Env, undefined) of case maps:get(X, Env, undefined) of
undefined -> throw(not_a_fate_value); undefined -> throw(not_a_fate_value);
V -> V V -> V
end; end;
term_to_fate(_Env, {builtin, map_empty, []}) -> term_to_fate(_GlobEnv, _Env, {builtin, map_empty, []}) ->
aeb_fate_data:make_map(#{}); aeb_fate_data:make_map(#{});
term_to_fate(Env, {op, map_set, [M, K, V]}) -> term_to_fate(GlobEnv, Env, {op, map_set, [M, K, V]}) ->
Map = term_to_fate(Env, M), Map = term_to_fate(GlobEnv, Env, M),
Map#{term_to_fate(Env, K) => term_to_fate(Env, V)}; Map#{term_to_fate(GlobEnv, Env, K) => term_to_fate(GlobEnv, Env, V)};
term_to_fate(_Env, _) -> term_to_fate(_GlobEnv, _Env, _) ->
throw(not_a_fate_value). throw(not_a_fate_value).
to_scode(Env, T) -> to_scode(Env, T) ->
try term_to_fate(T) of try term_to_fate(Env, T) of
V -> [push(?i(V))] V -> [push(?i(V))]
catch throw:not_a_fate_value -> catch throw:not_a_fate_value ->
to_scode1(Env, T) to_scode1(Env, T)
end. end.
to_scode1(_Env, {lit, L}) -> to_scode1(Env, {lit, L}) ->
[push(?i(lit_to_fate(L)))]; [push(?i(lit_to_fate(Env, L)))];
to_scode1(_Env, nil) -> to_scode1(_Env, nil) ->
[aeb_fate_ops:nil(?a)]; [aeb_fate_ops:nil(?a)];
@ -555,7 +579,27 @@ builtin_to_scode(Env, aens_lookup, [_Name] = Args) ->
builtin_to_scode(_Env, auth_tx_hash, []) -> builtin_to_scode(_Env, auth_tx_hash, []) ->
[aeb_fate_ops:auth_tx_hash(?a)]; [aeb_fate_ops:auth_tx_hash(?a)];
builtin_to_scode(_Env, auth_tx, []) -> builtin_to_scode(_Env, auth_tx, []) ->
[aeb_fate_ops:auth_tx(?a)]. [aeb_fate_ops:auth_tx(?a)];
builtin_to_scode(Env, chain_bytecode_hash, [_Addr] = Args) ->
call_to_scode(Env, aeb_fate_ops:bytecode_hash(?a, ?a), Args);
builtin_to_scode(Env, chain_clone,
[InitArgsT, GasCap, Value, Prot, Contract | InitArgs]) ->
case GasCap of
{builtin, call_gas_left, _} ->
call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a),
[Contract, InitArgsT, Value, Prot | InitArgs]
);
_ ->
call_to_scode(Env, aeb_fate_ops:clone_g(?a, ?a, ?a, ?a, ?a),
[Contract, InitArgsT, Value, GasCap, Prot | InitArgs]
)
end;
builtin_to_scode(Env, chain_create,
[ Code, InitArgsT, Value | InitArgs]) ->
call_to_scode(Env, aeb_fate_ops:create(?a, ?a, ?a),
[Code, InitArgsT, Value | InitArgs]
).
%% -- Operators -- %% -- Operators --
@ -941,6 +985,10 @@ attributes(I) ->
{'STR_TO_LOWER', A, B} -> Pure(A, [B]); {'STR_TO_LOWER', A, B} -> Pure(A, [B]);
{'CHAR_TO_INT', A, B} -> Pure(A, [B]); {'CHAR_TO_INT', A, B} -> Pure(A, [B]);
{'CHAR_FROM_INT', A, B} -> Pure(A, [B]); {'CHAR_FROM_INT', A, B} -> Pure(A, [B]);
{'CREATE', A, B, C} -> Impure(?a, [A, B, C]);
{'CLONE', A, B, C, D} -> Impure(?a, [A, B, C, D]);
{'CLONE_G', A, B, C, D, E} -> Impure(?a, [A, B, C, D, E]);
{'BYTECODE_HASH', A, B} -> Impure(A, [B]);
{'ABORT', A} -> Impure(pc, A); {'ABORT', A} -> Impure(pc, A);
{'EXIT', A} -> Impure(pc, A); {'EXIT', A} -> Impure(pc, A);
'NOP' -> Pure(none, []) 'NOP' -> Pure(none, [])
@ -1694,6 +1742,9 @@ split_calls(Ref, [I | Code], Acc, Blocks) when element(1, I) == 'CALL';
element(1, I) == 'CALL_R'; element(1, I) == 'CALL_R';
element(1, I) == 'CALL_GR'; element(1, I) == 'CALL_GR';
element(1, I) == 'CALL_PGR'; element(1, I) == 'CALL_PGR';
element(1, I) == 'CREATE';
element(1, I) == 'CLONE';
element(1, I) == 'CLONE_G';
element(1, I) == 'jumpif' -> element(1, I) == 'jumpif' ->
split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]); split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]);
split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) -> split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) ->

View File

@ -93,8 +93,20 @@ decl() ->
?LAZY_P( ?LAZY_P(
choice( choice(
%% Contract declaration %% Contract declaration
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4}) [ ?RULE(token(main), keyword(contract),
, ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5})) con(), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5})
, ?RULE(keyword(contract),
con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4})
, ?RULE(keyword(contract), token(interface),
con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5})
, ?RULE(token(payable), token(main), keyword(contract),
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6}))
, ?RULE(token(payable), keyword(contract),
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5}))
, ?RULE(token(payable), keyword(contract), token(interface),
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6}))
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4}) , ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2}) , ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
, pragma() , pragma()

View File

@ -13,6 +13,8 @@
-export_type([options/0]). -export_type([options/0]).
-include("aeso_utils.hrl").
-type doc() :: prettypr:document(). -type doc() :: prettypr:document().
-type options() :: [{indent, non_neg_integer()} | show_generated]. -type options() :: [{indent, non_neg_integer()} | show_generated].
@ -131,6 +133,10 @@ typed(A, Type) ->
false -> follow(hsep(A, text(":")), type(Type)) false -> follow(hsep(A, text(":")), type(Type))
end. end.
contract_head(contract_main) -> text("main contract");
contract_head(contract_child) -> text("contract");
contract_head(contract_interface) -> text("contract interface").
%% -- Exports ---------------------------------------------------------------- %% -- Exports ----------------------------------------------------------------
-spec decls([aeso_syntax:decl()], options()) -> doc(). -spec decls([aeso_syntax:decl()], options()) -> doc().
@ -145,11 +151,11 @@ decl(D, Options) ->
with_options(Options, fun() -> decl(D) end). with_options(Options, fun() -> decl(D) end).
-spec decl(aeso_syntax:decl()) -> doc(). -spec decl(aeso_syntax:decl()) -> doc().
decl({contract, Attrs, C, Ds}) -> decl({Con, Attrs, C, Ds}) when ?IS_CONTRACT_HEAD(Con) ->
Mod = fun({Mod, true}) when Mod == payable -> Mod = fun({Mod, true}) when Mod == payable ->
text(atom_to_list(Mod)); text(atom_to_list(Mod));
(_) -> empty() end, (_) -> empty() end,
block(follow( hsep(lists:map(Mod, Attrs) ++ [text("contract")]) block(follow( hsep(lists:map(Mod, Attrs) ++ [contract_head(Con)])
, hsep(name(C), text("="))), decls(Ds)); , hsep(name(C), text("="))), decls(Ds));
decl({namespace, _, C, Ds}) -> decl({namespace, _, C, Ds}) ->
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds)); block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));

View File

@ -44,7 +44,9 @@ lexer() ->
, {"[^/*]+|[/*]", skip()} ], , {"[^/*]+|[/*]", skip()} ],
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function", Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace"], "stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace",
"interface", "main"
],
KW = string:join(Keywords, "|"), KW = string:join(Keywords, "|"),
Rules = Rules =

View File

@ -25,7 +25,8 @@
-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()}
| stateful | private | payable | main | interface].
-type name() :: string(). -type name() :: string().
-type id() :: {id, ann(), name()}. -type id() :: {id, ann(), name()}.
@ -34,7 +35,9 @@
-type qcon() :: {qcon, ann(), [name()]}. -type qcon() :: {qcon, ann(), [name()]}.
-type tvar() :: {tvar, ann(), name()}. -type tvar() :: {tvar, ann(), name()}.
-type decl() :: {contract, ann(), con(), [decl()]} -type decl() :: {contract_main, ann(), con(), [decl()]}
| {contract_child, ann(), con(), [decl()]}
| {contract_interface, ann(), con(), [decl()]}
| {namespace, ann(), con(), [decl()]} | {namespace, ann(), con(), [decl()]}
| {pragma, ann(), pragma()} | {pragma, ann(), pragma()}
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs | {type_decl, ann(), id(), [tvar()]} % Only for error msgs

6
src/aeso_utils.hrl Normal file
View File

@ -0,0 +1,6 @@
-define(IS_CONTRACT_HEAD(X),
(X =:= contract_main orelse
X =:= contract_interface orelse
X =:= contract_child
)
).

View File

@ -190,7 +190,7 @@ parameterized_contract(ExtraCode, FunName, Types) ->
lists:flatten( lists:flatten(
["contract Remote =\n" ["contract Remote =\n"
" entrypoint bla : () => unit\n\n" " entrypoint bla : () => unit\n\n"
"contract Dummy =\n", "main contract Dummy =\n",
ExtraCode, "\n", ExtraCode, "\n",
" type an_alias('a) = string * 'a\n" " type an_alias('a) = string * 'a\n"
" record r = {x : an_alias(int), y : variant}\n" " record r = {x : an_alias(int), y : variant}\n"

View File

@ -23,6 +23,7 @@ test_cases(1) ->
#{name => <<"C">>, #{name => <<"C">>,
type_defs => [], type_defs => [],
payable => true, payable => true,
kind => contract_main,
functions => functions =>
[#{name => <<"a">>, [#{name => <<"a">>,
arguments => arguments =>
@ -31,16 +32,17 @@ test_cases(1) ->
returns => <<"int">>, returns => <<"int">>,
stateful => true, stateful => true,
payable => true}]}}, payable => true}]}},
DecACI = <<"payable contract C =\n" DecACI = <<"payable main contract C =\n"
" payable entrypoint a : (int) => int\n">>, " payable entrypoint a : (int) => int\n">>,
{Contract,MapACI,DecACI}; {Contract,MapACI,DecACI};
test_cases(2) -> test_cases(2) ->
Contract = <<"contract C =\n" Contract = <<"main contract C =\n"
" type allan = int\n" " type allan = int\n"
" entrypoint a(i : allan) = i+1\n">>, " entrypoint a(i : allan) = i+1\n">>,
MapACI = #{contract => MapACI = #{contract =>
#{name => <<"C">>, payable => false, #{name => <<"C">>, payable => false,
kind => contract_main,
type_defs => type_defs =>
[#{name => <<"allan">>, [#{name => <<"allan">>,
typedef => <<"int">>, typedef => <<"int">>,
@ -53,12 +55,12 @@ test_cases(2) ->
returns => <<"int">>, returns => <<"int">>,
stateful => false, stateful => false,
payable => false}]}}, payable => false}]}},
DecACI = <<"contract C =\n" DecACI = <<"main contract C =\n"
" type allan = int\n" " type allan = int\n"
" entrypoint a : (C.allan) => int\n">>, " entrypoint a : (C.allan) => int\n">>,
{Contract,MapACI,DecACI}; {Contract,MapACI,DecACI};
test_cases(3) -> test_cases(3) ->
Contract = <<"contract C =\n" Contract = <<"main contract C =\n"
" type state = unit\n" " type state = unit\n"
" datatype event = SingleEventDefined\n" " datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n" " datatype bert('a) = Bin('a)\n"
@ -71,7 +73,7 @@ test_cases(3) ->
#{<<"C.bert">> => [<<"string">>]}}], #{<<"C.bert">> => [<<"string">>]}}],
name => <<"a">>,returns => <<"int">>, name => <<"a">>,returns => <<"int">>,
stateful => false, payable => false}], stateful => false, payable => false}],
name => <<"C">>, payable => false, name => <<"C">>, payable => false, kind => contract_main,
event => #{variant => [#{<<"SingleEventDefined">> => []}]}, event => #{variant => [#{<<"SingleEventDefined">> => []}]},
state => <<"unit">>, state => <<"unit">>,
type_defs => type_defs =>
@ -80,7 +82,7 @@ test_cases(3) ->
#{variant => #{variant =>
[#{<<"Bin">> => [<<"'a">>]}]}, [#{<<"Bin">> => [<<"'a">>]}]},
vars => [#{name => <<"'a">>}]}]}}, vars => [#{name => <<"'a">>}]}]}},
DecACI = <<"contract C =\n" DecACI = <<"main contract C =\n"
" type state = unit\n" " type state = unit\n"
" datatype event = SingleEventDefined\n" " datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n" " datatype bert('a) = Bin('a)\n"
@ -101,17 +103,24 @@ aci_test_contract(Name) ->
true -> [debug_mode]; true -> [debug_mode];
false -> [] false -> []
end ++ [{include, {file_system, [aeso_test_utils:contract_path()]}}], end ++ [{include, {file_system, [aeso_test_utils:contract_path()]}}],
{ok, JSON} = aeso_aci:contract_interface(json, String, Opts), JSON = case aeso_aci:contract_interface(json, String, Opts) of
{ok, #{aci := JSON1}} = aeso_compiler:from_string(String, [{aci, json}, {backend, fate} | Opts]), {ok, J} -> J;
{error, ErrorStringJ} when is_binary(ErrorStringJ) -> error(ErrorStringJ);
{error, ErrorJ} -> aeso_compiler_tests:print_and_throw(ErrorJ)
end,
case aeso_compiler:from_string(String, [{aci, json}, {backend, fate} | Opts]) of
{ok, #{aci := JSON1}} ->
?assertEqual(JSON, JSON1), ?assertEqual(JSON, JSON1),
io:format("JSON:\n~p\n", [JSON]), io:format("JSON:\n~p\n", [JSON]),
{ok, ContractStub} = aeso_aci:render_aci_json(JSON), {ok, ContractStub} = aeso_aci:render_aci_json(JSON),
io:format("STUB:\n~s\n", [ContractStub]), io:format("STUB:\n~s\n", [ContractStub]),
check_stub(ContractStub, [{src_file, Name}]), check_stub(ContractStub, [{src_file, Name}]),
ok. ok;
{error, ErrorString} when is_binary(ErrorString) -> error(ErrorString);
{error, Error} -> aeso_compiler_tests:print_and_throw(Error)
end.
check_stub(Stub, Options) -> check_stub(Stub, Options) ->
try aeso_parser:string(binary_to_list(Stub), Options) of try aeso_parser:string(binary_to_list(Stub), Options) of

View File

@ -59,8 +59,8 @@ calldata_aci_test_() ->
end} || {ContractName, Fun, Args} <- compilable_contracts()]. end} || {ContractName, Fun, Args} <- compilable_contracts()].
parse_args(Fun, Args) -> parse_args(Fun, Args) ->
[{contract, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] = [{contract_main, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] =
aeso_parser:string("contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"), aeso_parser:string("main contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
strip_ann(AST). strip_ann(AST).
strip_ann(T) when is_tuple(T) -> strip_ann(T) when is_tuple(T) ->

View File

@ -34,9 +34,7 @@ simple_compile_test_() ->
#{fate_code := Code} when Backend == fate -> #{fate_code := Code} when Backend == fate ->
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)), Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
?assertMatch({X, X}, {Code1, Code}); ?assertMatch({X, X}, {Code1, Code});
ErrBin -> Error -> io:format("\n\n~p\n\n", [Error]), print_and_throw(Error)
io:format("\n~s", [ErrBin]),
error(ErrBin)
end end
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate], end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
not lists:member(ContractName, not_compilable_on(Backend))] ++ not lists:member(ContractName, not_compilable_on(Backend))] ++
@ -179,17 +177,16 @@ compilable_contracts() ->
"lhs_matching", "lhs_matching",
"more_strings", "more_strings",
"protected_call", "protected_call",
"hermetization_turnoff" "hermetization_turnoff",
"multiple_contracts",
"clone",
"clone_simple",
"create",
"test" % Custom general-purpose test file. Keep it last on the list.
]. ].
not_compilable_on(fate) -> []; not_compilable_on(fate) -> [];
not_compilable_on(aevm) -> not_compilable_on(aevm) -> compilable_contracts().
[ "stdlib_include", "manual_stdlib_include", "pairing_crypto"
, "aens_update", "basic_auth_tx", "more_strings"
, "unapplied_builtins", "bytes_to_x", "state_handling", "protected_call"
, "hermetization_turnoff"
].
debug_mode_contracts() -> debug_mode_contracts() ->
["hermetization_turnoff"]. ["hermetization_turnoff"].
@ -635,9 +632,9 @@ failing_contracts() ->
<<?Pos(2, 1) <<?Pos(2, 1)
"Cannot compile with this version of the compiler,\n" "Cannot compile with this version of the compiler,\n"
"because it does not satisfy the constraint ", Version/binary, " == 9.9.9">>]) "because it does not satisfy the constraint ", Version/binary, " == 9.9.9">>])
, ?TYPE_ERROR(multiple_contracts, , ?TYPE_ERROR(interface_with_defs,
[<<?Pos(2, 3) [<<?Pos(2, 3)
"Only the main contract can contain defined functions or entrypoints.\n" "Contract interfaces cannot contain defined functions or entrypoints.\n"
"Fix: replace the definition of 'foo' by a type signature.">>]) "Fix: replace the definition of 'foo' by a type signature.">>])
, ?TYPE_ERROR(contract_as_namespace, , ?TYPE_ERROR(contract_as_namespace,
[<<?Pos(5, 28) [<<?Pos(5, 28)
@ -733,6 +730,39 @@ failing_contracts() ->
, ?TYPE_ERROR(bad_state, , ?TYPE_ERROR(bad_state,
[<<?Pos(4, 16) [<<?Pos(4, 16)
"Conflicting updates for field 'foo'">>]) "Conflicting updates for field 'foo'">>])
, ?TYPE_ERROR(factories_type_errors,
[<<?Pos(10,18)
"Chain.clone requires `ref` named argument of contract type.">>,
<<?Pos(11,18)
"Cannot unify (gas : int, value : int, protected : bool) => if(protected, option(void), void)\n and (gas : int, value : int, protected : bool, int, bool) => 'b\n"
"when checking contract construction of type\n (gas : int, value : int, protected : bool) =>\n if(protected, option(void), void) (at line 11, column 18)\nagainst the expected type\n (gas : int, value : int, protected : bool, int, bool) => 'b">>,
<<?Pos(12,37)
"Cannot unify int\n and bool\n"
"when checking named argument\n gas : int\nagainst inferred type\n bool">>,
<<?Pos(13,18),
"Kaboom is not implemented.\n"
"when resolving arguments of variadic function\n Chain.create">>,
<<?Pos(18,18)
"Cannot unify (gas : int, value : int, protected : bool, int, bool) => if(protected, option(void), void)\n and (gas : int, value : int, protected : bool) => 'a\n"
"when checking contract construction of type\n (gas : int, value : int, protected : bool, int, bool) =>\n if(protected, option(void), void) (at line 18, column 18)\nagainst the expected type\n (gas : int, value : int, protected : bool) => 'a">>,
<<?Pos(19,42),
"Named argument protected (at line 19, column 42) is not one of the expected named arguments\n - value : int">>,
<<?Pos(20,42),
"Cannot unify int\n and bool\n"
"when checking named argument\n value : int\nagainst inferred type\n bool">>
])
, ?TYPE_ERROR(ambiguous_main,
[<<?Pos(1,1)
"Could not deduce the main contract. You can point it out manually with the `main` keyword.">>
])
, ?TYPE_ERROR(no_main_contract,
[<<?Pos(0,0)
"No contract defined.">>
])
, ?TYPE_ERROR(multiple_main_contracts,
[<<?Pos(1,6)
"Only one main contract can be defined.">>
])
]. ].
-define(Path(File), "code_errors/" ??File). -define(Path(File), "code_errors/" ??File).
@ -746,9 +776,7 @@ failing_contracts() ->
{fate, ?Msg(File, Line, Col, ErrFATE)}]}). {fate, ?Msg(File, Line, Col, ErrFATE)}]}).
failing_code_gen_contracts() -> failing_code_gen_contracts() ->
[ ?SAME(last_declaration_must_be_contract, 1, 1, [ ?SAME(missing_definition, 2, 14,
"Expected a contract as the last declaration instead of the namespace 'LastDeclarationIsNotAContract'")
, ?SAME(missing_definition, 2, 14,
"Missing definition of function 'foo'.") "Missing definition of function 'foo'.")
, ?AEVM(polymorphic_entrypoint, 2, 17, , ?AEVM(polymorphic_entrypoint, 2, 17,
"The argument\n" "The argument\n"
@ -846,6 +874,8 @@ failing_code_gen_contracts() ->
"Invalid state type\n" "Invalid state type\n"
" {f : (int) => int}\n" " {f : (int) => int}\n"
"The state cannot contain functions in the AEVM. Use FATE if you need this.") "The state cannot contain functions in the AEVM. Use FATE if you need this.")
, ?FATE(child_with_decls, 2, 14,
"Missing definition of function 'f'.")
]. ].
validation_test_() -> validation_test_() ->
@ -883,7 +913,8 @@ validation_fails() ->
"Byte code contract is not payable, but source code contract is.">>]}]. "Byte code contract is not payable, but source code contract is.">>]}].
validate(Contract1, Contract2) -> validate(Contract1, Contract2) ->
ByteCode = #{ fate_code := FCode } = compile(fate, Contract1), case compile(fate, Contract1) of
ByteCode = #{ fate_code := FCode } ->
FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)), FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)),
Source = aeso_test_utils:read_contract(Contract2), Source = aeso_test_utils:read_contract(Contract2),
aeso_compiler:validate_byte_code( aeso_compiler:validate_byte_code(
@ -892,5 +923,16 @@ validate(Contract1, Contract2) ->
true -> [debug_mode]; true -> [debug_mode];
false -> [] false -> []
end ++ end ++
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]). [{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]);
Error -> print_and_throw(Error)
end.
print_and_throw(Err) ->
case Err of
ErrBin when is_binary(ErrBin) ->
io:format("\n~s", [ErrBin]),
error(ErrBin);
Errors ->
io:format("Compilation error:\n~s", [string:join([aeso_errors:pp(E) || E <- Errors], "\n\n")]),
error(compilation_error)
end.

View File

@ -12,10 +12,10 @@ simple_contracts_test_() ->
fun(_) -> ok end, fun(_) -> ok end,
[{"Parse a contract with an identity function.", [{"Parse a contract with an identity function.",
fun() -> fun() ->
Text = "contract Identity =\n" Text = "main contract Identity =\n"
" function id(x) = x\n", " function id(x) = x\n",
?assertMatch( ?assertMatch(
[{contract, _, {con, _, "Identity"}, [{contract_main, _, {con, _, "Identity"},
[{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"}, [{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"},
{id, _, "x"}}]}], parse_string(Text)), {id, _, "x"}}]}], parse_string(Text)),
ok ok

View File

@ -1,5 +1,5 @@
contract Identity = contract Identity =
function main (x:int) = x function main_fun (x:int) = x
function __call() = 12 function __call() = 12

View File

@ -1,5 +1,5 @@
contract Remote = contract interface Remote =
entrypoint main : (int) => unit entrypoint main_fun : (int) => unit
contract AddrChain = contract AddrChain =
type o_type = oracle(string, map(string, int)) type o_type = oracle(string, map(string, int))

View File

@ -1,5 +1,5 @@
contract Remote = contract interface Remote =
entrypoint foo : () => unit entrypoint foo : () => unit
contract AddressLiterals = contract AddressLiterals =

View File

@ -0,0 +1,5 @@
contract C =
entrypoint f() = 123
contract D =
entrypoint f() = 123

View File

@ -1,5 +1,5 @@
contract Remote = contract interface Remote =
entrypoint foo : () => unit entrypoint foo : () => unit
contract AddressLiterals = contract AddressLiterals =

View File

@ -1,4 +1,4 @@
contract Remote = contract interface Remote =
entrypoint id : int => int entrypoint id : int => int
contract ProtectedCall = contract ProtectedCall =

View File

@ -1,3 +1,3 @@
function square(x) = x ^ 2 function square(x) = x ^ 2
contract Main = contract Main =
entrypoint main() = square(10) entrypoint main_fun() = square(10)

28
test/contracts/clone.aes Normal file
View File

@ -0,0 +1,28 @@
contract interface HigherOrderState =
entrypoint init : () => void
entrypoint apply : int => int
stateful entrypoint inc : int => unit
contract interface LowerDisorderAnarchy =
entrypoint init : (int) => void
main contract C =
// both `s` and `l` should be of type `HigherOrderState` in this test
stateful entrypoint run_clone(s : HigherOrderState, l : LowerDisorderAnarchy) : HigherOrderState =
let s1 = Chain.clone(ref=s)
let Some(s2) = Chain.clone(ref=s, protected=true)
let None = Chain.clone(ref=s, protected=true, gas=1)
let None = Chain.clone(ref=l, protected=true, 123) // since it should be HigherOrderState underneath
let s3 = Chain.clone(ref=s1)
require(s1.apply(2137) == 2137, "APPLY_S1_0")
require(s2.apply(2137) == 2137, "APPLY_S2_0")
require(s3.apply(2137) == 2137, "APPLY_S3_0")
s1.inc(1)
s2.inc(1)
s1.inc(1)
require(s1.apply(2137) == 2139, "APPLY_S1_2")
require(s2.apply(2137) == 2138, "APPLY_S2_1")
require(s3.apply(2137) == 2137, "APPLY_S3_0")
s1

View File

@ -0,0 +1,7 @@
contract interface I =
entrypoint init : () => void
contract C =
stateful entrypoint f(i : I) =
let Some(c1) = Chain.clone(ref=i, protected = true)
2

View File

@ -5,5 +5,5 @@ contract BadAENSresolve =
function fail() : t(int) = function fail() : t(int) =
AENS.resolve("foo.aet", "whatever") AENS.resolve("foo.aet", "whatever")
entrypoint main() = () entrypoint main_fun() = ()

View File

@ -0,0 +1,5 @@
contract C =
entrypoint f : () => unit
main contract M =
entrypoint f() = 123

View File

@ -3,4 +3,4 @@ contract MapAsMapKey =
function foo(m) : t(int => int) = {[m] = 0} function foo(m) : t(int => int) = {[m] = 0}
entrypoint main() = () entrypoint main_fun() = ()

View File

@ -2,4 +2,4 @@ contract HigherOrderQueryType =
stateful function foo(o) : oracle_query(_, string ) = stateful function foo(o) : oracle_query(_, string ) =
Oracle.query(o, (x) => x + 1, 100, RelativeTTL(100), RelativeTTL(100)) Oracle.query(o, (x) => x + 1, 100, RelativeTTL(100), RelativeTTL(100))
entrypoint main() = () entrypoint main_fun() = ()

View File

@ -2,4 +2,4 @@ contract HigherOrderResponseType =
stateful function foo(o, q : oracle_query(string, _)) = stateful function foo(o, q : oracle_query(string, _)) =
Oracle.respond(o, q, (x) => x + 1) Oracle.respond(o, q, (x) => x + 1)
entrypoint main() = () entrypoint main_fun() = ()

View File

@ -1,2 +0,0 @@
namespace LastDeclarationIsNotAContract =
function add(x, y) = x + y

View File

@ -1,3 +1,3 @@
contract MissingDefinition = contract MissingDefinition =
entrypoint foo : int => int entrypoint foo : int => int
entrypoint main() = foo(0) entrypoint main_fun() = foo(0)

View File

@ -3,5 +3,5 @@ contract PolymorphicAENSresolve =
function fail() : option('a) = function fail() : option('a) =
AENS.resolve("foo.aet", "whatever") AENS.resolve("foo.aet", "whatever")
entrypoint main() = () entrypoint main_fun() = ()

View File

@ -3,4 +3,4 @@ contract MapAsMapKey =
function foo(m) : t('a) = {[m] = 0} function foo(m) : t('a) = {[m] = 0}
entrypoint main() = () entrypoint main_fun() = ()

View File

@ -2,4 +2,4 @@ contract PolymorphicQueryType =
stateful function is_oracle(o) = stateful function is_oracle(o) =
Oracle.check(o) Oracle.check(o)
entrypoint main() = () entrypoint main_fun() = ()

View File

@ -2,4 +2,4 @@ contract PolymorphicResponseType =
function is_oracle(o : oracle(string, 'r)) = function is_oracle(o : oracle(string, 'r)) =
Oracle.check(o) Oracle.check(o)
entrypoint main(o : oracle(string, int)) = is_oracle(o) entrypoint main_fun(o : oracle(string, int)) = is_oracle(o)

View File

@ -1,4 +1,4 @@
contract Remote = contract interface Remote =
entrypoint foo : int => int entrypoint foo : int => int
contract UnappliedContractCall = contract UnappliedContractCall =

View File

@ -1,5 +1,5 @@
contract UnappliedNamedArgBuiltin = contract UnappliedNamedArgBuiltin =
// Allowed in FATE, but not AEVM // Allowed in FATE, but not AEVM
stateful entrypoint main(s) = stateful entrypoint main_fun(s) =
let reg = Oracle.register let reg = Oracle.register
reg(signature = s, Contract.address, 100, RelativeTTL(100)) : oracle(int, int) reg(signature = s, Contract.address, 100, RelativeTTL(100)) : oracle(int, int)

View File

@ -1,5 +1,5 @@
contract Remote = contract interface Remote =
entrypoint up_to : (int) => list(int) entrypoint up_to : (int) => list(int)
entrypoint sum : (list(int)) => int entrypoint sum : (list(int)) => int
entrypoint some_string : () => string entrypoint some_string : () => string

View File

@ -1,4 +1,4 @@
contract Foo = contract interface Foo =
entrypoint foo : () => int entrypoint foo : () => int
contract Fail = contract Fail =

28
test/contracts/create.aes Normal file
View File

@ -0,0 +1,28 @@
contract IntegerAdder =
entrypoint init() = ()
entrypoint addIntegers(x, y) = x + y
contract IntegerAdderHolder =
type state = IntegerAdder
stateful entrypoint init() = Chain.create() : IntegerAdder
entrypoint get() = state
contract IntegerAdderFactory =
entrypoint init() = ()
stateful entrypoint new() =
let i = Chain.create() : IntegerAdderHolder
i.get()
payable contract ValueAdder =
entrypoint init() = ()
stateful entrypoint addValue(x) =
let integerAdderFactory = Chain.create()
let adder = integerAdderFactory.new()
adder.addIntegers(x, Contract.balance)
main contract EnterpriseContract =
entrypoint init() = ()
stateful payable entrypoint increaseByThree(x) =
require(Call.value >= 3, "Price for addition = 3AEtto, insufficient funds")
let threeAdder = Chain.create(value = 3)
threeAdder.addValue(x)

View File

@ -1,6 +1,6 @@
// Testing primitives for accessing the block chain environment // Testing primitives for accessing the block chain environment
contract Interface = contract interface Interface =
entrypoint contract_address : () => address entrypoint contract_address : () => address
entrypoint call_origin : () => address entrypoint call_origin : () => address
entrypoint call_caller : () => address entrypoint call_caller : () => address

View File

@ -1,4 +1,4 @@
contract Remote = contract interface Remote =
entrypoint dummy : () => unit entrypoint dummy : () => unit
contract Events = contract Events =

View File

@ -1,6 +1,6 @@
// 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 interface FactorialServer =
entrypoint fac : (int) => int entrypoint fac : (int) => int
contract Factorial = contract Factorial =

View File

@ -0,0 +1,24 @@
contract interface Kaboom =
entrypoint init : () => void
contract Bakoom =
type state = int
entrypoint init(x, b) = if(b) x else 0
main contract Test =
stateful entrypoint test(k : Kaboom) =
let k_bad1 = Chain.clone() : Kaboom
let k_bad2 = Chain.clone(ref=k, 123, true)
let k_bad3 = Chain.clone(ref=k, gas=true)
let k_bad4 = Chain.create() : Kaboom
let k_gud1 = Chain.clone(ref=k)
let Some(k_gud2) = Chain.clone(ref=k, protected=true)
let Some(k_gud3) = Chain.clone(ref=k, value=10, protected=true, gas=123)
let b_bad1 = Chain.create() : Bakoom
let b_bad2 = Chain.create(123, true, protected=true) : Bakoom
let b_bad3 = Chain.create(123, true, value=true) : Bakoom
let b_gud1 = Chain.create(123, true) : Bakoom
let b_gud2 = Chain.create(123, true, value=100) : Bakoom
b_gud1

View File

@ -1,3 +1,2 @@
main contract Identity =
contract Identity = entrypoint main_fun (x:int) = x
entrypoint main (x:int) = x

View File

@ -0,0 +1,5 @@
contract interface ContractOne =
entrypoint foo() = "foo"
contract ContractTwo =
entrypoint bar() = "bar"

View File

@ -16,7 +16,7 @@ contract LHSMatching =
let null(_ :: _) = false let null(_ :: _) = false
!null(xs) !null(xs)
entrypoint main() = entrypoint main_fun() =
from_some(Some([0])) from_some(Some([0]))
++ append([length([true]), 2, 3], [4, 5, 6]) ++ append([length([true]), 2, 3], [4, 5, 6])
++ [7 | if (local_match([false]))] ++ [7 | if (local_match([false]))]

View File

@ -1,3 +1,3 @@
contract MissingEventType = contract MissingEventType =
entrypoint main() = entrypoint main_fun() =
Chain.event("MAIN") Chain.event("MAIN")

View File

@ -1,5 +1,7 @@
contract ContractOne = contract Child =
entrypoint foo() = "foo" entrypoint
add2 : int => int
add2(x) = x + 2
contract ContractTwo = main contract Main =
entrypoint bar() = "bar" entrypoint add4(x, c : Child) = c.add2(x) + 2

View File

@ -0,0 +1,5 @@
main contract C =
entrypoint f() = 123
main contract D =
entrypoint f() = 123

View File

@ -0,0 +1,2 @@
contract interface C =
entrypoint f : () => unit

View File

@ -1,4 +1,4 @@
contract C1 = contract interface C1 =
entrypoint f : int entrypoint f : int
contract C = contract C =

View File

@ -1,4 +1,4 @@
contract Remote = contract interface Remote =
entrypoint id : int => int entrypoint id : int => int
contract ProtectedCall = contract ProtectedCall =

View File

@ -1,18 +1,18 @@
contract Remote1 = contract interface Remote1 =
entrypoint main : (int) => int entrypoint main_fun : (int) => int
contract Remote2 = contract interface Remote2 =
entrypoint call : (Remote1, int) => int entrypoint call : (Remote1, int) => int
contract Remote3 = contract interface Remote3 =
entrypoint get : () => int entrypoint get : () => int
entrypoint tick : () => unit entrypoint tick : () => unit
contract RemoteCall = contract RemoteCall =
stateful entrypoint call(r : Remote1, x : int) : int = stateful entrypoint call(r : Remote1, x : int) : int =
r.main(gas = 10000, value = 10, x) r.main_fun(gas = 10000, value = 10, x)
entrypoint staged_call(r1 : Remote1, r2 : Remote2, x : int) = entrypoint staged_call(r1 : Remote1, r2 : Remote2, x : int) =
r2.call(r1, x) r2.call(r1, x)

View File

@ -1,5 +1,5 @@
contract SpendContract = contract interface SpendContract =
entrypoint withdraw : (int) => int entrypoint withdraw : (int) => int
contract SpendTest = contract SpendTest =

View File

@ -1,5 +1,5 @@
include "String.aes" include "String.aes"
contract Remote = contract interface Remote =
record rstate = { i : int, s : string, m : map(int, int) } record rstate = { i : int, s : string, m : map(int, int) }
entrypoint look_at : (rstate) => unit entrypoint look_at : (rstate) => unit

View File

@ -1,5 +1,5 @@
contract Remote = contract interface Remote =
stateful entrypoint remote_spend : (address, int) => unit stateful entrypoint remote_spend : (address, int) => unit
entrypoint remote_pure : int => int entrypoint remote_pure : int => int

View File

@ -1,102 +1,12 @@
// This is a custom test file if you need to run a compiler without
// changing aeso_compiler_tests.erl
contract Identity = include "List.aes"
// type xy = {x:int, y:int}
// type xz = {x:int, z:int}
// type yz = {y:int, z:int}
record point = {x:int,y:int}
record cp('a) = {color: string, p:'a}
//type intpoint = point(int)
// //if (x==42) 1 else (x*x)
// }
//let baz() = {age:3, name:(4:int)}
//let foo(a,b,c) = c
// let rec fac(n) = if((n:int)==0) 1 else (n*fac(n-1))
// and main(x) = x::[x+1]
// let lentr(l) = lent(0,l)
// let rec len(l) =
// switch(l) {
// | [] => 0
// | x::xs => 1+len(xs)
// }
// let lent(n,l) =
// switch (l) {
// | [] => n
// | (x::xs) => lent(n+1,xs)
// }
// let rec app(a,b) =
// switch(a) {
// | [] => b
// | (x::xs) => x::app(xs,b)
// }
// let rec revt(l,r) =
// switch(l) {
// | [] => r
// | x::xs => revt(xs,x::r)
// }
// let rev(l) = revt(l,[])
// let main(x:int) = {
// switch(rev([1,2,3])) {
// | h::_ => h
// }
// }
//let fac(n:int) = {
// if (n==0) 1 else (n*fac(n-1))
//}
//let main(x) = switch((12,34)) {
//| (13,_) => x
//| (_,a) => x+a
// | y => y+1
// }
//let main(x) = ({y:0>1, x:x==0}:point(bool))
//let main(x) = x
//let main(x) = len(1::2::[])
//let main(x) = ((x,x):list('a))
// let main(x) = switch("a") {
// | "b" => 0
// | "a" => 1
// | "c" => 2
// }
//let main(x) = x.color+1
//let main(x) = switch(({x:x, y:x+1}:cp(int))) {
// | {y:xx} => xx
// }
//let main(x) = {x:0, y:1, z:2}
// let id(x) = x
// let double(x) = x+x
// let pair(x) = (1,2)
// let unit(x) = ()
// let tuples(x) = ((1,x),(2,3,4))
// let singleton(x) = [x]
// let rec seq(n) = if (n==0) [] else (app(seq(n-1),[n]))
// let idString(s:string) = s
// let pairString(s:string) = (s,s)
// let revStrings(ss:list(string))=rev(ss)
// let makePoint(x,y) = {x:x, y:y}
// let getx(x) = x.x
// let updatex(p,x) = p{x:x}
// let quad(x) = {let y=x+x; let z=y+y; z;}
// let noblock(x) = {x; x}
// let unit(x) = ()
// let foo(x) = switch (x) {
// | y => y+1
// }
// let p(x) = {color:"blue", p:{x:x, y:x+1}}
//let twice(f,x) = f(f(x))
// let twice(f,x) = f(f(x))
// let double(x) = x+x
// let main(x) = twice((y=>y+y),x)
// let rec map(f,xs) = switch(xs) {
// | [] => []
// | (x::ys) => f(x)::map(f,ys)
// }
// let id(x) = x
// let main(xs) = map(double,xs)
function z(f,x) = x
function s(n) = (f,x)=>f(n(f,x))
function add(m,n) = (f,x)=>m(f,n(f,x))
entrypoint main() = contract IntegerHolder =
let three=s(s(s(z))) type state = int
add(three,three) entrypoint init(x) = x
(((i)=>i+1),0) entrypoint get() = state
main contract Test =
stateful entrypoint f(c) = Chain.clone(ref=c, 123)

View File

@ -1,5 +1,5 @@
contract Remote = contract interface Remote =
type themap = map(int, string) type themap = map(int, string)
entrypoint foo : () => themap entrypoint foo : () => themap

View File

@ -9,7 +9,7 @@
// Oracle.extend // Oracle.extend
include "String.aes" include "String.aes"
contract UnappliedBuiltins = contract UnappliedBuiltins =
entrypoint main() = () entrypoint main_fun() = ()
type o = oracle(int, int) type o = oracle(int, int)
type t = list(int * string) type t = list(int * string)
type m = map(int, int) type m = map(int, int)