Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0320ac959b | |||
| dcef89b486 | |||
| 4957d01e9e | |||
| 9d76e6186a | |||
| ae3edac53e | |||
| acec32e744 | |||
| 5784f074a6 | |||
| d07b321b25 | |||
| 2e6c01cb75 | |||
| b22eeffc3d | |||
| b366bed24b | |||
| 1975ccf804 | |||
| 4f68729631 | |||
| 10c845d3cf | |||
| 393d7710c1 | |||
| 37e5a92b2e | |||
| cb9c9df103 | |||
| c09313a92c | |||
| 75b2d6981f | |||
| 78d94786b6 | |||
| 216f7f8a25 | |||
| 254172e3a3 | |||
| eadb4e8c83 | |||
| e2af89287d | |||
| 3996b6a711 | |||
| e8b32a6875 | |||
| cca7bdff49 | |||
| 1d9f59fec3 | |||
| d82b42518e | |||
| 00a3a51d0d | |||
| 6858329faa | |||
| c2a3e333c7 | |||
| 4787830861 | |||
| a0111066e7 | |||
| 3b2ce63fa7 | |||
| 8b4a1aaf0d | |||
| c6e7db2381 | |||
| 4e60d019ca | |||
| b8002029cf | |||
| 1a14602f36 | |||
| e2ef95d6fd | |||
| 22aaeceba8 | |||
| f1d95484a5 | |||
| 7e65f26211 |
@@ -21,3 +21,4 @@ rebar3.crashdump
|
||||
aesophia
|
||||
.qcci
|
||||
current_counterexample.eqc
|
||||
test/contracts/test.aes
|
||||
|
||||
+104
-1
@@ -6,9 +6,108 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- `Option.force_msg`
|
||||
### Changed
|
||||
### Removed
|
||||
|
||||
## [6.0.2] 2021-07-05
|
||||
### Changed
|
||||
- `List.from_to_step` now forbids non-positive step (this change does
|
||||
*not* alter the behavior of the previously deployed contracts)
|
||||
- Fixed leaking state between contracts
|
||||
|
||||
## [6.0.1] 2021-06-24
|
||||
### Changed
|
||||
- Fixed a bug in calldata encoding for contracts containing multiple contracts
|
||||
- Fixed a missing `include` in the `Frac` standard library
|
||||
|
||||
## [6.0.0] 2021-05-26
|
||||
### Added
|
||||
- Child contracts
|
||||
- `Chain.clone`
|
||||
- `Chain.create`
|
||||
- `Chain.bytecode_hash`
|
||||
- Minor support for variadic functions
|
||||
- `void` type that represents an empty type
|
||||
- `Call.fee` builtin
|
||||
### 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
|
||||
|
||||
## [5.0.0] 2021-04-30
|
||||
### Added
|
||||
- A new and improved [`String` standard library](https://github.com/aeternity/aesophia/blob/master/docs/sophia_stdlib.md#string)
|
||||
has been added. Use it by `include "String.aes"`. It includes functions for
|
||||
turning strings into lists of characters for detailed manipulation. For
|
||||
example:
|
||||
```
|
||||
include "String.aes"
|
||||
contract C =
|
||||
entrypoint filter_all_a(s: string) : string =
|
||||
String.from_list(List.filter((c : char) => c != 'a', String.to_list(s)))
|
||||
```
|
||||
will return a list with all `a`'s removed.
|
||||
|
||||
There are also convenience functions `split`, `concat`, `to_upper`,
|
||||
`to_lower`, etc.
|
||||
|
||||
All String functions in FATEv2 operate on unicode code points.
|
||||
- Operations for pairing-based cryptography has been added the operations
|
||||
are in the standard library [BLS12_381](https://github.com/aeternity/aesophia/blob/master/docs/sophia_stdlib.md#bls12_381).
|
||||
With these operations it is possible to do Zero Knowledge-proofs, etc.
|
||||
The operations are for the BLS12-381 curve (as the name suggests).
|
||||
- Calls to functions in other contracts (i.e. _remote calls_) can now be
|
||||
[`protected`](https://github.com/aeternity/aesophia/blob/master/docs/sophia.md#protected-contract-calls).
|
||||
If a contract call fails for any reason (for instance, the remote contract
|
||||
crashes or runs out of gas, or the entrypoint doesn't exist or has the
|
||||
wrong type) the parent call also fails. To make it possible to recover
|
||||
from failures, contract calls takes a named argument `protected : bool`
|
||||
(default `false`).
|
||||
|
||||
If `protected = true` the result of the contract call is wrapped in an
|
||||
`option`, and `Some(value)` indicates a succesful execution and `None`
|
||||
indicates that the contract call failed. Note: any gas consumed until
|
||||
the failure is still charged, but all side effects in the remote
|
||||
contract are rolled back on failure.
|
||||
- A new chain operation [`AENS.update`](https://github.com/aeternity/aesophia/blob/master/docs/sophia.md#aens-interface)
|
||||
is supported.
|
||||
- New chain exploring operations `AENS.lookup` and `Oracle.expiry` to
|
||||
look up an AENS record and the expiry of an Oracle respectively, are added.
|
||||
- Transaction introspection (`Auth.tx`) has been added. When a Generalized
|
||||
account is authorized, the authorization function needs access to the
|
||||
transaction (and the transaction hash) for the wrapped transaction. The
|
||||
transaction and the transaction hash is available `Auth.tx`, it is only
|
||||
available during authentication if invoked by a normal contract call
|
||||
it returns `None`. Example:
|
||||
```
|
||||
switch(Auth.tx)
|
||||
None => abort("Not in Auth context")
|
||||
Some(tx0) =>
|
||||
switch(tx0.tx)
|
||||
Chain.SpendTx(_, amount, _) => amount > 400
|
||||
Chain.ContractCallTx(_, _) => true
|
||||
_ => false
|
||||
```
|
||||
- A debug mode is a added to the compiler. Right now its only use is to
|
||||
turn off hermetization.
|
||||
### Changed
|
||||
- The function `Chain.block_hash(height)` is now (in FATEv2) defined for
|
||||
the current height - this used to be an error.
|
||||
- Standard library: Sort is optimized to do `mergesort` and a `contains`
|
||||
function is added.
|
||||
- Improved type errors and explicit errors for some syntax errors (empty code
|
||||
blocks, etc.).
|
||||
- Compiler optimization: The ACI is generated alongside bytecode. This means
|
||||
that multiple compiler passes can be avoided.
|
||||
- Compiler optimization: Improved parsing (less stack used when transpiled).
|
||||
- A bug where constraints were handled out of order fixed.
|
||||
- Fixed calldata decoding for singleton records.
|
||||
- Improved the documentation w.r.t. signatures, especially stressing the fact that
|
||||
the network ID is a part of what is signed.
|
||||
### Removed
|
||||
|
||||
## [4.3.0]
|
||||
### Added
|
||||
- Added documentation (moved from `protocol`)
|
||||
@@ -211,7 +310,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Simplify calldata creation - instead of passing a compiled contract, simply
|
||||
pass a (stubbed) contract string.
|
||||
|
||||
[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.3.0...HEAD
|
||||
[Unreleased]: https://github.com/aeternity/aesophia/compare/v6.0.2...HEAD
|
||||
[6.0.2]: https://github.com/aeternity/aesophia/compare/v6.0.1...v6.0.2
|
||||
[6.0.1]: https://github.com/aeternity/aesophia/compare/v6.0.0...v6.0.1
|
||||
[6.0.0]: https://github.com/aeternity/aesophia/compare/v5.0.0...v6.0.0
|
||||
[5.0.0]: https://github.com/aeternity/aesophia/compare/v4.3.0...v5.0.0
|
||||
[4.3.0]: https://github.com/aeternity/aesophia/compare/v4.2.0...v4.3.0
|
||||
[4.2.0]: https://github.com/aeternity/aesophia/compare/v4.1.0...v4.2.0
|
||||
[4.1.0]: https://github.com/aeternity/aesophia/compare/v4.0.0...v4.1.0
|
||||
|
||||
@@ -15,11 +15,12 @@ The compiler is currently being used three places
|
||||
|
||||
## Versioning
|
||||
|
||||
`aesophia` has a version that is only loosely connected to the version of the
|
||||
Aeternity node - in principle they will share the major version but not
|
||||
minor/patch version. The `aesophia` compiler version MUST be bumped whenever
|
||||
there is a change in how byte code is generated, but it MAY also be bumped upon
|
||||
API changes etc.
|
||||
Versioning should follow the [semantic versioning](https://semver.org/spec/v2.0.0) guidelines. Id est, given a version number MAJOR.MINOR.PATCH, increment the:
|
||||
|
||||
- MAJOR version when you make incompatible API changes
|
||||
- MINOR version when you add functionality in a backwards compatible manner
|
||||
- PATCH version when you make backwards compatible bug fixes
|
||||
|
||||
|
||||
## Interface Modules
|
||||
|
||||
|
||||
+125
-93
@@ -1,65 +1,6 @@
|
||||
<!-- IMPORTANT: REMEMBER TO UPDATE THE TABLE OF CONTENTS AFTER YOUR EDIT -->
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
- [-](#-)
|
||||
- [Language Features](#language-features)
|
||||
- [Contracts](#contracts)
|
||||
- [Calling other contracts](#calling-other-contracts)
|
||||
- [Protected contract calls](#protected-contract-calls)
|
||||
- [Mutable state](#mutable-state)
|
||||
- [Stateful functions](#stateful-functions)
|
||||
- [Payable](#payable)
|
||||
- [Payable contracts](#payable-contracts)
|
||||
- [Payable entrypoints](#payable-entrypoints)
|
||||
- [Namespaces](#namespaces)
|
||||
- [Splitting code over multiple files](#splitting-code-over-multiple-files)
|
||||
- [Standard library](#standard-library)
|
||||
- [Types](#types)
|
||||
- [Literals](#literals)
|
||||
- [Arithmetic](#arithmetic)
|
||||
- [Bit fields](#bit-fields)
|
||||
- [Type aliases](#type-aliases)
|
||||
- [Algebraic data types](#algebraic-data-types)
|
||||
- [Lists](#lists)
|
||||
- [Maps and records](#maps-and-records)
|
||||
- [Constructing maps and records](#constructing-maps-and-records)
|
||||
- [Accessing values](#accessing-values)
|
||||
- [Updating a value](#updating-a-value)
|
||||
- [Map implementation](#map-implementation)
|
||||
- [Strings](#strings)
|
||||
- [Chars](#chars)
|
||||
- [Byte arrays](#byte-arrays)
|
||||
- [Cryptographic builins](#cryptographic-builins)
|
||||
- [AEVM note](#aevm-note)
|
||||
- [Authorization interface](#authorization-interface)
|
||||
- [Oracle interface](#oracle-interface)
|
||||
- [Example](#example)
|
||||
- [Sanity checks](#sanity-checks)
|
||||
- [AENS interface](#aens-interface)
|
||||
- [Example](#example-1)
|
||||
- [Events](#events)
|
||||
- [Argument order](#argument-order)
|
||||
- [Compiler pragmas](#compiler-pragmas)
|
||||
- [Exceptions](#exceptions)
|
||||
- [Syntax](#syntax)
|
||||
- [Lexical syntax](#lexical-syntax)
|
||||
- [Comments](#comments)
|
||||
- [Keywords](#keywords)
|
||||
- [Tokens](#tokens)
|
||||
- [Layout blocks](#layout-blocks)
|
||||
- [Notation](#notation)
|
||||
- [Declarations](#declarations)
|
||||
- [Types](#types-1)
|
||||
- [Statements](#statements)
|
||||
- [Expressions](#expressions)
|
||||
- [Operators types](#operators-types)
|
||||
- [Operator precendences](#operator-precendences)
|
||||
- [Examples](#examples)
|
||||
- [Delegation signature](#delegation-signature)
|
||||
|
||||
|
||||
## The Sophia Language
|
||||
# The Sophia Language
|
||||
An Æternity BlockChain Language
|
||||
|
||||
The Sophia is a language in the ML family. It is strongly typed and has
|
||||
@@ -69,6 +10,65 @@ 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**
|
||||
|
||||
- [Language Features](#language-features)
|
||||
- [Contracts](#contracts)
|
||||
- [Calling other contracts](#calling-other-contracts)
|
||||
- [Protected contract calls](#protected-contract-calls)
|
||||
- [Contract factories and child contracts](#contract-factories-and-child-contracts)
|
||||
- [Mutable state](#mutable-state)
|
||||
- [Stateful functions](#stateful-functions)
|
||||
- [Payable](#payable)
|
||||
- [Payable contracts](#payable-contracts)
|
||||
- [Payable entrypoints](#payable-entrypoints)
|
||||
- [Namespaces](#namespaces)
|
||||
- [Splitting code over multiple files](#splitting-code-over-multiple-files)
|
||||
- [Standard library](#standard-library)
|
||||
- [Types](#types)
|
||||
- [Literals](#literals)
|
||||
- [Arithmetic](#arithmetic)
|
||||
- [Bit fields](#bit-fields)
|
||||
- [Type aliases](#type-aliases)
|
||||
- [Algebraic data types](#algebraic-data-types)
|
||||
- [Lists](#lists)
|
||||
- [Maps and records](#maps-and-records)
|
||||
- [Constructing maps and records](#constructing-maps-and-records)
|
||||
- [Accessing values](#accessing-values)
|
||||
- [Updating a value](#updating-a-value)
|
||||
- [Map implementation](#map-implementation)
|
||||
- [Strings](#strings)
|
||||
- [Chars](#chars)
|
||||
- [Byte arrays](#byte-arrays)
|
||||
- [Cryptographic builins](#cryptographic-builins)
|
||||
- [AEVM note](#aevm-note)
|
||||
- [Authorization interface](#authorization-interface)
|
||||
- [Oracle interface](#oracle-interface)
|
||||
- [Example](#example)
|
||||
- [Sanity checks](#sanity-checks)
|
||||
- [AENS interface](#aens-interface)
|
||||
- [Example](#example)
|
||||
- [Events](#events)
|
||||
- [Argument order](#argument-order)
|
||||
- [Compiler pragmas](#compiler-pragmas)
|
||||
- [Exceptions](#exceptions)
|
||||
- [Syntax](#syntax)
|
||||
- [Lexical syntax](#lexical-syntax)
|
||||
- [Comments](#comments)
|
||||
- [Keywords](#keywords)
|
||||
- [Tokens](#tokens)
|
||||
- [Layout blocks](#layout-blocks)
|
||||
- [Notation](#notation)
|
||||
- [Declarations](#declarations)
|
||||
- [Types](#types)
|
||||
- [Statements](#statements)
|
||||
- [Expressions](#expressions)
|
||||
- [Operators types](#operators-types)
|
||||
- [Operator precendences](#operator-precendences)
|
||||
- [Examples](#examples)
|
||||
- [Delegation signature](#delegation-signature)
|
||||
|
||||
## Language Features
|
||||
### 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
|
||||
of a number of type definitions and entrypoint declarations. For instance,
|
||||
|
||||
```javascript
|
||||
```sophia
|
||||
// A contract type
|
||||
contract VotingType =
|
||||
contract interface VotingType =
|
||||
entrypoint vote : string => unit
|
||||
```
|
||||
|
||||
Now given contract address of type `VotingType` you can call the `vote`
|
||||
entrypoint of that contract:
|
||||
|
||||
```javascript
|
||||
```sophia
|
||||
contract VoteTwice =
|
||||
entrypoint voteTwice(v : VotingType, alt : string) =
|
||||
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
|
||||
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) =
|
||||
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)
|
||||
without calling it you can write
|
||||
|
||||
```javascript
|
||||
```sophia
|
||||
entrypoint pay(v : VotingType, amount : int) =
|
||||
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.
|
||||
|
||||
```sophia
|
||||
contract VotingType =
|
||||
contract interface VotingType =
|
||||
entrypoint : vote : string => unit
|
||||
|
||||
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.
|
||||
|
||||
|
||||
#### Contract factories and child contracts
|
||||
|
||||
Since the version 6.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 =
|
||||
stateful 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
|
||||
|
||||
Sophia does not have arbitrary mutable state, but only a limited form of
|
||||
state associated with each contract instance.
|
||||
Sophia does not have arbitrary mutable state, but only a limited form of state
|
||||
associated with each contract instance.
|
||||
|
||||
- Each contract defines a type `state` encapsulating its mutable state.
|
||||
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.
|
||||
For instance,
|
||||
|
||||
```javascript
|
||||
```sophia
|
||||
stateful entrypoint set_state(s : state) =
|
||||
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
|
||||
contract shall be able to receive funds in this way it has to be declared `payable`:
|
||||
|
||||
```javascript
|
||||
```sophia
|
||||
// A payable contract
|
||||
payable contract ExampleContract =
|
||||
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
|
||||
with a non-zero value should be declared `payable`.
|
||||
|
||||
```javascript
|
||||
```sophia
|
||||
payable stateful entrypoint buy(to : address) =
|
||||
if(Call.value > 42)
|
||||
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
|
||||
parameterized. For instance
|
||||
|
||||
```
|
||||
```sophia
|
||||
type number = int
|
||||
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:
|
||||
|
||||
```
|
||||
```sophia
|
||||
function get_left(x : one_or_both('a, 'b)) : option('a) =
|
||||
switch(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:
|
||||
```
|
||||
```sophia
|
||||
function
|
||||
get_left : one_or_both('a, 'b) => option('a)
|
||||
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
|
||||
`list('e)`. For example we can have the following lists:
|
||||
|
||||
```
|
||||
```sophia
|
||||
[1, 33, 2, 666] : list(int)
|
||||
[(1, "aaa"), (10, "jjj"), (666, "the beast")] : list(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.
|
||||
Example syntax:
|
||||
```
|
||||
```sophia
|
||||
[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]
|
||||
```
|
||||
|
||||
Lists can be constructed using the range syntax using special `..` operator:
|
||||
```
|
||||
```sophia
|
||||
[1..4] == [1,2,3,4]
|
||||
```
|
||||
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,
|
||||
possibly different, types. For instance
|
||||
```
|
||||
```sophia
|
||||
record account = { name : string,
|
||||
balance : int,
|
||||
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.
|
||||
For the example above,
|
||||
```
|
||||
```sophia
|
||||
function new_account(name) =
|
||||
{name = name, balance = 0, history = []}
|
||||
```
|
||||
Maps are constructed similarly, with keys enclosed in square brackets
|
||||
```
|
||||
```sophia
|
||||
function example_map() : map(string, int) =
|
||||
{["key1"] = 1, ["key2"] = 2}
|
||||
```
|
||||
@@ -524,7 +556,7 @@ The empty map is written `{}`.
|
||||
#### Accessing values
|
||||
|
||||
Record fields access is written `r.f` and map lookup `m[k]`. For instance,
|
||||
```
|
||||
```sophia
|
||||
function get_balance(a : address, accounts : map(address, account)) =
|
||||
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.
|
||||
|
||||
Updates can be nested:
|
||||
```
|
||||
```sophia
|
||||
function clear_history(a : address, accounts : map(address, account)) : map(address, account) =
|
||||
accounts{ [a].history = [] }
|
||||
```
|
||||
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
|
||||
an account if `a` is not in the map you can write (given a function `empty_account`):
|
||||
```
|
||||
```sophia
|
||||
accounts{ [a = empty_account()].history = [] }
|
||||
```
|
||||
|
||||
@@ -627,7 +659,7 @@ For a functionality documentation refer to the [standard library](sophia_stdlib.
|
||||
#### Example
|
||||
|
||||
Example for an oracle answering questions of type `string` with answers of type `int`:
|
||||
```
|
||||
```sophia
|
||||
contract Oracles =
|
||||
|
||||
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
|
||||
the name if it expires within 1000 blocks:
|
||||
```
|
||||
```sophia
|
||||
stateful entrypoint extend_if_necessary(addr : address, name : string, sig : signature) =
|
||||
switch(AENS.lookup(name))
|
||||
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
|
||||
name:
|
||||
```
|
||||
```sophia
|
||||
stateful entrypoint add_key(addr : address, name : string, key : string,
|
||||
pt : AENS.pointee, sig : signature) =
|
||||
switch(AENS.lookup(name))
|
||||
@@ -768,17 +800,17 @@ The fields can appear in any order.
|
||||
Events are emitted by using the `Chain.event` function. The following function
|
||||
will emit one Event of each kind in the example.
|
||||
|
||||
```
|
||||
```sophia
|
||||
entrypoint emit_events() : () =
|
||||
Chain.event(TheFirstEvent(42))
|
||||
Chain.event(AnotherEvent(Contract.address, "This is not indexed"))
|
||||
Chain.event(Event1(42, 34, "foo"))
|
||||
Chain.event(Event2("This is not indexed", Contract.address))
|
||||
```
|
||||
|
||||
#### Argument order
|
||||
|
||||
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.
|
||||
```
|
||||
```sophia
|
||||
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
|
||||
private payable stateful switch true type record datatype
|
||||
private payable stateful switch true type record datatype main interface
|
||||
```
|
||||
|
||||
#### Tokens
|
||||
@@ -941,7 +973,7 @@ Args ::= '(' Sep(Pattern, ',') ')'
|
||||
Contract declarations must appear at the top-level.
|
||||
|
||||
For example,
|
||||
```
|
||||
```sophia
|
||||
contract Test =
|
||||
type t = int
|
||||
entrypoint add (x : t, y : t) = x + y
|
||||
@@ -1089,7 +1121,7 @@ In order of highest to lowest precedence.
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
```sophia
|
||||
/*
|
||||
* A simple crowd-funding example
|
||||
*/
|
||||
|
||||
+183
-5
@@ -329,9 +329,40 @@ Crypto.verify_sig_secp256k1(msg : hash, pubkey : bytes(64), sig : bytes(64)) : b
|
||||
|
||||
## Auth
|
||||
|
||||
|
||||
#### tx
|
||||
|
||||
```
|
||||
Auth.tx : option(Chain.tx)
|
||||
```
|
||||
|
||||
Where `Chain.tx` is (built-in) defined like:
|
||||
```
|
||||
namespace Chain =
|
||||
record tx = { paying_for : option(Chain.paying_for_tx)
|
||||
, ga_metas : list(Chain.ga_meta_tx)
|
||||
, actor : address
|
||||
, fee : int
|
||||
, ttl : int
|
||||
, tx : Chain.base_tx }
|
||||
|
||||
datatype ga_meta_tx = GAMetaTx(address, int)
|
||||
datatype paying_for_tx = PayingForTx(address, int)
|
||||
datatype base_tx = SpendTx(address, int, string)
|
||||
| OracleRegisterTx | OracleQueryTx | OracleResponseTx | OracleExtendTx
|
||||
| NamePreclaimTx | NameClaimTx(hash) | NameUpdateTx(string)
|
||||
| NameRevokeTx(hash) | NameTransferTx(address, string)
|
||||
| ChannelCreateTx(address) | ChannelDepositTx(address, int) | ChannelWithdrawTx(address, int) |
|
||||
| ChannelForceProgressTx(address) | ChannelCloseMutualTx(address) | ChannelCloseSoloTx(address)
|
||||
| ChannelSlashTx(address) | ChannelSettleTx(address) | ChannelSnapshotSoloTx(address)
|
||||
| ContractCreateTx(int) | ContractCallTx(address, int)
|
||||
| GAAttachTx
|
||||
```
|
||||
|
||||
|
||||
#### tx_hash
|
||||
```
|
||||
Auth.tx_hash : option(Chain.tx)
|
||||
Auth.tx_hash : option(hash)
|
||||
```
|
||||
|
||||
Gets the transaction hash during authentication.
|
||||
@@ -614,7 +645,7 @@ Call.value : int
|
||||
The amount of coins transferred to the contract in the call.
|
||||
|
||||
|
||||
#### gas
|
||||
#### gas_price
|
||||
```
|
||||
Call.gas_price : int
|
||||
```
|
||||
@@ -622,7 +653,15 @@ Call.gas_price : int
|
||||
The gas price of the current call.
|
||||
|
||||
|
||||
#### gas
|
||||
#### fee
|
||||
```
|
||||
Call.fee : int
|
||||
```
|
||||
|
||||
The fee of the current call.
|
||||
|
||||
|
||||
#### gas_left
|
||||
```
|
||||
Call.gas_left() : int
|
||||
```
|
||||
@@ -734,6 +773,127 @@ Chain.gas_limit : int
|
||||
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
|
||||
```
|
||||
Chain.event(e : event) : unit
|
||||
@@ -1056,12 +1216,20 @@ List.unzip(l : list('a * 'b)) : list('a) * list('b)
|
||||
Opposite to the `zip` operation. Takes a list of pairs and returns pair of lists with respective elements on same indices.
|
||||
|
||||
|
||||
#### merge
|
||||
```
|
||||
List.merge(lesser_cmp : ('a, 'a) => bool, l1 : list('a), l2 : list('a)) : list('a)
|
||||
```
|
||||
|
||||
Merges two sorted lists into a single sorted list. O(length(l1) + length(l2))
|
||||
|
||||
|
||||
#### sort
|
||||
```
|
||||
List.sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a)
|
||||
```
|
||||
|
||||
Sorts a list using given comparator. `lesser_cmp(x, y)` should return `true` iff `x < y`. If `lesser_cmp` is not transitive or there exists an element `x` such that `lesser_cmp(x, x)` or there exists a pair of elements `x` and `y` such that `lesser_cmp(x, y) && lesser_cmp(y, x)` then the result is undefined. Currently O(n^2).
|
||||
Sorts a list using given comparator. `lesser_cmp(x, y)` should return `true` iff `x < y`. If `lesser_cmp` is not transitive or there exists an element `x` such that `lesser_cmp(x, x)` or there exists a pair of elements `x` and `y` such that `lesser_cmp(x, y) && lesser_cmp(y, x)` then the result is undefined. O(length(l) * log_2(length(l))).
|
||||
|
||||
|
||||
#### intersperse
|
||||
@@ -1125,7 +1293,17 @@ Escapes `option` wrapping by providing default value for `None`.
|
||||
Option.force(o : option('a)) : 'a
|
||||
```
|
||||
|
||||
Forcefully escapes `option` wrapping assuming it is `Some`. Throws error on `None`.
|
||||
Forcefully escapes the `option` wrapping assuming it is `Some`.
|
||||
Aborts on `None`.
|
||||
|
||||
|
||||
#### force_msg
|
||||
```
|
||||
Option.force_msg(o : option('a), err : string) : 'a
|
||||
```
|
||||
|
||||
Forcefully escapes the `option` wrapping assuming it is `Some`.
|
||||
Aborts with `err` error message on `None`.
|
||||
|
||||
|
||||
#### contains
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
include "String.aes"
|
||||
|
||||
namespace Frac =
|
||||
|
||||
private function gcd(a : int, b : int) =
|
||||
|
||||
+68
-8
@@ -80,6 +80,7 @@ namespace List =
|
||||
* `b` only if `(b - a) mod step == 0`. `step` should be bigger than 0.
|
||||
*/
|
||||
function from_to_step(a : int, b : int, s : int) : list(int) =
|
||||
require(s > 0, "List.from_to_step: non-positive step")
|
||||
from_to_step_(a, b - (b-a) mod s, s, [])
|
||||
private function from_to_step_(a : int, b : int, s : int, acc : list(int)) : list(int) =
|
||||
if(b < a) acc
|
||||
@@ -208,10 +209,13 @@ namespace List =
|
||||
[] => false
|
||||
h::t => if(p(h)) true else any(p, t)
|
||||
|
||||
function sum(l : list(int)) : int = foldl ((a, b) => a + b, 0, l)
|
||||
|
||||
function product(l : list(int)) : int = foldl((a, b) => a * b, 1, l)
|
||||
function sum(l : list(int)) : int = switch(l)
|
||||
[] => 0
|
||||
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.
|
||||
* Drops the tail of the longer list.
|
||||
@@ -235,11 +239,67 @@ namespace List =
|
||||
(h1::t1, h2::t2)
|
||||
|
||||
|
||||
// TODO: Improve?
|
||||
function sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a) = switch(l)
|
||||
[] => []
|
||||
h::t => switch (partition((x) => lesser_cmp(x, h), t))
|
||||
(lesser, bigger) => sort(lesser_cmp, lesser) ++ h::sort(lesser_cmp, bigger)
|
||||
/** Merges two sorted lists using `lt` comparator
|
||||
*/
|
||||
function
|
||||
merge : (('a, 'a) => bool, list('a), list('a)) => list('a)
|
||||
merge(lt, x::xs, y::ys) =
|
||||
if(lt(x, y)) x::merge(lt, xs, y::ys)
|
||||
else y::merge(lt, x::xs, ys)
|
||||
merge(_, [], ys) = ys
|
||||
merge(_, xs, []) = xs
|
||||
|
||||
|
||||
/** Mergesort inspired by
|
||||
* https://hackage.haskell.org/package/base-4.14.1.0/docs/src/Data.OldList.html#sort
|
||||
*/
|
||||
function
|
||||
sort : (('a, 'a) => bool, list('a)) => list('a)
|
||||
sort(_, []) = []
|
||||
sort(lt, l) =
|
||||
merge_all(lt, monotonic_subs(lt, l))
|
||||
|
||||
/** Splits list into compound increasing sublists
|
||||
*/
|
||||
private function
|
||||
monotonic_subs : (('a, 'a) => bool, list('a)) => list(list('a))
|
||||
monotonic_subs(lt, x::y::rest) =
|
||||
if(lt(y, x)) desc(lt, y, [x], rest)
|
||||
else asc(lt, y, [x], rest)
|
||||
monotonic_subs(_, l) = [l]
|
||||
|
||||
/** Extracts the longest descending prefix and proceeds with monotonic split
|
||||
*/
|
||||
private function
|
||||
desc : (('a, 'a) => bool, 'a, list('a), list('a)) => list(list('a))
|
||||
desc(lt, x, acc, h::t) =
|
||||
if(lt(x, h)) (x::acc) :: monotonic_subs(lt, h::t)
|
||||
else desc(lt, h, x::acc, t)
|
||||
desc(_, x, acc, []) = [x::acc]
|
||||
|
||||
/** Extracts the longest ascending prefix and proceeds with monotonic split
|
||||
*/
|
||||
private function
|
||||
asc : (('a, 'a) => bool, 'a, list('a), list('a)) => list(list('a))
|
||||
asc(lt, x, acc, h::t) =
|
||||
if(lt(h, x)) List.reverse(x::acc) :: monotonic_subs(lt, h::t)
|
||||
else asc(lt, h, x::acc, t)
|
||||
asc(_, x, acc, []) = [List.reverse(x::acc)]
|
||||
|
||||
/** Merges list of sorted lists
|
||||
*/
|
||||
private function
|
||||
merge_all : (('a, 'a) => bool, list(list('a))) => list('a)
|
||||
merge_all(_, [part]) = part
|
||||
merge_all(lt, parts) = merge_all(lt, merge_pairs(lt, parts))
|
||||
|
||||
/** Single round of `merge_all` – pairs of lists in a list of list
|
||||
*/
|
||||
private function
|
||||
merge_pairs : (('a, 'a) => bool, list(list('a))) => list(list('a))
|
||||
merge_pairs(lt, x::y::rest) = merge(lt, x, y) :: merge_pairs(lt, rest)
|
||||
merge_pairs(_, l) = l
|
||||
|
||||
|
||||
/** Puts `delim` between every two members of the list
|
||||
*/
|
||||
|
||||
@@ -26,6 +26,12 @@ namespace Option =
|
||||
None => abort("Forced None value")
|
||||
Some(x) => x
|
||||
|
||||
/** Assume it is `Some` with custom error message
|
||||
*/
|
||||
function force_msg(o : option('a), err : string) : 'a = switch(o)
|
||||
None => abort(err)
|
||||
Some(x) => x
|
||||
|
||||
function contains(e : 'a, o : option('a)) = o == Some(e)
|
||||
|
||||
function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o)
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
{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,"05dfd7f"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
, {eblake2, "1.0.0"}
|
||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
||||
@@ -15,7 +15,7 @@
|
||||
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
||||
]}.
|
||||
|
||||
{relx, [{release, {aesophia, "4.3.0"},
|
||||
{relx, [{release, {aesophia, "6.0.2"},
|
||||
[aesophia, aebytecode, getopt]},
|
||||
|
||||
{dev_mode, true},
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{"1.1.0",
|
||||
[{<<"aebytecode">>,
|
||||
{git,"https://github.com/aeternity/aebytecode.git",
|
||||
{ref,"7f0d3090d4dc6c4d5fca7645b0c21eb0e65ad208"}},
|
||||
{ref,"05dfd7ffc7fb1e07ecc0b1e516da571f56d7dc8f"}},
|
||||
0},
|
||||
{<<"aeserialization">>,
|
||||
{git,"https://github.com/aeternity/aeserialization.git",
|
||||
|
||||
+13
-7
@@ -21,6 +21,8 @@
|
||||
, json_encode_expr/1
|
||||
, json_encode_type/1]).
|
||||
|
||||
-include("aeso_utils.hrl").
|
||||
|
||||
-type aci_type() :: json | string.
|
||||
-type json() :: jsx:json_term().
|
||||
-type json_text() :: binary().
|
||||
@@ -68,9 +70,7 @@ do_contract_interface(Type, Contract, Options) when is_binary(Contract) ->
|
||||
do_contract_interface(Type, ContractString, Options) ->
|
||||
try
|
||||
Ast = aeso_compiler:parse(ContractString, Options),
|
||||
%% io:format("~p\n", [Ast]),
|
||||
{TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
|
||||
%% io:format("~p\n", [TypedAst]),
|
||||
from_typed_ast(Type, TypedAst)
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
@@ -83,7 +83,7 @@ from_typed_ast(Type, TypedAst) ->
|
||||
string -> do_render_aci_json(JArray)
|
||||
end.
|
||||
|
||||
encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
|
||||
encode_contract(Contract = {Head, _, {con, _, Name}, _}) when ?IS_CONTRACT_HEAD(Head) ->
|
||||
C0 = #{name => encode_name(Name)},
|
||||
|
||||
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)),
|
||||
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}, _}) ->
|
||||
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ],
|
||||
#{namespace => #{name => encode_name(Name),
|
||||
@@ -232,13 +232,19 @@ do_render_aci_json(Json) ->
|
||||
{ok, list_to_binary(string:join(DecodedContracts, "\n"))}.
|
||||
|
||||
decode_contract(#{contract := #{name := Name,
|
||||
kind := Kind,
|
||||
payable := Payable,
|
||||
type_defs := Ts0,
|
||||
functions := Fs} = C}) ->
|
||||
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
|
||||
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
|
||||
[ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0,
|
||||
[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_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] ->
|
||||
["namespace ", io_lib:format("~s", [Name])," =\n",
|
||||
@@ -332,10 +338,10 @@ payable(false) -> "".
|
||||
|
||||
%% #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)].
|
||||
|
||||
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) ].
|
||||
|
||||
is_fun({letfun, _, _, _, _, _}) -> true;
|
||||
|
||||
+324
-67
@@ -18,7 +18,9 @@
|
||||
, 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()]}
|
||||
| {tuple_t, aeso_syntax:ann(), [utype()]}
|
||||
| aeso_syntax:id() | aeso_syntax:qid()
|
||||
@@ -39,6 +41,7 @@
|
||||
element(1, T) =:= qcon).
|
||||
|
||||
-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()}.
|
||||
|
||||
-type pos() :: aeso_errors:pos().
|
||||
@@ -73,7 +76,10 @@
|
||||
-record(is_contract_constraint,
|
||||
{ contract_t :: utype(),
|
||||
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{}.
|
||||
@@ -96,7 +102,7 @@
|
||||
-type qname() :: [string()].
|
||||
-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 type_info() :: {aeso_syntax:ann(), typedef()}.
|
||||
@@ -123,13 +129,14 @@
|
||||
, in_pattern = false :: boolean()
|
||||
, stateful = false :: boolean()
|
||||
, current_function = none :: none | aeso_syntax:id()
|
||||
, what = top :: top | namespace | contract | main_contract
|
||||
, what = top :: top | namespace | contract | contract_interface
|
||||
}).
|
||||
|
||||
-type env() :: #env{}.
|
||||
|
||||
-define(PRINT_TYPES(Fmt, Args),
|
||||
when_option(pp_types, fun () -> io:format(Fmt, Args) end)).
|
||||
-define(CONSTRUCTOR_MOCK_NAME, "#__constructor__#").
|
||||
|
||||
%% -- Environment manipulation -----------------------------------------------
|
||||
|
||||
@@ -191,9 +198,9 @@ bind_fun(X, Type, Env) ->
|
||||
force_bind_fun(X, Type, Env = #env{ what = What }) ->
|
||||
Ann = aeso_syntax:get_ann(Type),
|
||||
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};
|
||||
What == contract -> {contract_fun, Ann, Type};
|
||||
What == contract_interface -> {contract_fun, Ann, Type};
|
||||
true -> {Ann, Type}
|
||||
end,
|
||||
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}}.
|
||||
|
||||
-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),
|
||||
Sys = [{origin, system}],
|
||||
Fields = [ {field_t, AnnF, Entrypoint, contract_call_type(Type)}
|
||||
|| {fun_decl, AnnF, Entrypoint, Type} <- Contents ] ++
|
||||
%% Predefined fields
|
||||
[ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ],
|
||||
Fields =
|
||||
[ {field_t, AnnF, Entrypoint, contract_call_type(Type)}
|
||||
|| {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
|
||||
[ {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,
|
||||
kind = contract,
|
||||
field_t = Type,
|
||||
@@ -396,8 +430,11 @@ global_env() ->
|
||||
Map = fun(A, B) -> {app_t, Ann, {id, Ann, "map"}, [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,
|
||||
FunC1 = fun(C, S, T) -> {type_sig, Ann, C, [], [S], T} end,
|
||||
Fun = fun(Ts, T) -> FunC(none, Ts, 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,
|
||||
%% Lambda1 = fun(S, T) -> Lambda([S], 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)},
|
||||
{"actor", Address}, {"fee", Int}, {"ttl", Int}, {"tx", BaseTx}],
|
||||
TxType = {record_t, [FldT(N, T) || {N, T} <- TxFlds ]},
|
||||
Stateful = fun(T) -> setelement(2, T, [stateful|element(2, T)]) end,
|
||||
|
||||
Fee = Int,
|
||||
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
|
||||
@@ -443,6 +481,7 @@ global_env() ->
|
||||
{"require", Fun([Bool, String], Unit)}])
|
||||
, types = MkDefs(
|
||||
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
|
||||
{"void", 0},
|
||||
{"unit", {[], {alias_t, Unit}}},
|
||||
{"hash", {[], {alias_t, Bytes(32)}}},
|
||||
{"signature", {[], {alias_t, Bytes(64)}}},
|
||||
@@ -463,6 +502,23 @@ global_env() ->
|
||||
{"block_height", Int},
|
||||
{"difficulty", 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
|
||||
{"GAMetaTx", Fun([Address, Int], GAMetaTx)},
|
||||
{"PayingForTx", Fun([Address, Int], PayForTx)},
|
||||
@@ -505,6 +561,7 @@ global_env() ->
|
||||
{"caller", Address},
|
||||
{"value", Int},
|
||||
{"gas_price", Int},
|
||||
{"fee", Int},
|
||||
{"gas_left", Fun([], Int)}])
|
||||
},
|
||||
|
||||
@@ -699,9 +756,13 @@ infer(Contracts, Options) ->
|
||||
try
|
||||
Env = init_env(Options),
|
||||
create_options(Options),
|
||||
ets_new(defined_contracts, [bag]),
|
||||
ets_new(type_vars, [set]),
|
||||
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} =
|
||||
case proplists:get_value(dont_unfold, Options, false) of
|
||||
true -> {Env1, Decls, Decls};
|
||||
@@ -719,12 +780,21 @@ infer(Contracts, Options) ->
|
||||
-spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) ->
|
||||
{env(), [aeso_syntax:decl()]}.
|
||||
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.
|
||||
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),
|
||||
Contract1 = {contract, Ann, ConName, Code1},
|
||||
Contract1 = {Contract, Ann, ConName, Code1},
|
||||
Env2 = pop_scope(Env1),
|
||||
Env3 = bind_contract(Contract1, Env2),
|
||||
infer1(Env3, Rest, [Contract1 | Acc], Options);
|
||||
@@ -737,6 +807,25 @@ infer1(Env, [{pragma, _, _} | Rest], Acc, Options) ->
|
||||
%% Pragmas are checked in check_modifiers
|
||||
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) ->
|
||||
case get_scope(Env, qname(Name)) of
|
||||
false -> ok;
|
||||
@@ -746,15 +835,17 @@ check_scope_name_clash(Env, Kind, Name) ->
|
||||
destroy_and_report_type_errors(Env)
|
||||
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()]}.
|
||||
infer_contract_top(Env, Kind, Defs0, Options) ->
|
||||
create_type_errors(),
|
||||
Defs = desugar(Defs0),
|
||||
destroy_and_report_type_errors(Env),
|
||||
infer_contract(Env, Kind, Defs, Options).
|
||||
|
||||
%% infer_contract takes a proplist mapping global names to types, and
|
||||
%% 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) ->
|
||||
create_type_errors(),
|
||||
Defs01 = process_blocks(Defs0),
|
||||
@@ -770,19 +861,19 @@ infer_contract(Env0, What, Defs0, Options) ->
|
||||
({fun_decl, _, _, _}) -> prototype;
|
||||
(_) -> unexpected
|
||||
end,
|
||||
Get = fun(K) -> [ Def || Def <- Defs, Kind(Def) == K ] end,
|
||||
{Env1, TypeDefs} = check_typedefs(Env, Get(type)),
|
||||
Get = fun(K, In) -> [ Def || Def <- In, Kind(Def) == K ] end,
|
||||
{Env1, TypeDefs} = check_typedefs(Env, Get(type, Defs)),
|
||||
create_type_errors(),
|
||||
check_unexpected(Get(unexpected)),
|
||||
check_unexpected(Get(unexpected, Defs)),
|
||||
Env2 =
|
||||
case What of
|
||||
namespace -> Env1;
|
||||
contract -> Env1;
|
||||
main_contract -> bind_state(Env1) %% bind state and put
|
||||
namespace -> Env1;
|
||||
contract_interface -> Env1;
|
||||
contract -> bind_state(Env1) %% bind state and put
|
||||
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),
|
||||
Functions = Get(function),
|
||||
Functions = Get(function, Defs),
|
||||
%% Check for duplicates in Functions (we turn it into a map below)
|
||||
FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}};
|
||||
({fun_clauses, Ann, {id, _, Fun}, _, _}) -> {Fun, {tuple_t, Ann, []}} end,
|
||||
@@ -792,11 +883,11 @@ infer_contract(Env0, What, Defs0, Options) ->
|
||||
check_reserved_entrypoints(FunMap),
|
||||
DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_ids(Def) end, FunMap),
|
||||
SCCs = aeso_utils:scc(DepGraph),
|
||||
%% io:format("Dependency sorted functions:\n ~p\n", [SCCs]),
|
||||
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
|
||||
%% Check that `init` doesn't read or write the state
|
||||
check_state_dependencies(Env4, Defs1),
|
||||
destroy_and_report_type_errors(Env4),
|
||||
%% Add inferred types of definitions
|
||||
{Env4, TypeDefs ++ Decls ++ Defs1}.
|
||||
|
||||
%% Restructure blocks into multi-clause fundefs (`fun_clauses`).
|
||||
@@ -828,9 +919,9 @@ expose_internals(Defs, What) ->
|
||||
[ begin
|
||||
Ann = element(2, Def),
|
||||
NewAnn = case What of
|
||||
namespace -> [A ||A <- Ann, A /= {private, true}, A /= private];
|
||||
main_contract -> [{entrypoint, true}|Ann]; % minor duplication
|
||||
contract -> Ann
|
||||
namespace -> [A ||A <- Ann, A /= {private, true}, A /= private];
|
||||
contract -> [{entrypoint, true}|Ann]; % minor duplication
|
||||
contract_interface -> Ann
|
||||
end,
|
||||
Def1 = setelement(2, Def, NewAnn),
|
||||
case Def1 of % fix inner clauses
|
||||
@@ -905,15 +996,16 @@ check_modifiers(Env, Contracts) ->
|
||||
check_modifiers_(Env, Contracts),
|
||||
destroy_and_report_type_errors(Env).
|
||||
|
||||
check_modifiers_(Env, [{contract, _, Con, Decls} | Rest]) ->
|
||||
IsMain = Rest == [],
|
||||
check_modifiers_(Env, [{Contract, _, Con, Decls} | Rest])
|
||||
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
IsInterface = Contract =:= contract_interface,
|
||||
check_modifiers1(contract, Decls),
|
||||
case {lists:keymember(letfun, 1, Decls),
|
||||
[ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of
|
||||
{true, []} -> type_error({contract_has_no_entrypoints, Con});
|
||||
_ when not IsMain ->
|
||||
case [ {Ann, Id} || {letfun, Ann, Id, _, _, _} <- Decls ] of
|
||||
[{Ann, Id} | _] -> type_error({definition_in_non_main_contract, Ann, Id});
|
||||
_ when IsInterface ->
|
||||
case [ {AnnF, Id} || {letfun, AnnF, Id, _, _, _} <- Decls ] of
|
||||
[{AnnF, Id} | _] -> type_error({definition_in_contract_interface, AnnF, Id});
|
||||
[] -> ok
|
||||
end;
|
||||
_ -> ok
|
||||
@@ -1406,8 +1498,7 @@ infer_expr(Env, {typed, As, Body, Type}) ->
|
||||
{typed, _, NewBody, NewType} = check_expr(Env, Body, Type1),
|
||||
{typed, As, NewBody, NewType};
|
||||
infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
|
||||
NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ],
|
||||
Args = Args0 -- NamedArgs,
|
||||
{NamedArgs, Args} = split_args(Args0),
|
||||
case aeso_syntax:get_ann(format, Ann) of
|
||||
infix ->
|
||||
infer_op(Env, Ann, Fun, Args, fun infer_infix/1);
|
||||
@@ -1416,13 +1507,13 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
|
||||
_ ->
|
||||
NamedArgsVar = fresh_uvar(Ann),
|
||||
NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ],
|
||||
%% TODO: named args constraints
|
||||
NewFun={typed, _, _, FunType} = infer_expr(Env, Fun),
|
||||
NewFun0 = infer_expr(Env, Fun),
|
||||
NewArgs = [infer_expr(Env, A) || A <- Args],
|
||||
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),
|
||||
ResultType = fresh_uvar(Ann),
|
||||
When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes},
|
||||
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When),
|
||||
add_named_argument_constraint(
|
||||
#dependent_type_constraint{ named_args_t = NamedArgsVar,
|
||||
@@ -1430,7 +1521,7 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
|
||||
general_type = GeneralResultType,
|
||||
specialized_type = ResultType,
|
||||
context = {check_return, App} }),
|
||||
{typed, Ann, {app, Ann, NewFun, NamedArgs1 ++ NewArgs}, dereference(ResultType)}
|
||||
{typed, Ann, {app, Ann, NewFun1, NamedArgs1 ++ NewArgs}, dereference(ResultType)}
|
||||
end;
|
||||
infer_expr(Env, {'if', Attrs, Cond, Then, Else}) ->
|
||||
NewCond = check_expr(Env, Cond, {id, Attrs, "bool"}),
|
||||
@@ -1535,6 +1626,62 @@ infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) ->
|
||||
type_error({missing_body_for_let, Attrs}),
|
||||
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}) ->
|
||||
CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E),
|
||||
check_stateful_named_arg(Env, Id, E),
|
||||
@@ -1689,11 +1836,20 @@ free_vars(L) when is_list(L) ->
|
||||
[V || Elem <- L,
|
||||
V <- free_vars(Elem)].
|
||||
|
||||
next_count() ->
|
||||
V = case get(counter) of
|
||||
undefined ->
|
||||
0;
|
||||
X -> X
|
||||
end,
|
||||
put(counter, V + 1),
|
||||
V.
|
||||
|
||||
%% Clean up all the ets tables (in case of an exception)
|
||||
|
||||
ets_tables() ->
|
||||
[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() ->
|
||||
[ catch ets_delete(Tab) || Tab <- ets_tables() ],
|
||||
@@ -1734,6 +1890,18 @@ ets_tab2list(Name) ->
|
||||
TabId = ets_tabid(Name),
|
||||
ets:tab2list(TabId).
|
||||
|
||||
ets_insert_ordered(_, []) -> true;
|
||||
ets_insert_ordered(Name, [H|T]) ->
|
||||
ets_insert_ordered(Name, H),
|
||||
ets_insert_ordered(Name, T);
|
||||
ets_insert_ordered(Name, Object) ->
|
||||
Count = next_count(),
|
||||
TabId = ets_tabid(Name),
|
||||
ets:insert(TabId, {Count, Object}).
|
||||
|
||||
ets_tab2list_ordered(Name) ->
|
||||
[E || {_, E} <- ets_tab2list(Name)].
|
||||
|
||||
%% Options
|
||||
|
||||
create_options(Options) ->
|
||||
@@ -1769,17 +1937,17 @@ destroy_and_report_unsolved_constraints(Env) ->
|
||||
%% -- Named argument constraints --
|
||||
|
||||
create_named_argument_constraints() ->
|
||||
ets_new(named_argument_constraints, [bag]).
|
||||
ets_new(named_argument_constraints, [ordered_set]).
|
||||
|
||||
destroy_named_argument_constraints() ->
|
||||
ets_delete(named_argument_constraints).
|
||||
|
||||
get_named_argument_constraints() ->
|
||||
ets_tab2list(named_argument_constraints).
|
||||
ets_tab2list_ordered(named_argument_constraints).
|
||||
|
||||
-spec add_named_argument_constraint(named_argument_constraint()) -> ok.
|
||||
add_named_argument_constraint(Constraint) ->
|
||||
ets_insert(named_argument_constraints, Constraint),
|
||||
ets_insert_ordered(named_argument_constraints, Constraint),
|
||||
ok.
|
||||
|
||||
solve_named_argument_constraints(Env) ->
|
||||
@@ -1791,7 +1959,7 @@ solve_named_argument_constraints(Env, Constraints0) ->
|
||||
[ C || C <- dereference_deep(Constraints0),
|
||||
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.
|
||||
check_named_argument_constraint(_Env, #named_argument_constraint{ args = {uvar, _, _} }) ->
|
||||
unsolved;
|
||||
@@ -1853,14 +2021,14 @@ destroy_and_report_unsolved_named_argument_constraints(Env) ->
|
||||
| {add_bytes, aeso_syntax:ann(), concat | split, utype(), utype(), utype()}.
|
||||
|
||||
create_bytes_constraints() ->
|
||||
ets_new(bytes_constraints, [bag]).
|
||||
ets_new(bytes_constraints, [ordered_set]).
|
||||
|
||||
get_bytes_constraints() ->
|
||||
ets_tab2list(bytes_constraints).
|
||||
ets_tab2list_ordered(bytes_constraints).
|
||||
|
||||
-spec add_bytes_constraint(byte_constraint()) -> true.
|
||||
add_bytes_constraint(Constraint) ->
|
||||
ets_insert(bytes_constraints, Constraint).
|
||||
ets_insert_ordered(bytes_constraints, Constraint).
|
||||
|
||||
solve_bytes_constraints(Env) ->
|
||||
[ solve_bytes_constraint(Env, C) || C <- get_bytes_constraints() ],
|
||||
@@ -1914,18 +2082,18 @@ check_bytes_constraint(Env, {add_bytes, Ann, Fun, A0, B0, C0}) ->
|
||||
|
||||
create_field_constraints() ->
|
||||
%% A relation from uvars to constraints
|
||||
ets_new(field_constraints, [bag]).
|
||||
ets_new(field_constraints, [ordered_set]).
|
||||
|
||||
destroy_field_constraints() ->
|
||||
ets_delete(field_constraints).
|
||||
|
||||
-spec constrain([field_constraint()]) -> true.
|
||||
constrain(FieldConstraints) ->
|
||||
ets_insert(field_constraints, FieldConstraints).
|
||||
ets_insert_ordered(field_constraints, FieldConstraints).
|
||||
|
||||
-spec get_field_constraints() -> [field_constraint()].
|
||||
get_field_constraints() ->
|
||||
ets_tab2list(field_constraints).
|
||||
ets_tab2list_ordered(field_constraints).
|
||||
|
||||
solve_field_constraints(Env) ->
|
||||
FieldCs =
|
||||
@@ -1955,12 +2123,20 @@ check_record_create_constraints(Env, [C | Cs]) ->
|
||||
end,
|
||||
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, [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)),
|
||||
case lookup_type(Env, record_type_name(Type1)) of
|
||||
{_, {_Ann, {[], {contract_t, _}}}} -> ok;
|
||||
TypeName = record_type_name(Type1),
|
||||
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})
|
||||
end,
|
||||
check_is_contract_constraints(Env, Cs).
|
||||
@@ -2286,8 +2462,13 @@ unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) ->
|
||||
unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, Else2}, When) ->
|
||||
unify(Env, Then1, Then2, When) andalso
|
||||
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)
|
||||
when length(Args1) == length(Args2) ->
|
||||
when length(Args1) == length(Args2) ->
|
||||
unify(Env, Named1, Named2, When) andalso
|
||||
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
|
||||
unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When)
|
||||
@@ -2296,6 +2477,9 @@ unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When
|
||||
unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, When)
|
||||
when length(As) == length(Bs) ->
|
||||
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
|
||||
%% arguments are represented as applications to an empty list of
|
||||
%% parameters or not. We therefore allow them to unify.
|
||||
@@ -2353,8 +2537,6 @@ occurs_check1(R, [H | T]) ->
|
||||
occurs_check(R, H) orelse occurs_check(R, T);
|
||||
occurs_check1(_, []) -> false.
|
||||
|
||||
fresh_uvar([{origin, system}]) ->
|
||||
error(oh_no_you_dont);
|
||||
fresh_uvar(Attrs) ->
|
||||
{uvar, Attrs, make_ref()}.
|
||||
|
||||
@@ -2403,7 +2585,11 @@ apply_typesig_constraint(Ann, address_to_contract, {fun_t, _, [], [_], Type}) ->
|
||||
apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [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]}}) ->
|
||||
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
|
||||
%% succession of tvars.
|
||||
@@ -2532,6 +2718,9 @@ mk_error({not_a_contract_type, Type, Cxt}) ->
|
||||
end,
|
||||
{Pos, Cxt1} =
|
||||
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} ->
|
||||
{pos(Lit),
|
||||
io_lib:format("when checking that the contract literal\n~s\n"
|
||||
@@ -2630,7 +2819,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",
|
||||
[Name, pp_loc(Pos)]),
|
||||
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",
|
||||
[Name, pp_loc(Pos)]),
|
||||
mk_t_err(pos(Pos), Msg);
|
||||
@@ -2705,8 +2894,8 @@ mk_error({contract_has_no_entrypoints, Con}) ->
|
||||
"contract functions must be declared with the 'entrypoint' keyword instead of\n"
|
||||
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]),
|
||||
mk_t_err(pos(Con), Msg);
|
||||
mk_error({definition_in_non_main_contract, Ann, {id, _, Id}}) ->
|
||||
Msg = "Only the main contract can contain defined functions or entrypoints.\n",
|
||||
mk_error({definition_in_contract_interface, Ann, {id, _, Id}}) ->
|
||||
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]),
|
||||
mk_t_err(pos(Ann), Msg, Cxt);
|
||||
mk_error({unbound_type, Type}) ->
|
||||
@@ -2772,6 +2961,32 @@ mk_error({mixed_record_and_map, Expr}) ->
|
||||
mk_error({named_argument_must_be_literal_bool, Name, Arg}) ->
|
||||
Msg = io_lib:format("Invalid '~s' argument\n~s\nIt must be either 'true' or 'false'.", [Name, pp_expr(" ", instantiate(Arg))]),
|
||||
mk_t_err(pos(Arg), Msg);
|
||||
mk_error({conflicting_updates_for_field, Upd, Key}) ->
|
||||
Msg = io_lib:format("Conflicting updates for field '~s'\n", [Key]),
|
||||
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) ->
|
||||
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
|
||||
mk_t_err(pos(0, 0), Msg).
|
||||
@@ -2807,6 +3022,12 @@ pp_when({field_constraint, FieldType0, InferredType0, Fld}) ->
|
||||
InferredType = instantiate(InferredType0),
|
||||
{pos(Fld),
|
||||
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} ->
|
||||
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),
|
||||
@@ -2829,6 +3050,13 @@ pp_when({record_constraint, RecType0, InferredType0, Fld}) ->
|
||||
InferredType = instantiate(InferredType0),
|
||||
{Pos, WhyRec} = pp_why_record(Fld),
|
||||
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} ->
|
||||
{Pos,
|
||||
io_lib:format("when checking that the record type\n~s\n~s\n"
|
||||
@@ -2882,17 +3110,42 @@ pp_when({check_named_arg_constraint, C}) ->
|
||||
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)]),
|
||||
{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), ""}.
|
||||
|
||||
-spec pp_why_record(why_record()) -> {pos(), iolist()}.
|
||||
pp_why_record(Fld = {field, _Ann, LV, _Id, _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({var_args, Ann, Fun}) ->
|
||||
{pos(Ann),
|
||||
io_lib:format("arising from resolution of variadic function ~s (at ~s)",
|
||||
[pp_expr(Fun), pp_loc(Fun)])};
|
||||
pp_why_record(Fld = {field, _Ann, LV, _E}) ->
|
||||
{pos(Fld),
|
||||
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}) ->
|
||||
{pos(Rec),
|
||||
io_lib:format("arising from the projection of the field ~s (at ~s)",
|
||||
@@ -2912,9 +3165,13 @@ pp_typed(Label, {typed, _, Expr, _}, Type) ->
|
||||
pp_typed(Label, Expr, Type) ->
|
||||
pp_expr(Label, {typed, [], Expr, Type}).
|
||||
|
||||
pp_expr(Expr) ->
|
||||
pp_expr("", Expr).
|
||||
pp_expr(Label, Expr) ->
|
||||
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:expr(Expr, [show_generated]))).
|
||||
|
||||
pp_type(Type) ->
|
||||
pp_type("", Type).
|
||||
pp_type(Label, Type) ->
|
||||
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated]))).
|
||||
|
||||
@@ -3003,7 +3260,7 @@ desugar_updates([Upd | Updates]) ->
|
||||
{More, Updates1} = updates_key(Key, Updates),
|
||||
%% Check conflicts
|
||||
case length([ [] || [] <- [Rest | More] ]) of
|
||||
N when N > 1 -> error({conflicting_updates_for_field, Upd, Key});
|
||||
N when N > 1 -> type_error({conflicting_updates_for_field, Upd, Key});
|
||||
_ -> ok
|
||||
end,
|
||||
[MakeField(lists:append([Rest | More])) | desugar_updates(Updates1)].
|
||||
|
||||
+116
-57
@@ -12,6 +12,8 @@
|
||||
-export([ast_to_fcode/2, format_fexpr/1]).
|
||||
-export_type([fcode/0, fexpr/0, fun_def/0]).
|
||||
|
||||
-include("aeso_utils.hrl").
|
||||
|
||||
%% -- Type definitions -------------------------------------------------------
|
||||
|
||||
-type option() :: term().
|
||||
@@ -53,6 +55,7 @@
|
||||
| {oracle_pubkey, binary()}
|
||||
| {oracle_query_id, binary()}
|
||||
| {bool, false | true}
|
||||
| {contract_code, string()} %% for CREATE, by name
|
||||
| {typerep, ftype()}.
|
||||
|
||||
-type fexpr() :: {lit, flit()}
|
||||
@@ -136,24 +139,27 @@
|
||||
-type type_env() :: #{ sophia_name() => type_def() }.
|
||||
-type fun_env() :: #{ sophia_name() => {fun_name(), non_neg_integer()} }.
|
||||
-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()}
|
||||
| {abstract_contract, string()}.
|
||||
|
||||
-type state_layout() :: {tuple, [state_layout()]} | {reg, state_reg()}.
|
||||
|
||||
-type env() :: #{ type_env := type_env(),
|
||||
fun_env := fun_env(),
|
||||
con_env := con_env(),
|
||||
event_type => aeso_syntax:typedef(),
|
||||
builtins := builtins(),
|
||||
options := [option()],
|
||||
state_layout => state_layout(),
|
||||
context => context(),
|
||||
vars => [var_name()],
|
||||
functions := #{ fun_name() => fun_def() } }.
|
||||
-type env() :: #{ type_env := type_env(),
|
||||
fun_env := fun_env(),
|
||||
con_env := con_env(),
|
||||
child_con_env := child_con_env(),
|
||||
event_type => aeso_syntax:typedef(),
|
||||
builtins := builtins(),
|
||||
options := [option()],
|
||||
state_layout => state_layout(),
|
||||
context => context(),
|
||||
vars => [var_name()],
|
||||
functions := #{ fun_name() => fun_def() }
|
||||
}.
|
||||
|
||||
-define(HASH_BYTES, 32).
|
||||
|
||||
@@ -161,17 +167,26 @@
|
||||
|
||||
%% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2
|
||||
%% 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) ->
|
||||
Verbose = lists:member(pp_fcode, Options),
|
||||
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],
|
||||
FCode2 = optimize_fcode(FCode1),
|
||||
[ io:format("-- After optimization --\n~s\n\n", [format_fcode(FCode2)]) || Verbose, FCode2 /= FCode1 ],
|
||||
FCode3 = lambda_lift(FCode2),
|
||||
[ io:format("-- After lambda lifting --\n~s\n\n", [format_fcode(FCode3)]) || Verbose, FCode3 /= FCode2 ],
|
||||
clear_fresh_names(),
|
||||
FCode3.
|
||||
|
||||
%% -- Environment ------------------------------------------------------------
|
||||
@@ -182,6 +197,7 @@ init_env(Options) ->
|
||||
#{ type_env => init_type_env(),
|
||||
fun_env => #{},
|
||||
builtins => builtins(),
|
||||
child_con_env => #{},
|
||||
con_env => #{["None"] => #con_tag{ tag = 0, arities = [0, 1] },
|
||||
["Some"] => #con_tag{ tag = 1, arities = [0, 1] },
|
||||
["RelativeTTL"] => #con_tag{ tag = 0, arities = [1, 1] },
|
||||
@@ -217,7 +233,8 @@ init_env(Options) ->
|
||||
["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities }
|
||||
},
|
||||
options => Options,
|
||||
functions => #{} }.
|
||||
functions => #{}
|
||||
}.
|
||||
|
||||
-spec builtins() -> builtins().
|
||||
builtins() ->
|
||||
@@ -227,9 +244,9 @@ builtins() ->
|
||||
Scopes = [{[], [{"abort", 1}, {"require", 2}]},
|
||||
{["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", 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}]},
|
||||
{["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none},
|
||||
{["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none}, {"fee", none},
|
||||
{"gas_left", 0}]},
|
||||
{["Oracle"], [{"register", 4}, {"expiry", 1}, {"query_fee", 1}, {"query", 5}, {"get_question", 2},
|
||||
{"respond", 4}, {"extend", 3}, {"get_answer", 2},
|
||||
@@ -307,31 +324,45 @@ get_option(Opt, Env, Default) ->
|
||||
|
||||
%% -- Compilation ------------------------------------------------------------
|
||||
|
||||
-spec to_fcode(env(), aeso_syntax:ast()) -> fcode().
|
||||
to_fcode(Env, [{contract, Attrs, MainCon = {con, _, Main}, Decls}]) ->
|
||||
#{ builtins := Builtins } = Env,
|
||||
MainEnv = Env#{ context => {main_contract, Main},
|
||||
builtins => Builtins#{[Main, "state"] => {get_state, none},
|
||||
[Main, "put"] => {set_state, 1},
|
||||
[Main, "Chain", "event"] => {chain_event, 1}} },
|
||||
#{ functions := Funs } = Env1 =
|
||||
decls_to_fcode(MainEnv, Decls),
|
||||
StateType = lookup_type(Env1, [Main, "state"], [], {tuple, []}),
|
||||
EventType = lookup_type(Env1, [Main, "event"], [], none),
|
||||
StateLayout = state_layout(Env1),
|
||||
Payable = proplists:get_value(payable, Attrs, false),
|
||||
#{ contract_name => Main,
|
||||
state_type => StateType,
|
||||
state_layout => StateLayout,
|
||||
event_type => EventType,
|
||||
payable => Payable,
|
||||
functions => add_init_function(Env1, MainCon, StateType,
|
||||
add_event_function(Env1, EventType, Funs)) };
|
||||
to_fcode(_Env, [NotContract]) ->
|
||||
fcode_error({last_declaration_must_be_contract, NotContract});
|
||||
to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) ->
|
||||
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls),
|
||||
to_fcode(Env1, Code);
|
||||
-spec to_fcode(env(), aeso_syntax:ast()) -> {env(), fcode()}.
|
||||
to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest])
|
||||
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
case Contract =:= contract_interface of
|
||||
false ->
|
||||
#{ builtins := Builtins } = Env,
|
||||
ConEnv = maps:remove(state_layout,
|
||||
Env#{ context => {contract_def, Name},
|
||||
builtins => Builtins#{[Name, "state"] => {get_state, none},
|
||||
[Name, "put"] => {set_state, 1},
|
||||
[Name, "Chain", "event"] => {chain_event, 1}} }),
|
||||
#{ functions := PrevFuns } = ConEnv,
|
||||
#{ functions := Funs } = Env1 =
|
||||
decls_to_fcode(ConEnv, Decls),
|
||||
StateType = lookup_type(Env1, [Name, "state"], [], {tuple, []}),
|
||||
EventType = lookup_type(Env1, [Name, "event"], [], none),
|
||||
StateLayout = state_layout(Env1),
|
||||
Payable = proplists:get_value(payable, Attrs, false),
|
||||
ConFcode = #{ contract_name => Name,
|
||||
state_type => StateType,
|
||||
state_layout => StateLayout,
|
||||
event_type => EventType,
|
||||
payable => Payable,
|
||||
functions => add_init_function(
|
||||
Env1, Con, StateType,
|
||||
add_event_function(Env1, EventType, Funs)) },
|
||||
case Contract of
|
||||
contract_main -> [] = Rest, {Env1, ConFcode};
|
||||
contract_child ->
|
||||
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]) ->
|
||||
Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls),
|
||||
to_fcode(Env1, Code).
|
||||
@@ -341,13 +372,11 @@ decls_to_fcode(Env, Decls) ->
|
||||
%% First compute mapping from Sophia names to fun_names and add it to the
|
||||
%% environment.
|
||||
Env1 = add_fun_env(Env, Decls),
|
||||
lists:foldl(fun(D, E) ->
|
||||
R = decl_to_fcode(E, D),
|
||||
R
|
||||
lists:foldl(fun(D, E) -> decl_to_fcode(E, D)
|
||||
end, Env1, Decls).
|
||||
|
||||
-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
|
||||
false -> fcode_error({missing_definition, Id});
|
||||
true -> Env
|
||||
@@ -410,7 +439,7 @@ typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) ->
|
||||
Env3 = compute_state_layout(Env2, Name, 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),
|
||||
Layout =
|
||||
case Type([]) of
|
||||
@@ -436,7 +465,7 @@ compute_state_layout(R, [H | T]) ->
|
||||
compute_state_layout(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
|
||||
{id, _, "state"} -> fcode_error({parameterized_state, Id});
|
||||
{id, _, "event"} -> fcode_error({parameterized_event, Id});
|
||||
@@ -461,8 +490,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, {bytes_t, _, N}) ->
|
||||
{bytes, N};
|
||||
type_to_fcode(_Env, _Sub, {tvar, Ann, "void"}) ->
|
||||
fcode_error({found_void, Ann});
|
||||
type_to_fcode(_Env, 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}) ->
|
||||
FNamed = [type_to_fcode(Env, Sub, Arg) || {named_arg_t, _, _, Arg, _} <- Named],
|
||||
FArgs = [type_to_fcode(Env, Sub, Arg) || Arg <- Args],
|
||||
@@ -678,14 +711,31 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
|
||||
end;
|
||||
|
||||
%% 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),
|
||||
FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1],
|
||||
case expr_to_fcode(Env, Fun) of
|
||||
{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);
|
||||
{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 is a closure, with first component the function name and
|
||||
%% second component the environment
|
||||
@@ -1614,6 +1664,10 @@ bind_constructors(Env = #{ con_env := ConEnv }, NewCons) ->
|
||||
|
||||
%% -- 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().
|
||||
add_fun_env(Env = #{ context := {abstract_contract, _} }, _) -> Env; %% no functions from abstract contracts
|
||||
add_fun_env(Env = #{ fun_env := FunEnv }, Decls) ->
|
||||
@@ -1628,7 +1682,7 @@ add_fun_env(Env = #{ fun_env := FunEnv }, Decls) ->
|
||||
make_fun_name(#{ context := Context }, Ann, Name) ->
|
||||
Entrypoint = proplists:get_value(entrypoint, Ann, false),
|
||||
case Context of
|
||||
{main_contract, Main} ->
|
||||
{contract_def, Main} ->
|
||||
if Entrypoint -> {entrypoint, list_to_binary(Name)};
|
||||
true -> {local_fun, [Main, Name]}
|
||||
end;
|
||||
@@ -1640,7 +1694,7 @@ make_fun_name(#{ context := Context }, Ann, Name) ->
|
||||
current_namespace(#{ context := Cxt }) ->
|
||||
case Cxt of
|
||||
{abstract_contract, Con} -> Con;
|
||||
{main_contract, Con} -> Con;
|
||||
{contract_def, Con} -> Con;
|
||||
{namespace, NS} -> NS
|
||||
end.
|
||||
|
||||
@@ -1987,8 +2041,11 @@ internal_error(Error) ->
|
||||
%% -- Pretty printing --------------------------------------------------------
|
||||
|
||||
format_fcode(#{ functions := Funs }) ->
|
||||
prettypr:format(pp_above(
|
||||
[ pp_fun(Name, Def) || {Name, Def} <- maps:to_list(Funs) ])).
|
||||
prettypr:format(format_funs(Funs)).
|
||||
|
||||
format_funs(Funs) ->
|
||||
pp_above(
|
||||
[ pp_fun(Name, Def) || {Name, Def} <- maps:to_list(Funs) ]).
|
||||
|
||||
format_fexpr(E) ->
|
||||
prettypr:format(pp_fexpr(E)).
|
||||
@@ -2105,7 +2162,9 @@ pp_fexpr({set_state, R, A}) ->
|
||||
pp_call(pp_text("set_state"), [{lit, {int, R}}, A]);
|
||||
pp_fexpr({get_state, 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_beside(Fun, pp_fexpr({tuple, Args})).
|
||||
|
||||
@@ -14,12 +14,13 @@
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include("aeso_icode.hrl").
|
||||
-include("aeso_utils.hrl").
|
||||
|
||||
-spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode().
|
||||
convert_typed(TypedTree, Options) ->
|
||||
{Payable, Name} =
|
||||
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};
|
||||
Decl ->
|
||||
gen_error({last_declaration_must_be_contract, Decl})
|
||||
@@ -29,7 +30,8 @@ convert_typed(TypedTree, Options) ->
|
||||
Icode = code(TypedTree, NewIcode, Options),
|
||||
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)),
|
||||
code(Rest, NewIcode, Options);
|
||||
code([{namespace, _Ann, Name, Code}|Rest], Icode, Options) ->
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
-export([format/1, pos/1]).
|
||||
|
||||
format({last_declaration_must_be_contract, Decl = {namespace, _, {con, _, C}, _}}) ->
|
||||
Msg = io_lib:format("Expected a contract as the last declaration instead of the namespace '~s'\n",
|
||||
[C]),
|
||||
format({last_declaration_must_be_main_contract, Decl = {Kind, _, {con, _, C}, _}}) ->
|
||||
Msg = io_lib:format("Expected a main contract as the last declaration instead of the ~p '~s'\n",
|
||||
[Kind, C]),
|
||||
mk_err(pos(Decl), Msg);
|
||||
format({missing_init_function, 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)]),
|
||||
Cxt = "The state cannot contain functions in the AEVM. Use FATE if you need this.\n",
|
||||
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) ->
|
||||
mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])).
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include("aeso_icode.hrl").
|
||||
-include("aeso_utils.hrl").
|
||||
|
||||
|
||||
-type option() :: pp_sophia_code
|
||||
@@ -137,8 +138,9 @@ from_string1(aevm, ContractString, Options) ->
|
||||
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)};
|
||||
from_string1(fate, ContractString, Options) ->
|
||||
#{ fcode := FCode
|
||||
, fcode_env := #{child_con_env := ChildContracts}
|
||||
, 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),
|
||||
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||
{ok, Version} = version(),
|
||||
@@ -178,8 +180,9 @@ string_to_code(ContractString, Options) ->
|
||||
, type_env => TypeEnv
|
||||
, ast => Ast };
|
||||
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_env => Env
|
||||
, unfolded_typed_ast => UnfoldedTypedAst
|
||||
, folded_typed_ast => FoldedTypedAst
|
||||
, type_env => TypeEnv
|
||||
@@ -241,8 +244,9 @@ check_call1(ContractString0, FunName, Args, Options) ->
|
||||
fate ->
|
||||
%% First check the contract without the __call function
|
||||
#{ fcode := OrgFcode
|
||||
, fcode_env := #{child_con_env := ChildContracts}
|
||||
, ast := Ast } = string_to_code(ContractString0, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(OrgFcode, []),
|
||||
FateCode = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, []),
|
||||
%% collect all hashes and compute the first name without hash collision to
|
||||
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
|
||||
CallName = first_none_match(?CALL_NAME, SymbolHashes,
|
||||
@@ -468,7 +472,7 @@ error_missing_call_function() ->
|
||||
Msg = "Internal error: missing '__call'-function",
|
||||
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}
|
||||
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
|
||||
{typed, _,
|
||||
@@ -482,7 +486,7 @@ get_call_type([_ | Contracts]) ->
|
||||
get_call_type(Contracts).
|
||||
|
||||
-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}];
|
||||
({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
|
||||
(_) -> [] end,
|
||||
|
||||
+96
-42
@@ -9,7 +9,7 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-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).
|
||||
-export([optimize_fun/4, to_basic_blocks/1]).
|
||||
@@ -45,7 +45,7 @@
|
||||
-define(s(N), {store, N}).
|
||||
-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 --------------------------------------------------------------
|
||||
|
||||
@@ -70,9 +70,11 @@ code_error(Err) ->
|
||||
|
||||
%% @doc Main entry point.
|
||||
compile(FCode, Options) ->
|
||||
compile(#{}, FCode, Options).
|
||||
compile(ChildContracts, FCode, Options) ->
|
||||
#{ contract_name := ContractName,
|
||||
functions := Functions } = FCode,
|
||||
SFuns = functions_to_scode(ContractName, Functions, Options),
|
||||
SFuns = functions_to_scode(ChildContracts, ContractName, Functions, Options),
|
||||
SFuns1 = optimize_scode(SFuns, Options),
|
||||
FateCode = to_basic_blocks(SFuns1),
|
||||
?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({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),
|
||||
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,
|
||||
body := Body,
|
||||
attrs := Attrs,
|
||||
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),
|
||||
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}.
|
||||
|
||||
-define(tvars, '$tvars').
|
||||
@@ -133,7 +136,9 @@ type_to_scode({tvar, X}) ->
|
||||
put(?tvars, {I + 1, Vars#{ X => I }}),
|
||||
{tvar, I};
|
||||
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).
|
||||
|
||||
@@ -142,11 +147,13 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
|
||||
|
||||
%% -- 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) ],
|
||||
contract = ContractName,
|
||||
child_contracts = ChildContracts,
|
||||
locals = FunNames,
|
||||
current_function = Name,
|
||||
options = Options,
|
||||
tailpos = true }.
|
||||
|
||||
next_var(#env{ vars = Vars }) ->
|
||||
@@ -169,7 +176,7 @@ lookup_var(#env{vars = Vars}, X) ->
|
||||
|
||||
%% -- The compiler --
|
||||
|
||||
lit_to_fate(L) ->
|
||||
lit_to_fate(Env, L) ->
|
||||
case L of
|
||||
{int, N} -> aeb_fate_data:make_integer(N);
|
||||
{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);
|
||||
{oracle_pubkey, K} -> aeb_fate_data:make_oracle(K);
|
||||
{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))
|
||||
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}) ->
|
||||
lit_to_fate(L);
|
||||
term_to_fate(GlobEnv, _Env, {lit, L}) ->
|
||||
lit_to_fate(GlobEnv, L);
|
||||
%% 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);
|
||||
term_to_fate(_Env, nil) ->
|
||||
term_to_fate(_GlobEnv, _Env, nil) ->
|
||||
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
|
||||
[term_to_fate(Env, Hd) | term_to_fate(Env, Tl)];
|
||||
term_to_fate(Env, {tuple, As}) ->
|
||||
aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(Env, A) || A<-As]));
|
||||
term_to_fate(Env, {con, Ar, I, As}) ->
|
||||
FateAs = [ term_to_fate(Env, A) || A <- As ],
|
||||
[term_to_fate(GlobEnv, Env, Hd) | term_to_fate(GlobEnv, Env, Tl)];
|
||||
term_to_fate(GlobEnv, Env, {tuple, As}) ->
|
||||
aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(GlobEnv, Env, A) || A<-As]));
|
||||
term_to_fate(GlobEnv, Env, {con, Ar, I, As}) ->
|
||||
FateAs = [ term_to_fate(GlobEnv, Env, A) || A <- As ],
|
||||
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);
|
||||
term_to_fate(_Env, {builtin, bits_none, []}) ->
|
||||
term_to_fate(_GlobEnv, _Env, {builtin, bits_none, []}) ->
|
||||
aeb_fate_data:make_bits(0);
|
||||
term_to_fate(_Env, {op, bits_set, [B, I]}) ->
|
||||
{bits, N} = term_to_fate(B),
|
||||
J = term_to_fate(I),
|
||||
term_to_fate(GlobEnv, _Env, {op, bits_set, [B, I]}) ->
|
||||
{bits, N} = term_to_fate(GlobEnv, B),
|
||||
J = term_to_fate(GlobEnv, I),
|
||||
{bits, N bor (1 bsl J)};
|
||||
term_to_fate(_Env, {op, bits_clear, [B, I]}) ->
|
||||
{bits, N} = term_to_fate(B),
|
||||
J = term_to_fate(I),
|
||||
term_to_fate(GlobEnv, _Env, {op, bits_clear, [B, I]}) ->
|
||||
{bits, N} = term_to_fate(GlobEnv, B),
|
||||
J = term_to_fate(GlobEnv, I),
|
||||
{bits, N band bnot (1 bsl J)};
|
||||
term_to_fate(Env, {'let', X, E, Body}) ->
|
||||
Env1 = Env#{ X => term_to_fate(Env, E) },
|
||||
term_to_fate(Env1, Body);
|
||||
term_to_fate(Env, {var, X}) ->
|
||||
term_to_fate(GlobEnv, Env, {'let', X, E, Body}) ->
|
||||
Env1 = Env#{ X => term_to_fate(GlobEnv, Env, E) },
|
||||
term_to_fate(GlobEnv, Env1, Body);
|
||||
term_to_fate(_GlobEnv, Env, {var, X}) ->
|
||||
case maps:get(X, Env, undefined) of
|
||||
undefined -> throw(not_a_fate_value);
|
||||
V -> V
|
||||
end;
|
||||
term_to_fate(_Env, {builtin, map_empty, []}) ->
|
||||
term_to_fate(_GlobEnv, _Env, {builtin, map_empty, []}) ->
|
||||
aeb_fate_data:make_map(#{});
|
||||
term_to_fate(Env, {op, map_set, [M, K, V]}) ->
|
||||
Map = term_to_fate(Env, M),
|
||||
Map#{term_to_fate(Env, K) => term_to_fate(Env, V)};
|
||||
term_to_fate(_Env, _) ->
|
||||
term_to_fate(GlobEnv, Env, {op, map_set, [M, K, V]}) ->
|
||||
Map = term_to_fate(GlobEnv, Env, M),
|
||||
Map#{term_to_fate(GlobEnv, Env, K) => term_to_fate(GlobEnv, Env, V)};
|
||||
term_to_fate(_GlobEnv, _Env, _) ->
|
||||
throw(not_a_fate_value).
|
||||
|
||||
to_scode(Env, T) ->
|
||||
try term_to_fate(T) of
|
||||
try term_to_fate(Env, T) of
|
||||
V -> [push(?i(V))]
|
||||
catch throw:not_a_fate_value ->
|
||||
to_scode1(Env, T)
|
||||
end.
|
||||
|
||||
to_scode1(_Env, {lit, L}) ->
|
||||
[push(?i(lit_to_fate(L)))];
|
||||
to_scode1(Env, {lit, L}) ->
|
||||
[push(?i(lit_to_fate(Env, L)))];
|
||||
|
||||
to_scode1(_Env, nil) ->
|
||||
[aeb_fate_ops:nil(?a)];
|
||||
@@ -503,6 +527,8 @@ builtin_to_scode(_Env, call_value, []) ->
|
||||
[aeb_fate_ops:call_value(?a)];
|
||||
builtin_to_scode(_Env, call_gas_price, []) ->
|
||||
[aeb_fate_ops:gasprice(?a)];
|
||||
builtin_to_scode(_Env, call_fee, []) ->
|
||||
[aeb_fate_ops:fee(?a)];
|
||||
builtin_to_scode(_Env, call_gas_left, []) ->
|
||||
[aeb_fate_ops:gas(?a)];
|
||||
builtin_to_scode(Env, oracle_register, [_Sign,_Account,_QFee,_TTL,_QType,_RType] = Args) ->
|
||||
@@ -555,7 +581,27 @@ builtin_to_scode(Env, aens_lookup, [_Name] = Args) ->
|
||||
builtin_to_scode(_Env, auth_tx_hash, []) ->
|
||||
[aeb_fate_ops:auth_tx_hash(?a)];
|
||||
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 --
|
||||
|
||||
@@ -881,6 +927,7 @@ attributes(I) ->
|
||||
{'ORIGIN', A} -> Pure(A, []);
|
||||
{'CALLER', A} -> Pure(A, []);
|
||||
{'GASPRICE', A} -> Pure(A, []);
|
||||
{'FEE', A} -> Pure(A, []);
|
||||
{'BLOCKHASH', A, B} -> Pure(A, [B]);
|
||||
{'BENEFICIARY', A} -> Pure(A, []);
|
||||
{'TIMESTAMP', A} -> Pure(A, []);
|
||||
@@ -941,6 +988,10 @@ attributes(I) ->
|
||||
{'STR_TO_LOWER', A, B} -> Pure(A, [B]);
|
||||
{'CHAR_TO_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);
|
||||
{'EXIT', A} -> Impure(pc, A);
|
||||
'NOP' -> Pure(none, [])
|
||||
@@ -1694,6 +1745,9 @@ split_calls(Ref, [I | Code], Acc, Blocks) when element(1, I) == 'CALL';
|
||||
element(1, I) == 'CALL_R';
|
||||
element(1, I) == 'CALL_GR';
|
||||
element(1, I) == 'CALL_PGR';
|
||||
element(1, I) == 'CREATE';
|
||||
element(1, I) == 'CLONE';
|
||||
element(1, I) == 'CLONE_G';
|
||||
element(1, I) == 'jumpif' ->
|
||||
split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]);
|
||||
split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) ->
|
||||
|
||||
+15
-7
@@ -74,25 +74,31 @@
|
||||
%% first argument. I.e. no backtracking to the second argument if the first
|
||||
%% fails.
|
||||
|
||||
trampoline({bounce, Cont}) when is_function(Cont, 0) ->
|
||||
trampoline(Cont());
|
||||
trampoline(Res) ->
|
||||
Res.
|
||||
-define(BOUNCE(X), {bounce, fun() -> X end}).
|
||||
|
||||
%% Apply a parser to its continuation. This compiles a parser to its low-level representation.
|
||||
-spec apply_p(parser(A), fun((A) -> parser1(B))) -> parser1(B).
|
||||
apply_p(?lazy(F), K) -> apply_p(F(), K);
|
||||
apply_p(?fail(Err), _) -> {fail, Err};
|
||||
apply_p(?choice([P | Ps]), K) -> lists:foldl(fun(Q, R) -> choice1(apply_p(Q, K), R) end,
|
||||
apply_p(P, K), Ps);
|
||||
apply_p(?choice([P | Ps]), K) -> lists:foldl(fun(Q, R) -> choice1(trampoline(apply_p(Q, K)), R) end,
|
||||
trampoline(apply_p(P, K)), Ps);
|
||||
apply_p(?bind(P, F), K) -> apply_p(P, fun(X) -> apply_p(F(X), K) end);
|
||||
apply_p(?right(P, Q), K) -> apply_p(P, fun(_) -> apply_p(Q, K) end);
|
||||
apply_p(?left(P, Q), K) -> apply_p(P, fun(X) -> apply_p(Q, fun(_) -> K(X) end) end);
|
||||
apply_p(?map(F, P), K) -> apply_p(P, fun(X) -> K(F(X)) end);
|
||||
apply_p(?layout, K) -> {layout, K, {fail, {expected, layout_block}}};
|
||||
apply_p(?tok(Atom), K) -> {tok_bind, #{Atom => K}};
|
||||
apply_p(?return(X), K) -> K(X);
|
||||
apply_p(?return(X), K) -> ?BOUNCE(K(X));
|
||||
apply_p([P | Q], K) -> apply_p(P, fun(H) -> apply_p(Q, fun(T) -> K([H | T]) end) end);
|
||||
apply_p(T, K) when is_tuple(T) -> apply_p(tuple_to_list(T), fun(Xs) -> K(list_to_tuple(Xs)) end);
|
||||
apply_p(M, K) when is_map(M) ->
|
||||
{Keys, Ps} = lists:unzip(maps:to_list(M)),
|
||||
apply_p(Ps, fun(Vals) -> K(maps:from_list(lists:zip(Keys, Vals))) end);
|
||||
apply_p(X, K) -> K(X).
|
||||
apply_p(X, K) -> ?BOUNCE(K(X)).
|
||||
|
||||
%% -- Primitive combinators --------------------------------------------------
|
||||
|
||||
@@ -160,7 +166,7 @@ layout() -> ?layout.
|
||||
%% @doc Parse a sequence of tokens using a parser. Fails if the parse is ambiguous.
|
||||
-spec parse(parser(A), tokens()) -> {ok, A} | {error, term()}.
|
||||
parse(P, S) ->
|
||||
case parse1(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end), S) of
|
||||
case parse1(trampoline(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end)), S) of
|
||||
{[], {Pos, Err}} -> {error, {add_current_file(Pos), parse_error, flatten_error(Err)}};
|
||||
{[A], _} -> {ok, A};
|
||||
{As, _} -> {error, {{1, 1}, ambiguous_parse, As}}
|
||||
@@ -241,7 +247,7 @@ col(T) when is_tuple(T) -> element(2, pos(T)).
|
||||
|
||||
%% If both parsers want the next token we grab it and merge the continuations.
|
||||
choice1({tok_bind, Map1}, {tok_bind, Map2}) ->
|
||||
{tok_bind, merge_with(fun(F, G) -> fun(T) -> choice1(F(T), G(T)) end end, Map1, Map2)};
|
||||
{tok_bind, merge_with(fun(F, G) -> fun(T) -> choice1(trampoline(F(T)), trampoline(G(T))) end end, Map1, Map2)};
|
||||
|
||||
%% If both parsers fail we combine the error messages. If only one fails we discard it.
|
||||
choice1({fail, E1}, {fail, E2}) -> {fail, add_error(E1, E2)};
|
||||
@@ -255,7 +261,7 @@ choice1(P, {return_plus, X, Q}) -> {return_plus, X, choice1(P, Q)};
|
||||
%% If both sides want a layout block we combine them. If only one side wants a layout block we
|
||||
%% will commit to a layout block is there is one.
|
||||
choice1({layout, F, P}, {layout, G, Q}) ->
|
||||
{layout, fun(N) -> choice1(F(N), G(N)) end, choice1(P, Q)};
|
||||
{layout, fun(N) -> choice1(trampoline(F(N)), trampoline(G(N))) end, choice1(P, Q)};
|
||||
choice1({layout, F, P}, Q) -> {layout, F, choice1(P, Q)};
|
||||
choice1(P, {layout, G, Q}) -> {layout, G, choice1(P, Q)}.
|
||||
|
||||
@@ -278,6 +284,8 @@ parse1(P, S) ->
|
||||
%% The main work horse. Returns a list of possible parses and an error message in case parsing
|
||||
%% fails.
|
||||
-spec parse1(parser1(A), #ts{}, [A], term()) -> {[A], error()}.
|
||||
parse1({bounce, F}, Ts, Acc, Err) ->
|
||||
parse1(F(), Ts, Acc, Err);
|
||||
parse1({tok_bind, Map}, Ts, Acc, Err) ->
|
||||
case next_token(Ts) of
|
||||
{T, Ts1} ->
|
||||
|
||||
+14
-2
@@ -93,8 +93,20 @@ decl() ->
|
||||
?LAZY_P(
|
||||
choice(
|
||||
%% Contract declaration
|
||||
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
|
||||
, ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5}))
|
||||
[ ?RULE(token(main), keyword(contract),
|
||||
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(include), str(), {include, get_ann(_1), _2})
|
||||
, pragma()
|
||||
|
||||
+8
-2
@@ -13,6 +13,8 @@
|
||||
|
||||
-export_type([options/0]).
|
||||
|
||||
-include("aeso_utils.hrl").
|
||||
|
||||
-type doc() :: prettypr:document().
|
||||
-type options() :: [{indent, non_neg_integer()} | show_generated].
|
||||
|
||||
@@ -131,6 +133,10 @@ typed(A, Type) ->
|
||||
false -> follow(hsep(A, text(":")), type(Type))
|
||||
end.
|
||||
|
||||
contract_head(contract_main) -> text("main contract");
|
||||
contract_head(contract_child) -> text("contract");
|
||||
contract_head(contract_interface) -> text("contract interface").
|
||||
|
||||
%% -- Exports ----------------------------------------------------------------
|
||||
|
||||
-spec decls([aeso_syntax:decl()], options()) -> doc().
|
||||
@@ -145,11 +151,11 @@ decl(D, Options) ->
|
||||
with_options(Options, fun() -> decl(D) end).
|
||||
|
||||
-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 ->
|
||||
text(atom_to_list(Mod));
|
||||
(_) -> 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));
|
||||
decl({namespace, _, C, Ds}) ->
|
||||
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
|
||||
|
||||
+3
-1
@@ -44,7 +44,9 @@ lexer() ->
|
||||
, {"[^/*]+|[/*]", skip()} ],
|
||||
|
||||
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, "|"),
|
||||
|
||||
Rules =
|
||||
|
||||
+5
-2
@@ -25,7 +25,8 @@
|
||||
-type ann_origin() :: system | user.
|
||||
-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 id() :: {id, ann(), name()}.
|
||||
@@ -34,7 +35,9 @@
|
||||
-type qcon() :: {qcon, 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()]}
|
||||
| {pragma, ann(), pragma()}
|
||||
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-define(IS_CONTRACT_HEAD(X),
|
||||
(X =:= contract_main orelse
|
||||
X =:= contract_interface orelse
|
||||
X =:= contract_child
|
||||
)
|
||||
).
|
||||
@@ -1,6 +1,6 @@
|
||||
{application, aesophia,
|
||||
[{description, "Contract Language for aeternity"},
|
||||
{vsn, "4.3.0"},
|
||||
[{description, "Compiler for Aeternity Sophia language"},
|
||||
{vsn, "6.0.2"},
|
||||
{registered, []},
|
||||
{applications,
|
||||
[kernel,
|
||||
|
||||
@@ -190,7 +190,7 @@ parameterized_contract(ExtraCode, FunName, Types) ->
|
||||
lists:flatten(
|
||||
["contract Remote =\n"
|
||||
" entrypoint bla : () => unit\n\n"
|
||||
"contract Dummy =\n",
|
||||
"main contract Dummy =\n",
|
||||
ExtraCode, "\n",
|
||||
" type an_alias('a) = string * 'a\n"
|
||||
" record r = {x : an_alias(int), y : variant}\n"
|
||||
|
||||
+53
-44
@@ -22,7 +22,8 @@ test_cases(1) ->
|
||||
MapACI = #{contract =>
|
||||
#{name => <<"C">>,
|
||||
type_defs => [],
|
||||
payable => true,
|
||||
payable => true,
|
||||
kind => contract_main,
|
||||
functions =>
|
||||
[#{name => <<"a">>,
|
||||
arguments =>
|
||||
@@ -31,56 +32,57 @@ test_cases(1) ->
|
||||
returns => <<"int">>,
|
||||
stateful => true,
|
||||
payable => true}]}},
|
||||
DecACI = <<"payable contract C =\n"
|
||||
DecACI = <<"payable main contract C =\n"
|
||||
" payable entrypoint a : (int) => int\n">>,
|
||||
{Contract,MapACI,DecACI};
|
||||
|
||||
test_cases(2) ->
|
||||
Contract = <<"contract C =\n"
|
||||
Contract = <<"main contract C =\n"
|
||||
" type allan = int\n"
|
||||
" entrypoint a(i : allan) = i+1\n">>,
|
||||
MapACI = #{contract =>
|
||||
#{name => <<"C">>, payable => false,
|
||||
type_defs =>
|
||||
[#{name => <<"allan">>,
|
||||
typedef => <<"int">>,
|
||||
vars => []}],
|
||||
functions =>
|
||||
[#{arguments =>
|
||||
[#{name => <<"i">>,
|
||||
type => <<"C.allan">>}],
|
||||
name => <<"a">>,
|
||||
returns => <<"int">>,
|
||||
stateful => false,
|
||||
payable => false}]}},
|
||||
DecACI = <<"contract C =\n"
|
||||
#{name => <<"C">>, payable => false,
|
||||
kind => contract_main,
|
||||
type_defs =>
|
||||
[#{name => <<"allan">>,
|
||||
typedef => <<"int">>,
|
||||
vars => []}],
|
||||
functions =>
|
||||
[#{arguments =>
|
||||
[#{name => <<"i">>,
|
||||
type => <<"C.allan">>}],
|
||||
name => <<"a">>,
|
||||
returns => <<"int">>,
|
||||
stateful => false,
|
||||
payable => false}]}},
|
||||
DecACI = <<"main contract C =\n"
|
||||
" type allan = int\n"
|
||||
" entrypoint a : (C.allan) => int\n">>,
|
||||
{Contract,MapACI,DecACI};
|
||||
test_cases(3) ->
|
||||
Contract = <<"contract C =\n"
|
||||
Contract = <<"main contract C =\n"
|
||||
" type state = unit\n"
|
||||
" datatype event = SingleEventDefined\n"
|
||||
" datatype bert('a) = Bin('a)\n"
|
||||
" entrypoint a(i : bert(string)) = 1\n">>,
|
||||
" datatype bert('a) = Bin('a)\n"
|
||||
" entrypoint a(i : bert(string)) = 1\n">>,
|
||||
MapACI = #{contract =>
|
||||
#{functions =>
|
||||
[#{arguments =>
|
||||
[#{name => <<"i">>,
|
||||
type =>
|
||||
#{<<"C.bert">> => [<<"string">>]}}],
|
||||
name => <<"a">>,returns => <<"int">>,
|
||||
stateful => false, payable => false}],
|
||||
name => <<"C">>, payable => false,
|
||||
event => #{variant => [#{<<"SingleEventDefined">> => []}]},
|
||||
state => <<"unit">>,
|
||||
[#{arguments =>
|
||||
[#{name => <<"i">>,
|
||||
type =>
|
||||
#{<<"C.bert">> => [<<"string">>]}}],
|
||||
name => <<"a">>,returns => <<"int">>,
|
||||
stateful => false, payable => false}],
|
||||
name => <<"C">>, payable => false, kind => contract_main,
|
||||
event => #{variant => [#{<<"SingleEventDefined">> => []}]},
|
||||
state => <<"unit">>,
|
||||
type_defs =>
|
||||
[#{name => <<"bert">>,
|
||||
typedef =>
|
||||
#{variant =>
|
||||
[#{<<"Bin">> => [<<"'a">>]}]},
|
||||
vars => [#{name => <<"'a">>}]}]}},
|
||||
DecACI = <<"contract C =\n"
|
||||
[#{name => <<"bert">>,
|
||||
typedef =>
|
||||
#{variant =>
|
||||
[#{<<"Bin">> => [<<"'a">>]}]},
|
||||
vars => [#{name => <<"'a">>}]}]}},
|
||||
DecACI = <<"main contract C =\n"
|
||||
" type state = unit\n"
|
||||
" datatype event = SingleEventDefined\n"
|
||||
" datatype bert('a) = Bin('a)\n"
|
||||
@@ -101,17 +103,24 @@ aci_test_contract(Name) ->
|
||||
true -> [debug_mode];
|
||||
false -> []
|
||||
end ++ [{include, {file_system, [aeso_test_utils:contract_path()]}}],
|
||||
{ok, JSON} = aeso_aci:contract_interface(json, String, Opts),
|
||||
{ok, #{aci := JSON1}} = aeso_compiler:from_string(String, [{aci, json}, {backend, fate} | Opts]),
|
||||
?assertEqual(JSON, JSON1),
|
||||
JSON = case aeso_aci:contract_interface(json, String, Opts) of
|
||||
{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),
|
||||
io:format("JSON:\n~p\n", [JSON]),
|
||||
{ok, ContractStub} = aeso_aci:render_aci_json(JSON),
|
||||
|
||||
io:format("JSON:\n~p\n", [JSON]),
|
||||
{ok, ContractStub} = aeso_aci:render_aci_json(JSON),
|
||||
io:format("STUB:\n~s\n", [ContractStub]),
|
||||
check_stub(ContractStub, [{src_file, Name}]),
|
||||
|
||||
io:format("STUB:\n~s\n", [ContractStub]),
|
||||
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) ->
|
||||
try aeso_parser:string(binary_to_list(Stub), Options) of
|
||||
|
||||
@@ -59,8 +59,8 @@ calldata_aci_test_() ->
|
||||
end} || {ContractName, Fun, Args} <- compilable_contracts()].
|
||||
|
||||
parse_args(Fun, Args) ->
|
||||
[{contract, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] =
|
||||
aeso_parser:string("contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
|
||||
[{contract_main, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] =
|
||||
aeso_parser:string("main contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
|
||||
strip_ann(AST).
|
||||
|
||||
strip_ann(T) when is_tuple(T) ->
|
||||
|
||||
@@ -34,9 +34,7 @@ simple_compile_test_() ->
|
||||
#{fate_code := Code} when Backend == fate ->
|
||||
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
|
||||
?assertMatch({X, X}, {Code1, Code});
|
||||
ErrBin ->
|
||||
io:format("\n~s", [ErrBin]),
|
||||
error(ErrBin)
|
||||
Error -> io:format("\n\n~p\n\n", [Error]), print_and_throw(Error)
|
||||
end
|
||||
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
|
||||
not lists:member(ContractName, not_compilable_on(Backend))] ++
|
||||
@@ -92,6 +90,23 @@ simple_compile_test_() ->
|
||||
end} || Backend <- [aevm, fate] ] ++
|
||||
[].
|
||||
|
||||
%% Check if all modules in the standard library compile
|
||||
stdlib_test_() ->
|
||||
{ok, Files} = file:list_dir(aeso_stdlib:stdlib_include_path()),
|
||||
[ { "Testing " ++ File ++ " from the stdlib",
|
||||
fun() ->
|
||||
String = "include \"" ++ File ++ "\"\nmain contract Test =\n entrypoint f(x) = x",
|
||||
Options = [{src_file, File}, {backend, fate}],
|
||||
case aeso_compiler:from_string(String, Options) of
|
||||
{ok, #{fate_code := Code}} ->
|
||||
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
|
||||
?assertMatch({X, X}, {Code1, Code});
|
||||
{error, Error} -> io:format("\n\n~p\n\n", [Error]), print_and_throw(Error)
|
||||
end
|
||||
end} || File <- Files,
|
||||
lists:suffix(".aes", File)
|
||||
].
|
||||
|
||||
check_errors(no_error, Actual) -> ?assertMatch(#{}, Actual);
|
||||
check_errors(Expect, #{}) ->
|
||||
?assertEqual({error, Expect}, ok);
|
||||
@@ -179,17 +194,17 @@ compilable_contracts() ->
|
||||
"lhs_matching",
|
||||
"more_strings",
|
||||
"protected_call",
|
||||
"hermetization_turnoff"
|
||||
"hermetization_turnoff",
|
||||
"multiple_contracts",
|
||||
"clone",
|
||||
"clone_simple",
|
||||
"create",
|
||||
"child_contract_init_bug",
|
||||
"test" % Custom general-purpose test file. Keep it last on the list.
|
||||
].
|
||||
|
||||
not_compilable_on(fate) -> [];
|
||||
not_compilable_on(aevm) ->
|
||||
[ "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"
|
||||
|
||||
].
|
||||
not_compilable_on(aevm) -> compilable_contracts().
|
||||
|
||||
debug_mode_contracts() ->
|
||||
["hermetization_turnoff"].
|
||||
@@ -635,9 +650,9 @@ failing_contracts() ->
|
||||
<<?Pos(2, 1)
|
||||
"Cannot compile with this version of the compiler,\n"
|
||||
"because it does not satisfy the constraint ", Version/binary, " == 9.9.9">>])
|
||||
, ?TYPE_ERROR(multiple_contracts,
|
||||
, ?TYPE_ERROR(interface_with_defs,
|
||||
[<<?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.">>])
|
||||
, ?TYPE_ERROR(contract_as_namespace,
|
||||
[<<?Pos(5, 28)
|
||||
@@ -730,6 +745,42 @@ failing_contracts() ->
|
||||
" g : (int, string) => 'c\nto arguments\n"
|
||||
" \"Litwo, ojczyzno moja\" : string">>
|
||||
])
|
||||
, ?TYPE_ERROR(bad_state,
|
||||
[<<?Pos(4, 16)
|
||||
"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).
|
||||
@@ -743,9 +794,7 @@ failing_contracts() ->
|
||||
{fate, ?Msg(File, Line, Col, ErrFATE)}]}).
|
||||
|
||||
failing_code_gen_contracts() ->
|
||||
[ ?SAME(last_declaration_must_be_contract, 1, 1,
|
||||
"Expected a contract as the last declaration instead of the namespace 'LastDeclarationIsNotAContract'")
|
||||
, ?SAME(missing_definition, 2, 14,
|
||||
[ ?SAME(missing_definition, 2, 14,
|
||||
"Missing definition of function 'foo'.")
|
||||
, ?AEVM(polymorphic_entrypoint, 2, 17,
|
||||
"The argument\n"
|
||||
@@ -843,6 +892,8 @@ failing_code_gen_contracts() ->
|
||||
"Invalid state type\n"
|
||||
" {f : (int) => int}\n"
|
||||
"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_() ->
|
||||
@@ -880,14 +931,26 @@ validation_fails() ->
|
||||
"Byte code contract is not payable, but source code contract is.">>]}].
|
||||
|
||||
validate(Contract1, Contract2) ->
|
||||
ByteCode = #{ fate_code := FCode } = compile(fate, Contract1),
|
||||
FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)),
|
||||
Source = aeso_test_utils:read_contract(Contract2),
|
||||
aeso_compiler:validate_byte_code(
|
||||
ByteCode#{ byte_code := FCode1 }, Source,
|
||||
case lists:member(Contract2, debug_mode_contracts()) of
|
||||
true -> [debug_mode];
|
||||
false -> []
|
||||
end ++
|
||||
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]).
|
||||
case compile(fate, Contract1) of
|
||||
ByteCode = #{ fate_code := FCode } ->
|
||||
FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)),
|
||||
Source = aeso_test_utils:read_contract(Contract2),
|
||||
aeso_compiler:validate_byte_code(
|
||||
ByteCode#{ byte_code := FCode1 }, Source,
|
||||
case lists:member(Contract2, debug_mode_contracts()) of
|
||||
true -> [debug_mode];
|
||||
false -> []
|
||||
end ++
|
||||
[{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.
|
||||
|
||||
@@ -12,10 +12,10 @@ simple_contracts_test_() ->
|
||||
fun(_) -> ok end,
|
||||
[{"Parse a contract with an identity function.",
|
||||
fun() ->
|
||||
Text = "contract Identity =\n"
|
||||
Text = "main contract Identity =\n"
|
||||
" function id(x) = x\n",
|
||||
?assertMatch(
|
||||
[{contract, _, {con, _, "Identity"},
|
||||
[{contract_main, _, {con, _, "Identity"},
|
||||
[{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"},
|
||||
{id, _, "x"}}]}], parse_string(Text)),
|
||||
ok
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract Identity =
|
||||
function main (x:int) = x
|
||||
function main_fun (x:int) = x
|
||||
|
||||
function __call() = 12
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
contract Remote =
|
||||
entrypoint main : (int) => unit
|
||||
contract interface Remote =
|
||||
entrypoint main_fun : (int) => unit
|
||||
|
||||
contract AddrChain =
|
||||
type o_type = oracle(string, map(string, int))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
entrypoint foo : () => unit
|
||||
|
||||
contract AddressLiterals =
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
contract C =
|
||||
entrypoint f() = 123
|
||||
|
||||
contract D =
|
||||
entrypoint f() = 123
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
entrypoint foo : () => unit
|
||||
|
||||
contract AddressLiterals =
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
entrypoint id : int => int
|
||||
|
||||
contract ProtectedCall =
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
contract C =
|
||||
record state = { foo : int }
|
||||
entrypoint init(i : int) =
|
||||
state{ foo = i,
|
||||
foo = 42 }
|
||||
@@ -1,3 +1,3 @@
|
||||
function square(x) = x ^ 2
|
||||
contract Main =
|
||||
entrypoint main() = square(10)
|
||||
entrypoint main_fun() = square(10)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
contract Identity =
|
||||
record state = {foo: int, bar: string}
|
||||
entrypoint init() = {foo = 0, bar = ""}
|
||||
|
||||
main contract IdentityService =
|
||||
stateful entrypoint createNewIdentity() : Identity =
|
||||
put(())
|
||||
Chain.create()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -5,5 +5,5 @@ contract BadAENSresolve =
|
||||
function fail() : t(int) =
|
||||
AENS.resolve("foo.aet", "whatever")
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
contract C =
|
||||
entrypoint f : () => unit
|
||||
|
||||
main contract M =
|
||||
entrypoint f() = 123
|
||||
@@ -3,4 +3,4 @@ contract MapAsMapKey =
|
||||
|
||||
function foo(m) : t(int => int) = {[m] = 0}
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
@@ -2,4 +2,4 @@ contract HigherOrderQueryType =
|
||||
stateful function foo(o) : oracle_query(_, string ) =
|
||||
Oracle.query(o, (x) => x + 1, 100, RelativeTTL(100), RelativeTTL(100))
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
@@ -2,4 +2,4 @@ contract HigherOrderResponseType =
|
||||
stateful function foo(o, q : oracle_query(string, _)) =
|
||||
Oracle.respond(o, q, (x) => x + 1)
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
namespace LastDeclarationIsNotAContract =
|
||||
function add(x, y) = x + y
|
||||
@@ -1,3 +1,3 @@
|
||||
contract MissingDefinition =
|
||||
entrypoint foo : int => int
|
||||
entrypoint main() = foo(0)
|
||||
entrypoint main_fun() = foo(0)
|
||||
|
||||
@@ -3,5 +3,5 @@ contract PolymorphicAENSresolve =
|
||||
function fail() : option('a) =
|
||||
AENS.resolve("foo.aet", "whatever")
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@ contract MapAsMapKey =
|
||||
|
||||
function foo(m) : t('a) = {[m] = 0}
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
@@ -2,4 +2,4 @@ contract PolymorphicQueryType =
|
||||
stateful function is_oracle(o) =
|
||||
Oracle.check(o)
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
@@ -2,4 +2,4 @@ contract PolymorphicResponseType =
|
||||
function is_oracle(o : oracle(string, 'r)) =
|
||||
Oracle.check(o)
|
||||
|
||||
entrypoint main(o : oracle(string, int)) = is_oracle(o)
|
||||
entrypoint main_fun(o : oracle(string, int)) = is_oracle(o)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
entrypoint foo : int => int
|
||||
|
||||
contract UnappliedContractCall =
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
contract UnappliedNamedArgBuiltin =
|
||||
// Allowed in FATE, but not AEVM
|
||||
stateful entrypoint main(s) =
|
||||
stateful entrypoint main_fun(s) =
|
||||
let reg = Oracle.register
|
||||
reg(signature = s, Contract.address, 100, RelativeTTL(100)) : oracle(int, int)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
entrypoint up_to : (int) => list(int)
|
||||
entrypoint sum : (list(int)) => int
|
||||
entrypoint some_string : () => string
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
contract Foo =
|
||||
contract interface Foo =
|
||||
entrypoint foo : () => int
|
||||
|
||||
contract Fail =
|
||||
|
||||
@@ -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)
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
// Testing primitives for accessing the block chain environment
|
||||
contract Interface =
|
||||
contract interface Interface =
|
||||
entrypoint contract_address : () => address
|
||||
entrypoint call_origin : () => address
|
||||
entrypoint call_caller : () => address
|
||||
@@ -44,6 +44,9 @@ contract Environment =
|
||||
// Gas price
|
||||
entrypoint call_gas_price() : int = Call.gas_price
|
||||
|
||||
// Fee
|
||||
entrypoint call_fee() : int = Call.fee
|
||||
|
||||
// -- Information about the chain ---
|
||||
|
||||
// Account balances
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
entrypoint dummy : () => unit
|
||||
|
||||
contract Events =
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// An implementation of the factorial function where each recursive
|
||||
// call is to another contract. Not the cheapest way to compute factorial.
|
||||
contract FactorialServer =
|
||||
contract interface FactorialServer =
|
||||
entrypoint fac : (int) => int
|
||||
|
||||
contract Factorial =
|
||||
|
||||
@@ -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
|
||||
@@ -1,3 +1,2 @@
|
||||
|
||||
contract Identity =
|
||||
entrypoint main (x:int) = x
|
||||
main contract Identity =
|
||||
entrypoint main_fun (x:int) = x
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
contract interface ContractOne =
|
||||
entrypoint foo() = "foo"
|
||||
|
||||
contract ContractTwo =
|
||||
entrypoint bar() = "bar"
|
||||
@@ -16,7 +16,7 @@ contract LHSMatching =
|
||||
let null(_ :: _) = false
|
||||
!null(xs)
|
||||
|
||||
entrypoint main() =
|
||||
entrypoint main_fun() =
|
||||
from_some(Some([0]))
|
||||
++ append([length([true]), 2, 3], [4, 5, 6])
|
||||
++ [7 | if (local_match([false]))]
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
contract MissingEventType =
|
||||
entrypoint main() =
|
||||
entrypoint main_fun() =
|
||||
Chain.event("MAIN")
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
contract ContractOne =
|
||||
entrypoint foo() = "foo"
|
||||
contract Child =
|
||||
entrypoint
|
||||
add2 : int => int
|
||||
add2(x) = x + 2
|
||||
|
||||
contract ContractTwo =
|
||||
entrypoint bar() = "bar"
|
||||
main contract Main =
|
||||
entrypoint add4(x, c : Child) = c.add2(x) + 2
|
||||
@@ -0,0 +1,5 @@
|
||||
main contract C =
|
||||
entrypoint f() = 123
|
||||
|
||||
main contract D =
|
||||
entrypoint f() = 123
|
||||
@@ -0,0 +1,2 @@
|
||||
contract interface C =
|
||||
entrypoint f : () => unit
|
||||
@@ -1,4 +1,4 @@
|
||||
contract C1 =
|
||||
contract interface C1 =
|
||||
entrypoint f : int
|
||||
|
||||
contract C =
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
entrypoint id : int => int
|
||||
|
||||
contract ProtectedCall =
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
|
||||
contract Remote1 =
|
||||
entrypoint main : (int) => int
|
||||
contract interface Remote1 =
|
||||
entrypoint main_fun : (int) => int
|
||||
|
||||
contract Remote2 =
|
||||
contract interface Remote2 =
|
||||
entrypoint call : (Remote1, int) => int
|
||||
|
||||
contract Remote3 =
|
||||
contract interface Remote3 =
|
||||
entrypoint get : () => int
|
||||
entrypoint tick : () => unit
|
||||
|
||||
contract RemoteCall =
|
||||
|
||||
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) =
|
||||
r2.call(r1, x)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract SpendContract =
|
||||
contract interface SpendContract =
|
||||
entrypoint withdraw : (int) => int
|
||||
|
||||
contract SpendTest =
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
include "String.aes"
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
record rstate = { i : int, s : string, m : map(int, int) }
|
||||
|
||||
entrypoint look_at : (rstate) => unit
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
stateful entrypoint remote_spend : (address, int) => unit
|
||||
entrypoint remote_pure : int => int
|
||||
|
||||
|
||||
+9
-99
@@ -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 =
|
||||
// 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))
|
||||
include "List.aes"
|
||||
|
||||
entrypoint main() =
|
||||
let three=s(s(s(z)))
|
||||
add(three,three)
|
||||
(((i)=>i+1),0)
|
||||
contract IntegerHolder =
|
||||
type state = int
|
||||
entrypoint init(x) = x
|
||||
entrypoint get() = state
|
||||
|
||||
main contract Test =
|
||||
stateful entrypoint f(c) = Chain.clone(ref=c, 123)
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
|
||||
type themap = map(int, string)
|
||||
entrypoint foo : () => themap
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// Oracle.extend
|
||||
include "String.aes"
|
||||
contract UnappliedBuiltins =
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
type o = oracle(int, int)
|
||||
type t = list(int * string)
|
||||
type m = map(int, int)
|
||||
|
||||
Reference in New Issue
Block a user