Contract factories and bytecode introspection (#305)
* Support for CREATE, CLONE and BYTECODE_HASH * Add missing files * Pushed the clone example through the typechecker * CLONE compiles * Fix dependent type in CLONE * Bytecode hash fixes * Refactor * Refactor 2 * move some logic away * Fixed some error messages. Type inference of child contract still does some random shit\n(mistakes arguments with result type) * CREATE sometimes compiles and sometimes not * Fix some scoping/constraint issues * works, needs cleanup * cleanup * Fix some tests. Remove optimization of singleton tuples * Fix default argument for clone * Cleanup * CHANGELOG * Mention void type * Address review, fix some dialyzer errors * Please dialyzer * Fix failing tests * Write negative tests * Docs * TOC * missing 'the' * missing 'the' * missing 'the' * missing 'the' * mention pre-fund * format * pre-fund clarification * format * Grammar in docs
This commit is contained in:
parent
d82b42518e
commit
1d9f59fec3
@ -6,7 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
|
- Child contracts
|
||||||
|
- `Chain.clone`
|
||||||
|
- `Chain.create`
|
||||||
|
- `Chain.bytecode_hash`
|
||||||
|
- Minor support for variadic functions
|
||||||
|
- `void` type that represents an empty type
|
||||||
### Changed
|
### Changed
|
||||||
|
- Contract interfaces must be now invocated by `contract interface` keywords
|
||||||
|
- `main` keyword to indicate the main contract in case there are child contracts around
|
||||||
|
- `List.sum` and `List.product` no longer use `List.foldl`
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
## [5.0.0] 2021-04-30
|
## [5.0.0] 2021-04-30
|
||||||
|
214
docs/sophia.md
214
docs/sophia.md
@ -1,65 +1,6 @@
|
|||||||
<!-- IMPORTANT: REMEMBER TO UPDATE THE TABLE OF CONTENTS AFTER YOUR EDIT -->
|
<!-- IMPORTANT: REMEMBER TO UPDATE THE TABLE OF CONTENTS AFTER YOUR EDIT -->
|
||||||
|
|
||||||
**Table of Contents**
|
# The Sophia Language
|
||||||
|
|
||||||
- [-](#-)
|
|
||||||
- [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
|
|
||||||
An Æternity BlockChain Language
|
An Æternity BlockChain Language
|
||||||
|
|
||||||
The Sophia is a language in the ML family. It is strongly typed and has
|
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
|
to a blockchain (the Æternity BlockChain). Thus some features of conventional
|
||||||
languages, such as floating point arithmetic, are not present in Sophia, and
|
languages, such as floating point arithmetic, are not present in Sophia, and
|
||||||
some blockchain specific primitives, constructions and types have been added.
|
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
|
## Language Features
|
||||||
### Contracts
|
### Contracts
|
||||||
|
|
||||||
@ -94,16 +94,16 @@ To call a function in another contract you need the address to an instance of
|
|||||||
the contract. The type of the address must be a contract type, which consists
|
the contract. The type of the address must be a contract type, which consists
|
||||||
of a number of type definitions and entrypoint declarations. For instance,
|
of a number of type definitions and entrypoint declarations. For instance,
|
||||||
|
|
||||||
```javascript
|
```sophia
|
||||||
// A contract type
|
// A contract type
|
||||||
contract VotingType =
|
contract interface VotingType =
|
||||||
entrypoint vote : string => unit
|
entrypoint vote : string => unit
|
||||||
```
|
```
|
||||||
|
|
||||||
Now given contract address of type `VotingType` you can call the `vote`
|
Now given contract address of type `VotingType` you can call the `vote`
|
||||||
entrypoint of that contract:
|
entrypoint of that contract:
|
||||||
|
|
||||||
```javascript
|
```sophia
|
||||||
contract VoteTwice =
|
contract VoteTwice =
|
||||||
entrypoint voteTwice(v : VotingType, alt : string) =
|
entrypoint voteTwice(v : VotingType, alt : string) =
|
||||||
v.vote(alt)
|
v.vote(alt)
|
||||||
@ -114,7 +114,7 @@ Contract calls take two optional named arguments `gas : int` and `value : int`
|
|||||||
that lets you set a gas limit and provide tokens to a contract call. If omitted
|
that lets you set a gas limit and provide tokens to a contract call. If omitted
|
||||||
the defaults are no gas limit and no tokens. Suppose there is a fee for voting:
|
the defaults are no gas limit and no tokens. Suppose there is a fee for voting:
|
||||||
|
|
||||||
```javascript
|
```sophia
|
||||||
entrypoint voteTwice(v : VotingType, fee : int, alt : string) =
|
entrypoint voteTwice(v : VotingType, fee : int, alt : string) =
|
||||||
v.vote(value = fee, alt)
|
v.vote(value = fee, alt)
|
||||||
v.vote(value = fee, alt)
|
v.vote(value = fee, alt)
|
||||||
@ -136,7 +136,7 @@ To recover the underlying `address` of a contract instance there is a field
|
|||||||
`address : address`. For instance, to send tokens to the voting contract (given that it is payable)
|
`address : address`. For instance, to send tokens to the voting contract (given that it is payable)
|
||||||
without calling it you can write
|
without calling it you can write
|
||||||
|
|
||||||
```javascript
|
```sophia
|
||||||
entrypoint pay(v : VotingType, amount : int) =
|
entrypoint pay(v : VotingType, amount : int) =
|
||||||
Chain.spend(v.address, amount)
|
Chain.spend(v.address, amount)
|
||||||
```
|
```
|
||||||
@ -154,7 +154,7 @@ If the call fails the result is `None`, otherwise it's `Some(r)` where `r` is
|
|||||||
the return value of the call.
|
the return value of the call.
|
||||||
|
|
||||||
```sophia
|
```sophia
|
||||||
contract VotingType =
|
contract interface VotingType =
|
||||||
entrypoint : vote : string => unit
|
entrypoint : vote : string => unit
|
||||||
|
|
||||||
contract Voter =
|
contract Voter =
|
||||||
@ -171,10 +171,42 @@ However, note that errors that would normally consume all the gas in the
|
|||||||
transaction still only uses up the gas spent running the contract.
|
transaction still only uses up the gas spent running the contract.
|
||||||
|
|
||||||
|
|
||||||
|
#### Contract factories and child contracts
|
||||||
|
|
||||||
|
Since the version 5.0.0 Sophia supports deploying contracts by other
|
||||||
|
contracts. This can be done in two ways:
|
||||||
|
|
||||||
|
- Contract cloning via [`Chain.clone`](sophia_stdlib.md#clone)
|
||||||
|
- Direct deploy via [`Chain.create`](sophia_stdlib.md#create)
|
||||||
|
|
||||||
|
These functions take variable number of arguments that must match the created
|
||||||
|
contract's `init` function. Beside that they take some additional named
|
||||||
|
arguments – please refer to their documentation for the details.
|
||||||
|
|
||||||
|
While `Chain.clone` requires only a `contract interface` and a living instance
|
||||||
|
of a given contract on the chain, `Chain.create` needs a full definition of a
|
||||||
|
to-create contract defined by the standard `contract` syntax, for example
|
||||||
|
|
||||||
|
```
|
||||||
|
contract IntHolder =
|
||||||
|
type state = int
|
||||||
|
entrypoint init(x) = x
|
||||||
|
entrypoint get() = state
|
||||||
|
|
||||||
|
main contract IntHolderFactory =
|
||||||
|
entrypoint new(x : int) : IntHolder =
|
||||||
|
let ih = Chain.create(x) : IntHolder
|
||||||
|
ih
|
||||||
|
```
|
||||||
|
|
||||||
|
In case of a presence of child contracts (`IntHolder` in this case), the main
|
||||||
|
contract must be pointed out with the `main` keyword as shown in the example.
|
||||||
|
|
||||||
|
|
||||||
### Mutable state
|
### Mutable state
|
||||||
|
|
||||||
Sophia does not have arbitrary mutable state, but only a limited form of
|
Sophia does not have arbitrary mutable state, but only a limited form of state
|
||||||
state associated with each contract instance.
|
associated with each contract instance.
|
||||||
|
|
||||||
- Each contract defines a type `state` encapsulating its mutable state.
|
- Each contract defines a type `state` encapsulating its mutable state.
|
||||||
The type `state` defaults to the `unit`.
|
The type `state` defaults to the `unit`.
|
||||||
@ -200,7 +232,7 @@ Top-level functions and entrypoints must be annotated with the
|
|||||||
`stateful` keyword to be allowed to affect the state of the running contract.
|
`stateful` keyword to be allowed to affect the state of the running contract.
|
||||||
For instance,
|
For instance,
|
||||||
|
|
||||||
```javascript
|
```sophia
|
||||||
stateful entrypoint set_state(s : state) =
|
stateful entrypoint set_state(s : state) =
|
||||||
put(s)
|
put(s)
|
||||||
```
|
```
|
||||||
@ -237,7 +269,7 @@ A concrete contract is by default *not* payable. Any attempt at spending to such
|
|||||||
a contract (either a `Chain.spend` or a normal spend transaction) will fail. If a
|
a contract (either a `Chain.spend` or a normal spend transaction) will fail. If a
|
||||||
contract shall be able to receive funds in this way it has to be declared `payable`:
|
contract shall be able to receive funds in this way it has to be declared `payable`:
|
||||||
|
|
||||||
```javascript
|
```sophia
|
||||||
// A payable contract
|
// A payable contract
|
||||||
payable contract ExampleContract =
|
payable contract ExampleContract =
|
||||||
stateful entrypoint do_stuff() = ...
|
stateful entrypoint do_stuff() = ...
|
||||||
@ -253,7 +285,7 @@ A contract entrypoint is by default *not* payable. Any call to such a function
|
|||||||
that has a non-zero `value` will fail. Contract entrypoints that should be called
|
that has a non-zero `value` will fail. Contract entrypoints that should be called
|
||||||
with a non-zero value should be declared `payable`.
|
with a non-zero value should be declared `payable`.
|
||||||
|
|
||||||
```javascript
|
```sophia
|
||||||
payable stateful entrypoint buy(to : address) =
|
payable stateful entrypoint buy(to : address) =
|
||||||
if(Call.value > 42)
|
if(Call.value > 42)
|
||||||
transfer_item(to)
|
transfer_item(to)
|
||||||
@ -414,7 +446,7 @@ corresponding integer, so setting very high bits can be expensive).
|
|||||||
Type aliases can be introduced with the `type` keyword and can be
|
Type aliases can be introduced with the `type` keyword and can be
|
||||||
parameterized. For instance
|
parameterized. For instance
|
||||||
|
|
||||||
```
|
```sophia
|
||||||
type number = int
|
type number = int
|
||||||
type string_map('a) = map(string, 'a)
|
type string_map('a) = map(string, 'a)
|
||||||
```
|
```
|
||||||
@ -434,7 +466,7 @@ datatype one_or_both('a, 'b) = Left('a) | Right('b) | Both('a, 'b)
|
|||||||
|
|
||||||
Elements of data types can be pattern matched against, using the `switch` construct:
|
Elements of data types can be pattern matched against, using the `switch` construct:
|
||||||
|
|
||||||
```
|
```sophia
|
||||||
function get_left(x : one_or_both('a, 'b)) : option('a) =
|
function get_left(x : one_or_both('a, 'b)) : option('a) =
|
||||||
switch(x)
|
switch(x)
|
||||||
Left(x) => Some(x)
|
Left(x) => Some(x)
|
||||||
@ -443,7 +475,7 @@ function get_left(x : one_or_both('a, 'b)) : option('a) =
|
|||||||
```
|
```
|
||||||
|
|
||||||
or directly in the left-hand side:
|
or directly in the left-hand side:
|
||||||
```
|
```sophia
|
||||||
function
|
function
|
||||||
get_left : one_or_both('a, 'b) => option('a)
|
get_left : one_or_both('a, 'b) => option('a)
|
||||||
get_left(Left(x)) = Some(x)
|
get_left(Left(x)) = Some(x)
|
||||||
@ -461,7 +493,7 @@ elements of a list can be any of datatype but they must have the same
|
|||||||
type. The type of lists with elements of type `'e` is written
|
type. The type of lists with elements of type `'e` is written
|
||||||
`list('e)`. For example we can have the following lists:
|
`list('e)`. For example we can have the following lists:
|
||||||
|
|
||||||
```
|
```sophia
|
||||||
[1, 33, 2, 666] : list(int)
|
[1, 33, 2, 666] : list(int)
|
||||||
[(1, "aaa"), (10, "jjj"), (666, "the beast")] : list(int * string)
|
[(1, "aaa"), (10, "jjj"), (666, "the beast")] : list(int * string)
|
||||||
[{[1] = "aaa", [10] = "jjj"}, {[5] = "eee", [666] = "the beast"}] : list(map(int, string))
|
[{[1] = "aaa", [10] = "jjj"}, {[5] = "eee", [666] = "the beast"}] : list(map(int, string))
|
||||||
@ -475,13 +507,13 @@ and returns the resulting list. So concatenating two lists
|
|||||||
|
|
||||||
Sophia supports list comprehensions known from languages like Python, Haskell or Erlang.
|
Sophia supports list comprehensions known from languages like Python, Haskell or Erlang.
|
||||||
Example syntax:
|
Example syntax:
|
||||||
```
|
```sophia
|
||||||
[x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]]
|
[x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]]
|
||||||
// yields [12,13,14,20,21,22,30,31,32]
|
// yields [12,13,14,20,21,22,30,31,32]
|
||||||
```
|
```
|
||||||
|
|
||||||
Lists can be constructed using the range syntax using special `..` operator:
|
Lists can be constructed using the range syntax using special `..` operator:
|
||||||
```
|
```sophia
|
||||||
[1..4] == [1,2,3,4]
|
[1..4] == [1,2,3,4]
|
||||||
```
|
```
|
||||||
The ranges are always ascending and have step equal to 1.
|
The ranges are always ascending and have step equal to 1.
|
||||||
@ -493,7 +525,7 @@ Please refer to the [standard library](sophia_stdlib.md#List) for the predefined
|
|||||||
|
|
||||||
A Sophia record type is given by a fixed set of fields with associated,
|
A Sophia record type is given by a fixed set of fields with associated,
|
||||||
possibly different, types. For instance
|
possibly different, types. For instance
|
||||||
```
|
```sophia
|
||||||
record account = { name : string,
|
record account = { name : string,
|
||||||
balance : int,
|
balance : int,
|
||||||
history : list(transaction) }
|
history : list(transaction) }
|
||||||
@ -510,12 +542,12 @@ Please refer to the [standard library](sophia_stdlib.md#Map) for the predefined
|
|||||||
|
|
||||||
A value of record type is constructed by giving a value for each of the fields.
|
A value of record type is constructed by giving a value for each of the fields.
|
||||||
For the example above,
|
For the example above,
|
||||||
```
|
```sophia
|
||||||
function new_account(name) =
|
function new_account(name) =
|
||||||
{name = name, balance = 0, history = []}
|
{name = name, balance = 0, history = []}
|
||||||
```
|
```
|
||||||
Maps are constructed similarly, with keys enclosed in square brackets
|
Maps are constructed similarly, with keys enclosed in square brackets
|
||||||
```
|
```sophia
|
||||||
function example_map() : map(string, int) =
|
function example_map() : map(string, int) =
|
||||||
{["key1"] = 1, ["key2"] = 2}
|
{["key1"] = 1, ["key2"] = 2}
|
||||||
```
|
```
|
||||||
@ -524,7 +556,7 @@ The empty map is written `{}`.
|
|||||||
#### Accessing values
|
#### Accessing values
|
||||||
|
|
||||||
Record fields access is written `r.f` and map lookup `m[k]`. For instance,
|
Record fields access is written `r.f` and map lookup `m[k]`. For instance,
|
||||||
```
|
```sophia
|
||||||
function get_balance(a : address, accounts : map(address, account)) =
|
function get_balance(a : address, accounts : map(address, account)) =
|
||||||
accounts[a].balance
|
accounts[a].balance
|
||||||
```
|
```
|
||||||
@ -549,14 +581,14 @@ in the map or execution fails, but a default value can be provided:
|
|||||||
`k` is not in the map.
|
`k` is not in the map.
|
||||||
|
|
||||||
Updates can be nested:
|
Updates can be nested:
|
||||||
```
|
```sophia
|
||||||
function clear_history(a : address, accounts : map(address, account)) : map(address, account) =
|
function clear_history(a : address, accounts : map(address, account)) : map(address, account) =
|
||||||
accounts{ [a].history = [] }
|
accounts{ [a].history = [] }
|
||||||
```
|
```
|
||||||
This is equivalent to `accounts{ [a] @ acc = acc{ history = [] } }` and thus
|
This is equivalent to `accounts{ [a] @ acc = acc{ history = [] } }` and thus
|
||||||
requires `a` to be present in the accounts map. To have `clear_history` create
|
requires `a` to be present in the accounts map. To have `clear_history` create
|
||||||
an account if `a` is not in the map you can write (given a function `empty_account`):
|
an account if `a` is not in the map you can write (given a function `empty_account`):
|
||||||
```
|
```sophia
|
||||||
accounts{ [a = empty_account()].history = [] }
|
accounts{ [a = empty_account()].history = [] }
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -627,7 +659,7 @@ For a functionality documentation refer to the [standard library](sophia_stdlib.
|
|||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
Example for an oracle answering questions of type `string` with answers of type `int`:
|
Example for an oracle answering questions of type `string` with answers of type `int`:
|
||||||
```
|
```sophia
|
||||||
contract Oracles =
|
contract Oracles =
|
||||||
|
|
||||||
stateful entrypoint registerOracle(acct : address,
|
stateful entrypoint registerOracle(acct : address,
|
||||||
@ -698,7 +730,7 @@ an account with address `addr`. In order to allow a contract `ct` to handle
|
|||||||
|
|
||||||
Armed with this information we can for example write a function that extends
|
Armed with this information we can for example write a function that extends
|
||||||
the name if it expires within 1000 blocks:
|
the name if it expires within 1000 blocks:
|
||||||
```
|
```sophia
|
||||||
stateful entrypoint extend_if_necessary(addr : address, name : string, sig : signature) =
|
stateful entrypoint extend_if_necessary(addr : address, name : string, sig : signature) =
|
||||||
switch(AENS.lookup(name))
|
switch(AENS.lookup(name))
|
||||||
None => ()
|
None => ()
|
||||||
@ -709,7 +741,7 @@ the name if it expires within 1000 blocks:
|
|||||||
|
|
||||||
And we can write functions that adds and removes keys from the pointers of the
|
And we can write functions that adds and removes keys from the pointers of the
|
||||||
name:
|
name:
|
||||||
```
|
```sophia
|
||||||
stateful entrypoint add_key(addr : address, name : string, key : string,
|
stateful entrypoint add_key(addr : address, name : string, key : string,
|
||||||
pt : AENS.pointee, sig : signature) =
|
pt : AENS.pointee, sig : signature) =
|
||||||
switch(AENS.lookup(name))
|
switch(AENS.lookup(name))
|
||||||
@ -768,7 +800,7 @@ The fields can appear in any order.
|
|||||||
Events are emitted by using the `Chain.event` function. The following function
|
Events are emitted by using the `Chain.event` function. The following function
|
||||||
will emit one Event of each kind in the example.
|
will emit one Event of each kind in the example.
|
||||||
|
|
||||||
```
|
```sophia
|
||||||
entrypoint emit_events() : () =
|
entrypoint emit_events() : () =
|
||||||
Chain.event(TheFirstEvent(42))
|
Chain.event(TheFirstEvent(42))
|
||||||
Chain.event(AnotherEvent(Contract.address, "This is not indexed"))
|
Chain.event(AnotherEvent(Contract.address, "This is not indexed"))
|
||||||
@ -778,7 +810,7 @@ will emit one Event of each kind in the example.
|
|||||||
|
|
||||||
It is only possible to have one (1) `string` parameter in the event, but it can
|
It is only possible to have one (1) `string` parameter in the event, but it can
|
||||||
be placed in any position (and its value will end up in the `data` field), i.e.
|
be placed in any position (and its value will end up in the `data` field), i.e.
|
||||||
```
|
```sophia
|
||||||
AnotherEvent(string, indexed address)
|
AnotherEvent(string, indexed address)
|
||||||
|
|
||||||
...
|
...
|
||||||
@ -837,7 +869,7 @@ and `*/` and can be nested.
|
|||||||
|
|
||||||
```
|
```
|
||||||
contract elif else entrypoint false function if import include let mod namespace
|
contract elif else entrypoint false function if import include let mod namespace
|
||||||
private payable stateful switch true type record datatype
|
private payable stateful switch true type record datatype main interface
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Tokens
|
#### Tokens
|
||||||
@ -941,7 +973,7 @@ Args ::= '(' Sep(Pattern, ',') ')'
|
|||||||
Contract declarations must appear at the top-level.
|
Contract declarations must appear at the top-level.
|
||||||
|
|
||||||
For example,
|
For example,
|
||||||
```
|
```sophia
|
||||||
contract Test =
|
contract Test =
|
||||||
type t = int
|
type t = int
|
||||||
entrypoint add (x : t, y : t) = x + y
|
entrypoint add (x : t, y : t) = x + y
|
||||||
@ -1089,7 +1121,7 @@ In order of highest to lowest precedence.
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
```
|
```sophia
|
||||||
/*
|
/*
|
||||||
* A simple crowd-funding example
|
* A simple crowd-funding example
|
||||||
*/
|
*/
|
||||||
|
@ -765,6 +765,127 @@ Chain.gas_limit : int
|
|||||||
The gas limit of the current block.
|
The gas limit of the current block.
|
||||||
|
|
||||||
|
|
||||||
|
#### bytecode_hash
|
||||||
|
```
|
||||||
|
Chain.bytecode_hash : 'c => option(hash)
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns the hash of the contract's bytecode (or `None` if it is
|
||||||
|
nonexistent or deployed before FATE2). The type `'c` must be
|
||||||
|
instantiated with a contract. The charged gas increases linearly to
|
||||||
|
the size of the serialized bytecode of the deployed contract.
|
||||||
|
|
||||||
|
|
||||||
|
#### create
|
||||||
|
```
|
||||||
|
Chain.create(value : int, ...) => 'c
|
||||||
|
```
|
||||||
|
|
||||||
|
Creates and deploys a new instance of a contract `'c`. All of the
|
||||||
|
unnamed arguments will be passed to the `init` function. The charged
|
||||||
|
gas increases linearly with the size of the compiled child contract's
|
||||||
|
bytecode. The `source_hash` on-chain entry of the newly created
|
||||||
|
contract will be the SHA256 hash over concatenation of
|
||||||
|
|
||||||
|
- whole contract source code
|
||||||
|
- single null byte
|
||||||
|
- name of the child contract
|
||||||
|
|
||||||
|
The resulting contract's public key can be predicted and in case it happens to
|
||||||
|
have some funds before its creation, its balance will be increased by
|
||||||
|
the `value` parameter.
|
||||||
|
|
||||||
|
The `value` argument (default `0`) is equivalent to the value in the contract
|
||||||
|
creation transaction – it sets the initial value of the newly created contract
|
||||||
|
charging the calling contract. Note that this won't be visible in `Call.value`
|
||||||
|
in the `init` call of the new contract. It will be included in
|
||||||
|
`Contract.balance`, however.
|
||||||
|
|
||||||
|
|
||||||
|
The type `'c` must be instantiated with a contract.
|
||||||
|
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
```
|
||||||
|
payable contract Auction =
|
||||||
|
record state = {supply: int, name: string}
|
||||||
|
entrypoint init(supply, name) = {supply: supply, name: name}
|
||||||
|
stateful payable entrypoint buy(amount) =
|
||||||
|
require(Call.value == amount, "amount_value_mismatch")
|
||||||
|
...
|
||||||
|
stateful entrypoint sell(amount) =
|
||||||
|
require(amount >= 0, "negative_amount")
|
||||||
|
...
|
||||||
|
|
||||||
|
main contract Market =
|
||||||
|
type state = list(Auction)
|
||||||
|
entrypoint init() = []
|
||||||
|
stateful entrypoint new(name : string) =
|
||||||
|
let auction = Chain.create(0, name) : Auction
|
||||||
|
put(new_auction::state)
|
||||||
|
```
|
||||||
|
|
||||||
|
The typechecker must be certain about the created contract's type, so it is
|
||||||
|
worth writing it explicitly as shown in the example.
|
||||||
|
|
||||||
|
#### clone
|
||||||
|
```
|
||||||
|
Chain.clone : ( ref : 'c, gas : int, value : int, protected : bool, ...
|
||||||
|
) => if(protected) option('c) else 'c
|
||||||
|
```
|
||||||
|
|
||||||
|
Clones the contract under the mandatory named argument `ref`. That means a new
|
||||||
|
contract of the same bytecode and the same `payable` parameter shall be created.
|
||||||
|
**NOTE:** the `state` won't be copied and the contract will be initialized with
|
||||||
|
a regular call to the `init` function with the remaining unnamed arguments. The
|
||||||
|
resulting contract's public key can be predicted and in case it happens to have
|
||||||
|
some funds before its creation, its balance will be increased by the `value`
|
||||||
|
parameter. This operation is significantly cheaper than `Chain.create` as it
|
||||||
|
costs a fixed amount of gas.
|
||||||
|
|
||||||
|
|
||||||
|
The `gas` argument (default `Call.gas_left`) limits the gas supply for the
|
||||||
|
`init` call of the cloned contract.
|
||||||
|
|
||||||
|
The `value` argument (default `0`) is equivalent to the value in the contract
|
||||||
|
creation transaction – it sets the initial value of the newly created contract
|
||||||
|
charging the calling contract. Note that this won't be visible in `Call.value`
|
||||||
|
in the `init` call of the new contract. It will be included in
|
||||||
|
`Contract.balance`, however.
|
||||||
|
|
||||||
|
The `protected` argument (default `false`) works identically as in remote calls.
|
||||||
|
If set to `true` it will change the return type to `option('c)` and will catch
|
||||||
|
all errors such as `abort`, out of gas and wrong arguments. Note that it can
|
||||||
|
only take a boolean *literal*, so other expressions such as variables will be
|
||||||
|
rejected by the compiler.
|
||||||
|
|
||||||
|
The type `'c` must be instantiated with a contract.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
```
|
||||||
|
payable contract interface Auction =
|
||||||
|
entrypoint init : (int, string) => void
|
||||||
|
stateful payable entrypoint buy : (int) => ()
|
||||||
|
stateful entrypoint sell : (int) => ()
|
||||||
|
|
||||||
|
main contract Market =
|
||||||
|
type state = list(Auction)
|
||||||
|
entrypoint init() = []
|
||||||
|
stateful entrypoint new_of(template : Auction, name : string) =
|
||||||
|
switch(Chain.clone(ref=template, protected=true, 0, name))
|
||||||
|
None => abort("Bad auction!")
|
||||||
|
Some(new_auction) =>
|
||||||
|
put(new_auction::state)
|
||||||
|
```
|
||||||
|
|
||||||
|
When cloning by an interface, `init` entrypoint declaration is required. It is a
|
||||||
|
good practice to set its return type to `void` in order to indicate that this
|
||||||
|
function is not supposed to be called and is state agnostic. Trivia: internal
|
||||||
|
implementation of the `init` function does not actually return `state`, but
|
||||||
|
calls `put` instead. Moreover, FATE prevents even handcrafted calls to `init`.
|
||||||
|
|
||||||
|
|
||||||
#### event
|
#### event
|
||||||
```
|
```
|
||||||
Chain.event(e : event) : unit
|
Chain.event(e : event) : unit
|
||||||
|
@ -208,10 +208,13 @@ namespace List =
|
|||||||
[] => false
|
[] => false
|
||||||
h::t => if(p(h)) true else any(p, t)
|
h::t => if(p(h)) true else any(p, t)
|
||||||
|
|
||||||
function sum(l : list(int)) : int = foldl ((a, b) => a + b, 0, l)
|
function sum(l : list(int)) : int = switch(l)
|
||||||
|
[] => 0
|
||||||
function product(l : list(int)) : int = foldl((a, b) => a * b, 1, l)
|
h::t => h + sum(t)
|
||||||
|
|
||||||
|
function product(l : list(int)) : int = switch(l)
|
||||||
|
[] => 1
|
||||||
|
h::t => h * sum(t)
|
||||||
|
|
||||||
/** Zips two list by applying bimapping function on respective elements.
|
/** Zips two list by applying bimapping function on respective elements.
|
||||||
* Drops the tail of the longer list.
|
* Drops the tail of the longer list.
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{erl_opts, [debug_info]}.
|
{erl_opts, [debug_info]}.
|
||||||
|
|
||||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"7f0d309"}}}
|
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"951db9f"}}}
|
||||||
, {getopt, "1.0.1"}
|
, {getopt, "1.0.1"}
|
||||||
, {eblake2, "1.0.0"}
|
, {eblake2, "1.0.0"}
|
||||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{"1.1.0",
|
{"1.1.0",
|
||||||
[{<<"aebytecode">>,
|
[{<<"aebytecode">>,
|
||||||
{git,"https://github.com/aeternity/aebytecode.git",
|
{git,"https://github.com/aeternity/aebytecode.git",
|
||||||
{ref,"7f0d3090d4dc6c4d5fca7645b0c21eb0e65ad208"}},
|
{ref,"951db9f38412a1f798987403d24c512d82fec8c7"}},
|
||||||
0},
|
0},
|
||||||
{<<"aeserialization">>,
|
{<<"aeserialization">>,
|
||||||
{git,"https://github.com/aeternity/aeserialization.git",
|
{git,"https://github.com/aeternity/aeserialization.git",
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
, json_encode_expr/1
|
, json_encode_expr/1
|
||||||
, json_encode_type/1]).
|
, json_encode_type/1]).
|
||||||
|
|
||||||
|
-include("aeso_utils.hrl").
|
||||||
|
|
||||||
-type aci_type() :: json | string.
|
-type aci_type() :: json | string.
|
||||||
-type json() :: jsx:json_term().
|
-type json() :: jsx:json_term().
|
||||||
-type json_text() :: binary().
|
-type json_text() :: binary().
|
||||||
@ -68,9 +70,7 @@ do_contract_interface(Type, Contract, Options) when is_binary(Contract) ->
|
|||||||
do_contract_interface(Type, ContractString, Options) ->
|
do_contract_interface(Type, ContractString, Options) ->
|
||||||
try
|
try
|
||||||
Ast = aeso_compiler:parse(ContractString, Options),
|
Ast = aeso_compiler:parse(ContractString, Options),
|
||||||
%% io:format("~p\n", [Ast]),
|
|
||||||
{TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
|
{TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
|
||||||
%% io:format("~p\n", [TypedAst]),
|
|
||||||
from_typed_ast(Type, TypedAst)
|
from_typed_ast(Type, TypedAst)
|
||||||
catch
|
catch
|
||||||
throw:{error, Errors} -> {error, Errors}
|
throw:{error, Errors} -> {error, Errors}
|
||||||
@ -83,7 +83,7 @@ from_typed_ast(Type, TypedAst) ->
|
|||||||
string -> do_render_aci_json(JArray)
|
string -> do_render_aci_json(JArray)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
|
encode_contract(Contract = {Head, _, {con, _, Name}, _}) when ?IS_CONTRACT_HEAD(Head) ->
|
||||||
C0 = #{name => encode_name(Name)},
|
C0 = #{name => encode_name(Name)},
|
||||||
|
|
||||||
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ],
|
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ],
|
||||||
@ -107,7 +107,7 @@ encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
|
|||||||
|| F <- sort_decls(contract_funcs(Contract)),
|
|| F <- sort_decls(contract_funcs(Contract)),
|
||||||
is_entrypoint(F) ],
|
is_entrypoint(F) ],
|
||||||
|
|
||||||
#{contract => C3#{functions => Fdefs, payable => is_payable(Contract)}};
|
#{contract => C3#{kind => Head, functions => Fdefs, payable => is_payable(Contract)}};
|
||||||
encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) ->
|
encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) ->
|
||||||
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ],
|
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ],
|
||||||
#{namespace => #{name => encode_name(Name),
|
#{namespace => #{name => encode_name(Name),
|
||||||
@ -232,13 +232,19 @@ do_render_aci_json(Json) ->
|
|||||||
{ok, list_to_binary(string:join(DecodedContracts, "\n"))}.
|
{ok, list_to_binary(string:join(DecodedContracts, "\n"))}.
|
||||||
|
|
||||||
decode_contract(#{contract := #{name := Name,
|
decode_contract(#{contract := #{name := Name,
|
||||||
|
kind := Kind,
|
||||||
payable := Payable,
|
payable := Payable,
|
||||||
type_defs := Ts0,
|
type_defs := Ts0,
|
||||||
functions := Fs} = C}) ->
|
functions := Fs} = C}) ->
|
||||||
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
|
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
|
||||||
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
|
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
|
||||||
[ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0,
|
[ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0,
|
||||||
[payable(Payable), "contract ", io_lib:format("~s", [Name])," =\n",
|
[payable(Payable), case Kind of
|
||||||
|
contract_main -> "main contract ";
|
||||||
|
contract_child -> "contract ";
|
||||||
|
contract_interface -> "contract interface "
|
||||||
|
end,
|
||||||
|
io_lib:format("~s", [Name])," =\n",
|
||||||
decode_tdefs(Ts), decode_funcs(Fs)];
|
decode_tdefs(Ts), decode_funcs(Fs)];
|
||||||
decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] ->
|
decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] ->
|
||||||
["namespace ", io_lib:format("~s", [Name])," =\n",
|
["namespace ", io_lib:format("~s", [Name])," =\n",
|
||||||
@ -332,10 +338,10 @@ payable(false) -> "".
|
|||||||
|
|
||||||
%% #contract{Ann, Con, [Declarations]}.
|
%% #contract{Ann, Con, [Declarations]}.
|
||||||
|
|
||||||
contract_funcs({C, _, _, Decls}) when C == contract; C == namespace ->
|
contract_funcs({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace ->
|
||||||
[ D || D <- Decls, is_fun(D)].
|
[ D || D <- Decls, is_fun(D)].
|
||||||
|
|
||||||
contract_types({C, _, _, Decls}) when C == contract; C == namespace ->
|
contract_types({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace ->
|
||||||
[ D || D <- Decls, is_type(D) ].
|
[ D || D <- Decls, is_type(D) ].
|
||||||
|
|
||||||
is_fun({letfun, _, _, _, _, _}) -> true;
|
is_fun({letfun, _, _, _, _, _}) -> true;
|
||||||
|
@ -18,7 +18,9 @@
|
|||||||
, pp_type/2
|
, pp_type/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()], utype()}
|
-include("aeso_utils.hrl").
|
||||||
|
|
||||||
|
-type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()] | var_args, utype()}
|
||||||
| {app_t, aeso_syntax:ann(), utype(), [utype()]}
|
| {app_t, aeso_syntax:ann(), utype(), [utype()]}
|
||||||
| {tuple_t, aeso_syntax:ann(), [utype()]}
|
| {tuple_t, aeso_syntax:ann(), [utype()]}
|
||||||
| aeso_syntax:id() | aeso_syntax:qid()
|
| aeso_syntax:id() | aeso_syntax:qid()
|
||||||
@ -39,6 +41,7 @@
|
|||||||
element(1, T) =:= qcon).
|
element(1, T) =:= qcon).
|
||||||
|
|
||||||
-type why_record() :: aeso_syntax:field(aeso_syntax:expr())
|
-type why_record() :: aeso_syntax:field(aeso_syntax:expr())
|
||||||
|
| {var_args, aeso_syntax:ann(), aeso_syntax:expr()}
|
||||||
| {proj, aeso_syntax:ann(), aeso_syntax:expr(), aeso_syntax:id()}.
|
| {proj, aeso_syntax:ann(), aeso_syntax:expr(), aeso_syntax:id()}.
|
||||||
|
|
||||||
-type pos() :: aeso_errors:pos().
|
-type pos() :: aeso_errors:pos().
|
||||||
@ -73,7 +76,10 @@
|
|||||||
-record(is_contract_constraint,
|
-record(is_contract_constraint,
|
||||||
{ contract_t :: utype(),
|
{ contract_t :: utype(),
|
||||||
context :: {contract_literal, aeso_syntax:expr()} |
|
context :: {contract_literal, aeso_syntax:expr()} |
|
||||||
{address_to_contract, aeso_syntax:ann()}
|
{address_to_contract, aeso_syntax:ann()} |
|
||||||
|
{bytecode_hash, aeso_syntax:ann()} |
|
||||||
|
{var_args, aeso_syntax:ann(), aeso_syntax:expr()},
|
||||||
|
force_def = false :: boolean()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}.
|
-type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}.
|
||||||
@ -96,7 +102,7 @@
|
|||||||
-type qname() :: [string()].
|
-type qname() :: [string()].
|
||||||
-type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [type()], type()}.
|
-type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [type()], type()}.
|
||||||
|
|
||||||
-type type_constraints() :: none | bytes_concat | bytes_split | address_to_contract.
|
-type type_constraints() :: none | bytes_concat | bytes_split | address_to_contract | bytecode_hash.
|
||||||
|
|
||||||
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
|
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
|
||||||
-type type_info() :: {aeso_syntax:ann(), typedef()}.
|
-type type_info() :: {aeso_syntax:ann(), typedef()}.
|
||||||
@ -123,13 +129,14 @@
|
|||||||
, in_pattern = false :: boolean()
|
, in_pattern = false :: boolean()
|
||||||
, stateful = false :: boolean()
|
, stateful = false :: boolean()
|
||||||
, current_function = none :: none | aeso_syntax:id()
|
, current_function = none :: none | aeso_syntax:id()
|
||||||
, what = top :: top | namespace | contract | main_contract
|
, what = top :: top | namespace | contract | contract_interface
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type env() :: #env{}.
|
-type env() :: #env{}.
|
||||||
|
|
||||||
-define(PRINT_TYPES(Fmt, Args),
|
-define(PRINT_TYPES(Fmt, Args),
|
||||||
when_option(pp_types, fun () -> io:format(Fmt, Args) end)).
|
when_option(pp_types, fun () -> io:format(Fmt, Args) end)).
|
||||||
|
-define(CONSTRUCTOR_MOCK_NAME, "#__constructor__#").
|
||||||
|
|
||||||
%% -- Environment manipulation -----------------------------------------------
|
%% -- Environment manipulation -----------------------------------------------
|
||||||
|
|
||||||
@ -191,9 +198,9 @@ bind_fun(X, Type, Env) ->
|
|||||||
force_bind_fun(X, Type, Env = #env{ what = What }) ->
|
force_bind_fun(X, Type, Env = #env{ what = What }) ->
|
||||||
Ann = aeso_syntax:get_ann(Type),
|
Ann = aeso_syntax:get_ann(Type),
|
||||||
NoCode = get_option(no_code, false),
|
NoCode = get_option(no_code, false),
|
||||||
Entry = if X == "init", What == main_contract, not NoCode ->
|
Entry = if X == "init", What == contract, not NoCode ->
|
||||||
{reserved_init, Ann, Type};
|
{reserved_init, Ann, Type};
|
||||||
What == contract -> {contract_fun, Ann, Type};
|
What == contract_interface -> {contract_fun, Ann, Type};
|
||||||
true -> {Ann, Type}
|
true -> {Ann, Type}
|
||||||
end,
|
end,
|
||||||
on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) ->
|
on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) ->
|
||||||
@ -261,13 +268,40 @@ contract_call_type({fun_t, Ann, [], Args, Ret}) ->
|
|||||||
Args, {if_t, Ann, Id("protected"), {app_t, Ann, {id, Ann, "option"}, [Ret]}, Ret}}.
|
Args, {if_t, Ann, Id("protected"), {app_t, Ann, {id, Ann, "option"}, [Ret]}, Ret}}.
|
||||||
|
|
||||||
-spec bind_contract(aeso_syntax:decl(), env()) -> env().
|
-spec bind_contract(aeso_syntax:decl(), env()) -> env().
|
||||||
bind_contract({contract, Ann, Id, Contents}, Env) ->
|
bind_contract({Contract, Ann, Id, Contents}, Env)
|
||||||
|
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||||
Key = name(Id),
|
Key = name(Id),
|
||||||
Sys = [{origin, system}],
|
Sys = [{origin, system}],
|
||||||
Fields = [ {field_t, AnnF, Entrypoint, contract_call_type(Type)}
|
Fields =
|
||||||
|| {fun_decl, AnnF, Entrypoint, Type} <- Contents ] ++
|
[ {field_t, AnnF, Entrypoint, contract_call_type(Type)}
|
||||||
%% Predefined fields
|
|| {fun_decl, AnnF, Entrypoint, Type} <- Contents ] ++
|
||||||
[ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ],
|
[ {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,
|
FieldInfo = [ {Entrypoint, #field_info{ ann = FieldAnn,
|
||||||
kind = contract,
|
kind = contract,
|
||||||
field_t = Type,
|
field_t = Type,
|
||||||
@ -396,8 +430,11 @@ global_env() ->
|
|||||||
Map = fun(A, B) -> {app_t, Ann, {id, Ann, "map"}, [A, B]} end,
|
Map = fun(A, B) -> {app_t, Ann, {id, Ann, "map"}, [A, B]} end,
|
||||||
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
|
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
|
||||||
FunC = fun(C, Ts, T) -> {type_sig, Ann, C, [], Ts, T} end,
|
FunC = fun(C, Ts, T) -> {type_sig, Ann, C, [], Ts, T} end,
|
||||||
|
FunC1 = fun(C, S, T) -> {type_sig, Ann, C, [], [S], T} end,
|
||||||
Fun = fun(Ts, T) -> FunC(none, Ts, T) end,
|
Fun = fun(Ts, T) -> FunC(none, Ts, T) end,
|
||||||
Fun1 = fun(S, T) -> Fun([S], T) end,
|
Fun1 = fun(S, T) -> Fun([S], T) end,
|
||||||
|
FunCN = fun(C, Named, Normal, Ret) -> {type_sig, Ann, C, Named, Normal, Ret} end,
|
||||||
|
FunN = fun(Named, Normal, Ret) -> FunCN(none, Named, Normal, Ret) end,
|
||||||
%% Lambda = fun(Ts, T) -> {fun_t, Ann, [], Ts, T} end,
|
%% Lambda = fun(Ts, T) -> {fun_t, Ann, [], Ts, T} end,
|
||||||
%% Lambda1 = fun(S, T) -> Lambda([S], T) end,
|
%% Lambda1 = fun(S, T) -> Lambda([S], T) end,
|
||||||
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], none, [], Ts, T} end,
|
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], none, [], Ts, T} end,
|
||||||
@ -424,6 +461,7 @@ global_env() ->
|
|||||||
TxFlds = [{"paying_for", Option(PayForTx)}, {"ga_metas", List(GAMetaTx)},
|
TxFlds = [{"paying_for", Option(PayForTx)}, {"ga_metas", List(GAMetaTx)},
|
||||||
{"actor", Address}, {"fee", Int}, {"ttl", Int}, {"tx", BaseTx}],
|
{"actor", Address}, {"fee", Int}, {"ttl", Int}, {"tx", BaseTx}],
|
||||||
TxType = {record_t, [FldT(N, T) || {N, T} <- TxFlds ]},
|
TxType = {record_t, [FldT(N, T) || {N, T} <- TxFlds ]},
|
||||||
|
Stateful = fun(T) -> setelement(2, T, [stateful|element(2, T)]) end,
|
||||||
|
|
||||||
Fee = Int,
|
Fee = Int,
|
||||||
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
|
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
|
||||||
@ -443,6 +481,7 @@ global_env() ->
|
|||||||
{"require", Fun([Bool, String], Unit)}])
|
{"require", Fun([Bool, String], Unit)}])
|
||||||
, types = MkDefs(
|
, types = MkDefs(
|
||||||
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
|
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
|
||||||
|
{"void", 0},
|
||||||
{"unit", {[], {alias_t, Unit}}},
|
{"unit", {[], {alias_t, Unit}}},
|
||||||
{"hash", {[], {alias_t, Bytes(32)}}},
|
{"hash", {[], {alias_t, Bytes(32)}}},
|
||||||
{"signature", {[], {alias_t, Bytes(64)}}},
|
{"signature", {[], {alias_t, Bytes(64)}}},
|
||||||
@ -463,6 +502,23 @@ global_env() ->
|
|||||||
{"block_height", Int},
|
{"block_height", Int},
|
||||||
{"difficulty", Int},
|
{"difficulty", Int},
|
||||||
{"gas_limit", Int},
|
{"gas_limit", Int},
|
||||||
|
{"bytecode_hash",FunC1(bytecode_hash, A, Option(Hash))},
|
||||||
|
{"create", Stateful(
|
||||||
|
FunN([ {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}}
|
||||||
|
], var_args, A))},
|
||||||
|
{"clone", Stateful(
|
||||||
|
FunN([ {named_arg_t, Ann, {id, Ann, "gas"}, Int,
|
||||||
|
{typed, Ann,
|
||||||
|
{app, Ann,
|
||||||
|
{typed, Ann, {qid, Ann, ["Call","gas_left"]},
|
||||||
|
typesig_to_fun_t(Fun([], Int))
|
||||||
|
},
|
||||||
|
[]}, Int
|
||||||
|
}}
|
||||||
|
, {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}}
|
||||||
|
, {named_arg_t, Ann, {id, Ann, "protected"}, Bool, {typed, Ann, {bool, Ann, false}, Bool}}
|
||||||
|
, {named_arg_t, Ann, {id, Ann, "ref"}, A, undefined}
|
||||||
|
], var_args, A))},
|
||||||
%% Tx constructors
|
%% Tx constructors
|
||||||
{"GAMetaTx", Fun([Address, Int], GAMetaTx)},
|
{"GAMetaTx", Fun([Address, Int], GAMetaTx)},
|
||||||
{"PayingForTx", Fun([Address, Int], PayForTx)},
|
{"PayingForTx", Fun([Address, Int], PayForTx)},
|
||||||
@ -699,9 +755,13 @@ infer(Contracts, Options) ->
|
|||||||
try
|
try
|
||||||
Env = init_env(Options),
|
Env = init_env(Options),
|
||||||
create_options(Options),
|
create_options(Options),
|
||||||
|
ets_new(defined_contracts, [bag]),
|
||||||
ets_new(type_vars, [set]),
|
ets_new(type_vars, [set]),
|
||||||
check_modifiers(Env, Contracts),
|
check_modifiers(Env, Contracts),
|
||||||
{Env1, Decls} = infer1(Env, Contracts, [], Options),
|
create_type_errors(),
|
||||||
|
Contracts1 = identify_main_contract(Contracts, Options),
|
||||||
|
destroy_and_report_type_errors(Env),
|
||||||
|
{Env1, Decls} = infer1(Env, Contracts1, [], Options),
|
||||||
{Env2, DeclsFolded, DeclsUnfolded} =
|
{Env2, DeclsFolded, DeclsUnfolded} =
|
||||||
case proplists:get_value(dont_unfold, Options, false) of
|
case proplists:get_value(dont_unfold, Options, false) of
|
||||||
true -> {Env1, Decls, Decls};
|
true -> {Env1, Decls, Decls};
|
||||||
@ -719,12 +779,21 @@ infer(Contracts, Options) ->
|
|||||||
-spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) ->
|
-spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) ->
|
||||||
{env(), [aeso_syntax:decl()]}.
|
{env(), [aeso_syntax:decl()]}.
|
||||||
infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)};
|
infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)};
|
||||||
infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc, Options) ->
|
infer1(Env, [{Contract, Ann, ConName, Code} | Rest], Acc, Options)
|
||||||
|
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||||
%% do type inference on each contract independently.
|
%% do type inference on each contract independently.
|
||||||
check_scope_name_clash(Env, contract, ConName),
|
check_scope_name_clash(Env, contract, ConName),
|
||||||
What = if Rest == [] -> main_contract; true -> contract end,
|
What = case Contract of
|
||||||
|
contract_main -> contract;
|
||||||
|
contract_child -> contract;
|
||||||
|
contract_interface -> contract_interface
|
||||||
|
end,
|
||||||
|
case What of
|
||||||
|
contract -> ets_insert(defined_contracts, {qname(ConName)});
|
||||||
|
contract_interface -> ok
|
||||||
|
end,
|
||||||
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), What, Code, Options),
|
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), What, Code, Options),
|
||||||
Contract1 = {contract, Ann, ConName, Code1},
|
Contract1 = {Contract, Ann, ConName, Code1},
|
||||||
Env2 = pop_scope(Env1),
|
Env2 = pop_scope(Env1),
|
||||||
Env3 = bind_contract(Contract1, Env2),
|
Env3 = bind_contract(Contract1, Env2),
|
||||||
infer1(Env3, Rest, [Contract1 | Acc], Options);
|
infer1(Env3, Rest, [Contract1 | Acc], Options);
|
||||||
@ -737,6 +806,25 @@ infer1(Env, [{pragma, _, _} | Rest], Acc, Options) ->
|
|||||||
%% Pragmas are checked in check_modifiers
|
%% Pragmas are checked in check_modifiers
|
||||||
infer1(Env, Rest, Acc, Options).
|
infer1(Env, Rest, Acc, Options).
|
||||||
|
|
||||||
|
%% Asserts that the main contract is somehow defined.
|
||||||
|
identify_main_contract(Contracts, Options) ->
|
||||||
|
Children = [C || C = {contract_child, _, _, _} <- Contracts],
|
||||||
|
Mains = [C || C = {contract_main, _, _, _} <- Contracts],
|
||||||
|
case Mains of
|
||||||
|
[] -> case Children of
|
||||||
|
[] -> type_error(
|
||||||
|
{main_contract_undefined,
|
||||||
|
[{file, File} || {src_file, File} <- Options]});
|
||||||
|
[{contract_child, Ann, Con, Body}] ->
|
||||||
|
(Contracts -- Children) ++ [{contract_main, Ann, Con, Body}];
|
||||||
|
[H|_] -> type_error({ambiguous_main_contract,
|
||||||
|
aeso_syntax:get_ann(H)})
|
||||||
|
end;
|
||||||
|
[_] -> (Contracts -- Mains) ++ Mains; %% Move to the end
|
||||||
|
[H|_] -> type_error({multiple_main_contracts,
|
||||||
|
aeso_syntax:get_ann(H)})
|
||||||
|
end.
|
||||||
|
|
||||||
check_scope_name_clash(Env, Kind, Name) ->
|
check_scope_name_clash(Env, Kind, Name) ->
|
||||||
case get_scope(Env, qname(Name)) of
|
case get_scope(Env, qname(Name)) of
|
||||||
false -> ok;
|
false -> ok;
|
||||||
@ -746,7 +834,7 @@ check_scope_name_clash(Env, Kind, Name) ->
|
|||||||
destroy_and_report_type_errors(Env)
|
destroy_and_report_type_errors(Env)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec infer_contract_top(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) ->
|
-spec infer_contract_top(env(), contract_interface | contract | namespace, [aeso_syntax:decl()], list(option())) ->
|
||||||
{env(), [aeso_syntax:decl()]}.
|
{env(), [aeso_syntax:decl()]}.
|
||||||
infer_contract_top(Env, Kind, Defs0, Options) ->
|
infer_contract_top(Env, Kind, Defs0, Options) ->
|
||||||
create_type_errors(),
|
create_type_errors(),
|
||||||
@ -756,7 +844,7 @@ infer_contract_top(Env, Kind, Defs0, Options) ->
|
|||||||
|
|
||||||
%% infer_contract takes a proplist mapping global names to types, and
|
%% infer_contract takes a proplist mapping global names to types, and
|
||||||
%% a list of definitions.
|
%% a list of definitions.
|
||||||
-spec infer_contract(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) -> {env(), [aeso_syntax:decl()]}.
|
-spec infer_contract(env(), contract_interface | contract | namespace, [aeso_syntax:decl()], list(option())) -> {env(), [aeso_syntax:decl()]}.
|
||||||
infer_contract(Env0, What, Defs0, Options) ->
|
infer_contract(Env0, What, Defs0, Options) ->
|
||||||
create_type_errors(),
|
create_type_errors(),
|
||||||
Defs01 = process_blocks(Defs0),
|
Defs01 = process_blocks(Defs0),
|
||||||
@ -772,19 +860,19 @@ infer_contract(Env0, What, Defs0, Options) ->
|
|||||||
({fun_decl, _, _, _}) -> prototype;
|
({fun_decl, _, _, _}) -> prototype;
|
||||||
(_) -> unexpected
|
(_) -> unexpected
|
||||||
end,
|
end,
|
||||||
Get = fun(K) -> [ Def || Def <- Defs, Kind(Def) == K ] end,
|
Get = fun(K, In) -> [ Def || Def <- In, Kind(Def) == K ] end,
|
||||||
{Env1, TypeDefs} = check_typedefs(Env, Get(type)),
|
{Env1, TypeDefs} = check_typedefs(Env, Get(type, Defs)),
|
||||||
create_type_errors(),
|
create_type_errors(),
|
||||||
check_unexpected(Get(unexpected)),
|
check_unexpected(Get(unexpected, Defs)),
|
||||||
Env2 =
|
Env2 =
|
||||||
case What of
|
case What of
|
||||||
namespace -> Env1;
|
namespace -> Env1;
|
||||||
contract -> Env1;
|
contract_interface -> Env1;
|
||||||
main_contract -> bind_state(Env1) %% bind state and put
|
contract -> bind_state(Env1) %% bind state and put
|
||||||
end,
|
end,
|
||||||
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype) ]),
|
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype, Defs) ]),
|
||||||
Env3 = bind_funs(ProtoSigs, Env2),
|
Env3 = bind_funs(ProtoSigs, Env2),
|
||||||
Functions = Get(function),
|
Functions = Get(function, Defs),
|
||||||
%% Check for duplicates in Functions (we turn it into a map below)
|
%% Check for duplicates in Functions (we turn it into a map below)
|
||||||
FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}};
|
FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}};
|
||||||
({fun_clauses, Ann, {id, _, Fun}, _, _}) -> {Fun, {tuple_t, Ann, []}} end,
|
({fun_clauses, Ann, {id, _, Fun}, _, _}) -> {Fun, {tuple_t, Ann, []}} end,
|
||||||
@ -794,11 +882,11 @@ infer_contract(Env0, What, Defs0, Options) ->
|
|||||||
check_reserved_entrypoints(FunMap),
|
check_reserved_entrypoints(FunMap),
|
||||||
DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_ids(Def) end, FunMap),
|
DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_ids(Def) end, FunMap),
|
||||||
SCCs = aeso_utils:scc(DepGraph),
|
SCCs = aeso_utils:scc(DepGraph),
|
||||||
%% io:format("Dependency sorted functions:\n ~p\n", [SCCs]),
|
|
||||||
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
|
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
|
||||||
%% Check that `init` doesn't read or write the state
|
%% Check that `init` doesn't read or write the state
|
||||||
check_state_dependencies(Env4, Defs1),
|
check_state_dependencies(Env4, Defs1),
|
||||||
destroy_and_report_type_errors(Env4),
|
destroy_and_report_type_errors(Env4),
|
||||||
|
%% Add inferred types of definitions
|
||||||
{Env4, TypeDefs ++ Decls ++ Defs1}.
|
{Env4, TypeDefs ++ Decls ++ Defs1}.
|
||||||
|
|
||||||
%% Restructure blocks into multi-clause fundefs (`fun_clauses`).
|
%% Restructure blocks into multi-clause fundefs (`fun_clauses`).
|
||||||
@ -830,9 +918,9 @@ expose_internals(Defs, What) ->
|
|||||||
[ begin
|
[ begin
|
||||||
Ann = element(2, Def),
|
Ann = element(2, Def),
|
||||||
NewAnn = case What of
|
NewAnn = case What of
|
||||||
namespace -> [A ||A <- Ann, A /= {private, true}, A /= private];
|
namespace -> [A ||A <- Ann, A /= {private, true}, A /= private];
|
||||||
main_contract -> [{entrypoint, true}|Ann]; % minor duplication
|
contract -> [{entrypoint, true}|Ann]; % minor duplication
|
||||||
contract -> Ann
|
contract_interface -> Ann
|
||||||
end,
|
end,
|
||||||
Def1 = setelement(2, Def, NewAnn),
|
Def1 = setelement(2, Def, NewAnn),
|
||||||
case Def1 of % fix inner clauses
|
case Def1 of % fix inner clauses
|
||||||
@ -907,15 +995,16 @@ check_modifiers(Env, Contracts) ->
|
|||||||
check_modifiers_(Env, Contracts),
|
check_modifiers_(Env, Contracts),
|
||||||
destroy_and_report_type_errors(Env).
|
destroy_and_report_type_errors(Env).
|
||||||
|
|
||||||
check_modifiers_(Env, [{contract, _, Con, Decls} | Rest]) ->
|
check_modifiers_(Env, [{Contract, _, Con, Decls} | Rest])
|
||||||
IsMain = Rest == [],
|
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||||
|
IsInterface = Contract =:= contract_interface,
|
||||||
check_modifiers1(contract, Decls),
|
check_modifiers1(contract, Decls),
|
||||||
case {lists:keymember(letfun, 1, Decls),
|
case {lists:keymember(letfun, 1, Decls),
|
||||||
[ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of
|
[ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of
|
||||||
{true, []} -> type_error({contract_has_no_entrypoints, Con});
|
{true, []} -> type_error({contract_has_no_entrypoints, Con});
|
||||||
_ when not IsMain ->
|
_ when IsInterface ->
|
||||||
case [ {Ann, Id} || {letfun, Ann, Id, _, _, _} <- Decls ] of
|
case [ {AnnF, Id} || {letfun, AnnF, Id, _, _, _} <- Decls ] of
|
||||||
[{Ann, Id} | _] -> type_error({definition_in_non_main_contract, Ann, Id});
|
[{AnnF, Id} | _] -> type_error({definition_in_contract_interface, AnnF, Id});
|
||||||
[] -> ok
|
[] -> ok
|
||||||
end;
|
end;
|
||||||
_ -> ok
|
_ -> ok
|
||||||
@ -1408,8 +1497,7 @@ infer_expr(Env, {typed, As, Body, Type}) ->
|
|||||||
{typed, _, NewBody, NewType} = check_expr(Env, Body, Type1),
|
{typed, _, NewBody, NewType} = check_expr(Env, Body, Type1),
|
||||||
{typed, As, NewBody, NewType};
|
{typed, As, NewBody, NewType};
|
||||||
infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
|
infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
|
||||||
NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ],
|
{NamedArgs, Args} = split_args(Args0),
|
||||||
Args = Args0 -- NamedArgs,
|
|
||||||
case aeso_syntax:get_ann(format, Ann) of
|
case aeso_syntax:get_ann(format, Ann) of
|
||||||
infix ->
|
infix ->
|
||||||
infer_op(Env, Ann, Fun, Args, fun infer_infix/1);
|
infer_op(Env, Ann, Fun, Args, fun infer_infix/1);
|
||||||
@ -1418,13 +1506,13 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
|
|||||||
_ ->
|
_ ->
|
||||||
NamedArgsVar = fresh_uvar(Ann),
|
NamedArgsVar = fresh_uvar(Ann),
|
||||||
NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ],
|
NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ],
|
||||||
%% TODO: named args constraints
|
NewFun0 = infer_expr(Env, Fun),
|
||||||
NewFun={typed, _, _, FunType} = infer_expr(Env, Fun),
|
|
||||||
NewArgs = [infer_expr(Env, A) || A <- Args],
|
NewArgs = [infer_expr(Env, A) || A <- Args],
|
||||||
ArgTypes = [T || {typed, _, _, T} <- NewArgs],
|
ArgTypes = [T || {typed, _, _, T} <- NewArgs],
|
||||||
|
NewFun1 = {typed, _, _, FunType} = infer_var_args_fun(Env, NewFun0, NamedArgs1, ArgTypes),
|
||||||
|
When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes},
|
||||||
GeneralResultType = fresh_uvar(Ann),
|
GeneralResultType = fresh_uvar(Ann),
|
||||||
ResultType = fresh_uvar(Ann),
|
ResultType = fresh_uvar(Ann),
|
||||||
When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes},
|
|
||||||
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When),
|
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When),
|
||||||
add_named_argument_constraint(
|
add_named_argument_constraint(
|
||||||
#dependent_type_constraint{ named_args_t = NamedArgsVar,
|
#dependent_type_constraint{ named_args_t = NamedArgsVar,
|
||||||
@ -1432,7 +1520,7 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
|
|||||||
general_type = GeneralResultType,
|
general_type = GeneralResultType,
|
||||||
specialized_type = ResultType,
|
specialized_type = ResultType,
|
||||||
context = {check_return, App} }),
|
context = {check_return, App} }),
|
||||||
{typed, Ann, {app, Ann, NewFun, NamedArgs1 ++ NewArgs}, dereference(ResultType)}
|
{typed, Ann, {app, Ann, NewFun1, NamedArgs1 ++ NewArgs}, dereference(ResultType)}
|
||||||
end;
|
end;
|
||||||
infer_expr(Env, {'if', Attrs, Cond, Then, Else}) ->
|
infer_expr(Env, {'if', Attrs, Cond, Then, Else}) ->
|
||||||
NewCond = check_expr(Env, Cond, {id, Attrs, "bool"}),
|
NewCond = check_expr(Env, Cond, {id, Attrs, "bool"}),
|
||||||
@ -1537,6 +1625,62 @@ infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) ->
|
|||||||
type_error({missing_body_for_let, Attrs}),
|
type_error({missing_body_for_let, Attrs}),
|
||||||
infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}).
|
infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}).
|
||||||
|
|
||||||
|
infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) ->
|
||||||
|
FunType =
|
||||||
|
case Fun of
|
||||||
|
{qid, _, ["Chain", "create"]} ->
|
||||||
|
{fun_t, _, NamedArgsT, var_args, RetT} = FunType0,
|
||||||
|
GasCapMock = {named_arg_t, Ann, {id, Ann, "gas"}, {id, Ann, "int"}, {int, Ann, 0}},
|
||||||
|
ProtectedMock = {named_arg_t, Ann, {id, Ann, "protected"}, {id, Ann, "bool"}, {bool, Ann, false}},
|
||||||
|
NamedArgsT1 = case NamedArgsT of
|
||||||
|
[Value|Rest] -> [GasCapMock, Value, ProtectedMock|Rest];
|
||||||
|
% generally type error, but will be caught
|
||||||
|
_ -> [GasCapMock, ProtectedMock|NamedArgsT]
|
||||||
|
end,
|
||||||
|
check_contract_construction(Env, true, RetT, Fun, NamedArgsT1, ArgTypes, RetT),
|
||||||
|
{fun_t, Ann, NamedArgsT, ArgTypes, RetT};
|
||||||
|
{qid, _, ["Chain", "clone"]} ->
|
||||||
|
{fun_t, _, NamedArgsT, var_args, RetT} = FunType0,
|
||||||
|
ContractT =
|
||||||
|
case [ContractT || {named_arg, _, {id, _, "ref"}, {typed, _, _, ContractT}} <- NamedArgs] of
|
||||||
|
[C] -> C;
|
||||||
|
_ -> type_error({clone_no_contract, Ann}),
|
||||||
|
fresh_uvar(Ann)
|
||||||
|
end,
|
||||||
|
NamedArgsTNoRef =
|
||||||
|
lists:filter(fun({named_arg_t, _, {id, _, "ref"}, _, _}) -> false; (_) -> true end, NamedArgsT),
|
||||||
|
check_contract_construction(Env, false, ContractT, Fun, NamedArgsTNoRef, ArgTypes, RetT),
|
||||||
|
{fun_t, Ann, NamedArgsT, ArgTypes,
|
||||||
|
{if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}};
|
||||||
|
_ -> FunType0
|
||||||
|
end,
|
||||||
|
{typed, Ann, Fun, FunType}.
|
||||||
|
|
||||||
|
-spec check_contract_construction(env(), boolean(), utype(), utype(), named_args_t(), [utype()], utype()) -> ok.
|
||||||
|
check_contract_construction(Env, ForceDef, ContractT, Fun, NamedArgsT, ArgTypes, RetT) ->
|
||||||
|
Ann = aeso_syntax:get_ann(Fun),
|
||||||
|
InitT = fresh_uvar(Ann),
|
||||||
|
unify(Env, InitT, {fun_t, Ann, NamedArgsT, ArgTypes, fresh_uvar(Ann)}, {checking_init_args, Ann, ContractT, ArgTypes}),
|
||||||
|
unify(Env, RetT, ContractT, {return_contract, Fun, ContractT}),
|
||||||
|
constrain(
|
||||||
|
[ #field_constraint{
|
||||||
|
record_t = unfold_types_in_type(Env, ContractT),
|
||||||
|
field = {id, Ann, ?CONSTRUCTOR_MOCK_NAME},
|
||||||
|
field_t = InitT,
|
||||||
|
kind = project,
|
||||||
|
context = {var_args, Ann, Fun} }
|
||||||
|
, #is_contract_constraint{ contract_t = ContractT,
|
||||||
|
context = {var_args, Ann, Fun},
|
||||||
|
force_def = ForceDef
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
split_args(Args0) ->
|
||||||
|
NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ],
|
||||||
|
Args = Args0 -- NamedArgs,
|
||||||
|
{NamedArgs, Args}.
|
||||||
|
|
||||||
infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) ->
|
infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) ->
|
||||||
CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E),
|
CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E),
|
||||||
check_stateful_named_arg(Env, Id, E),
|
check_stateful_named_arg(Env, Id, E),
|
||||||
@ -1704,7 +1848,7 @@ next_count() ->
|
|||||||
|
|
||||||
ets_tables() ->
|
ets_tables() ->
|
||||||
[options, type_vars, type_defs, record_fields, named_argument_constraints,
|
[options, type_vars, type_defs, record_fields, named_argument_constraints,
|
||||||
field_constraints, freshen_tvars, type_errors].
|
field_constraints, freshen_tvars, type_errors, defined_contracts].
|
||||||
|
|
||||||
clean_up_ets() ->
|
clean_up_ets() ->
|
||||||
[ catch ets_delete(Tab) || Tab <- ets_tables() ],
|
[ catch ets_delete(Tab) || Tab <- ets_tables() ],
|
||||||
@ -1814,7 +1958,7 @@ solve_named_argument_constraints(Env, Constraints0) ->
|
|||||||
[ C || C <- dereference_deep(Constraints0),
|
[ C || C <- dereference_deep(Constraints0),
|
||||||
unsolved == check_named_argument_constraint(Env, C) ].
|
unsolved == check_named_argument_constraint(Env, C) ].
|
||||||
|
|
||||||
%% If false, a type error have been emitted, so it's safe to drop the constraint.
|
%% If false, a type error has been emitted, so it's safe to drop the constraint.
|
||||||
-spec check_named_argument_constraint(env(), named_argument_constraint()) -> true | false | unsolved.
|
-spec check_named_argument_constraint(env(), named_argument_constraint()) -> true | false | unsolved.
|
||||||
check_named_argument_constraint(_Env, #named_argument_constraint{ args = {uvar, _, _} }) ->
|
check_named_argument_constraint(_Env, #named_argument_constraint{ args = {uvar, _, _} }) ->
|
||||||
unsolved;
|
unsolved;
|
||||||
@ -1978,12 +2122,20 @@ check_record_create_constraints(Env, [C | Cs]) ->
|
|||||||
end,
|
end,
|
||||||
check_record_create_constraints(Env, Cs).
|
check_record_create_constraints(Env, Cs).
|
||||||
|
|
||||||
|
is_contract_defined(C) ->
|
||||||
|
ets_lookup(defined_contracts, qname(C)) =/= [].
|
||||||
|
|
||||||
check_is_contract_constraints(_Env, []) -> ok;
|
check_is_contract_constraints(_Env, []) -> ok;
|
||||||
check_is_contract_constraints(Env, [C | Cs]) ->
|
check_is_contract_constraints(Env, [C | Cs]) ->
|
||||||
#is_contract_constraint{ contract_t = Type, context = Cxt } = C,
|
#is_contract_constraint{ contract_t = Type, context = Cxt, force_def = ForceDef } = C,
|
||||||
Type1 = unfold_types_in_type(Env, instantiate(Type)),
|
Type1 = unfold_types_in_type(Env, instantiate(Type)),
|
||||||
case lookup_type(Env, record_type_name(Type1)) of
|
TypeName = record_type_name(Type1),
|
||||||
{_, {_Ann, {[], {contract_t, _}}}} -> ok;
|
case lookup_type(Env, TypeName) of
|
||||||
|
{_, {_Ann, {[], {contract_t, _}}}} ->
|
||||||
|
case not ForceDef orelse is_contract_defined(TypeName) of
|
||||||
|
true -> ok;
|
||||||
|
false -> type_error({contract_lacks_definition, Type1, Cxt})
|
||||||
|
end;
|
||||||
_ -> type_error({not_a_contract_type, Type1, Cxt})
|
_ -> type_error({not_a_contract_type, Type1, Cxt})
|
||||||
end,
|
end,
|
||||||
check_is_contract_constraints(Env, Cs).
|
check_is_contract_constraints(Env, Cs).
|
||||||
@ -2309,8 +2461,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) ->
|
unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, Else2}, When) ->
|
||||||
unify(Env, Then1, Then2, When) andalso
|
unify(Env, Then1, Then2, When) andalso
|
||||||
unify(Env, Else1, Else2, When);
|
unify(Env, Else1, Else2, When);
|
||||||
|
|
||||||
|
unify1(_Env, {fun_t, _, _, _, _}, {fun_t, _, _, var_args, _}, When) ->
|
||||||
|
type_error({unify_varargs, When});
|
||||||
|
unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, When) ->
|
||||||
|
type_error({unify_varargs, When});
|
||||||
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When)
|
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When)
|
||||||
when length(Args1) == length(Args2) ->
|
when length(Args1) == length(Args2) ->
|
||||||
unify(Env, Named1, Named2, When) andalso
|
unify(Env, Named1, Named2, When) andalso
|
||||||
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
|
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
|
||||||
unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When)
|
unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When)
|
||||||
@ -2319,6 +2476,9 @@ unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When
|
|||||||
unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, When)
|
unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, When)
|
||||||
when length(As) == length(Bs) ->
|
when length(As) == length(Bs) ->
|
||||||
unify(Env, As, Bs, When);
|
unify(Env, As, Bs, When);
|
||||||
|
unify1(Env, {named_arg_t, _, Id1, Type1, _}, {named_arg_t, _, Id2, Type2, _}, When) ->
|
||||||
|
unify1(Env, Id1, Id2, {arg_name, Id1, Id2, When}),
|
||||||
|
unify1(Env, Type1, Type2, When);
|
||||||
%% The grammar is a bit inconsistent about whether types without
|
%% The grammar is a bit inconsistent about whether types without
|
||||||
%% arguments are represented as applications to an empty list of
|
%% arguments are represented as applications to an empty list of
|
||||||
%% parameters or not. We therefore allow them to unify.
|
%% parameters or not. We therefore allow them to unify.
|
||||||
@ -2376,8 +2536,6 @@ occurs_check1(R, [H | T]) ->
|
|||||||
occurs_check(R, H) orelse occurs_check(R, T);
|
occurs_check(R, H) orelse occurs_check(R, T);
|
||||||
occurs_check1(_, []) -> false.
|
occurs_check1(_, []) -> false.
|
||||||
|
|
||||||
fresh_uvar([{origin, system}]) ->
|
|
||||||
error(oh_no_you_dont);
|
|
||||||
fresh_uvar(Attrs) ->
|
fresh_uvar(Attrs) ->
|
||||||
{uvar, Attrs, make_ref()}.
|
{uvar, Attrs, make_ref()}.
|
||||||
|
|
||||||
@ -2426,7 +2584,11 @@ apply_typesig_constraint(Ann, address_to_contract, {fun_t, _, [], [_], Type}) ->
|
|||||||
apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) ->
|
apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) ->
|
||||||
add_bytes_constraint({add_bytes, Ann, concat, A, B, C});
|
add_bytes_constraint({add_bytes, Ann, concat, A, B, C});
|
||||||
apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) ->
|
apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) ->
|
||||||
add_bytes_constraint({add_bytes, Ann, split, A, B, C}).
|
add_bytes_constraint({add_bytes, Ann, split, A, B, C});
|
||||||
|
apply_typesig_constraint(Ann, bytecode_hash, {fun_t, _, _, [Con], _}) ->
|
||||||
|
constrain([#is_contract_constraint{ contract_t = Con,
|
||||||
|
context = {bytecode_hash, Ann} }]).
|
||||||
|
|
||||||
|
|
||||||
%% Dereferences all uvars and replaces the uninstantiated ones with a
|
%% Dereferences all uvars and replaces the uninstantiated ones with a
|
||||||
%% succession of tvars.
|
%% succession of tvars.
|
||||||
@ -2555,6 +2717,9 @@ mk_error({not_a_contract_type, Type, Cxt}) ->
|
|||||||
end,
|
end,
|
||||||
{Pos, Cxt1} =
|
{Pos, Cxt1} =
|
||||||
case Cxt of
|
case Cxt of
|
||||||
|
{var_args, Ann, Fun} ->
|
||||||
|
{pos(Ann),
|
||||||
|
io_lib:format("when calling variadic function\n~s\n", [pp_expr(" ", Fun)])};
|
||||||
{contract_literal, Lit} ->
|
{contract_literal, Lit} ->
|
||||||
{pos(Lit),
|
{pos(Lit),
|
||||||
io_lib:format("when checking that the contract literal\n~s\n"
|
io_lib:format("when checking that the contract literal\n~s\n"
|
||||||
@ -2653,7 +2818,7 @@ mk_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
|
|||||||
Msg = io_lib:format("Nested namespaces are not allowed\nNamespace '~s' at ~s not defined at top level.\n",
|
Msg = io_lib:format("Nested namespaces are not allowed\nNamespace '~s' at ~s not defined at top level.\n",
|
||||||
[Name, pp_loc(Pos)]),
|
[Name, pp_loc(Pos)]),
|
||||||
mk_t_err(pos(Pos), Msg);
|
mk_t_err(pos(Pos), Msg);
|
||||||
mk_error({contract, _Pos, {con, Pos, Name}, _Def}) ->
|
mk_error({Contract, _Pos, {con, Pos, Name}, _Def}) when ?IS_CONTRACT_HEAD(Contract) ->
|
||||||
Msg = io_lib:format("Nested contracts are not allowed\nContract '~s' at ~s not defined at top level.\n",
|
Msg = io_lib:format("Nested contracts are not allowed\nContract '~s' at ~s not defined at top level.\n",
|
||||||
[Name, pp_loc(Pos)]),
|
[Name, pp_loc(Pos)]),
|
||||||
mk_t_err(pos(Pos), Msg);
|
mk_t_err(pos(Pos), Msg);
|
||||||
@ -2728,8 +2893,8 @@ mk_error({contract_has_no_entrypoints, Con}) ->
|
|||||||
"contract functions must be declared with the 'entrypoint' keyword instead of\n"
|
"contract functions must be declared with the 'entrypoint' keyword instead of\n"
|
||||||
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]),
|
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]),
|
||||||
mk_t_err(pos(Con), Msg);
|
mk_t_err(pos(Con), Msg);
|
||||||
mk_error({definition_in_non_main_contract, Ann, {id, _, Id}}) ->
|
mk_error({definition_in_contract_interface, Ann, {id, _, Id}}) ->
|
||||||
Msg = "Only the main contract can contain defined functions or entrypoints.\n",
|
Msg = "Contract interfaces cannot contain defined functions or entrypoints.\n",
|
||||||
Cxt = io_lib:format("Fix: replace the definition of '~s' by a type signature.\n", [Id]),
|
Cxt = io_lib:format("Fix: replace the definition of '~s' by a type signature.\n", [Id]),
|
||||||
mk_t_err(pos(Ann), Msg, Cxt);
|
mk_t_err(pos(Ann), Msg, Cxt);
|
||||||
mk_error({unbound_type, Type}) ->
|
mk_error({unbound_type, Type}) ->
|
||||||
@ -2798,6 +2963,29 @@ mk_error({named_argument_must_be_literal_bool, Name, Arg}) ->
|
|||||||
mk_error({conflicting_updates_for_field, Upd, Key}) ->
|
mk_error({conflicting_updates_for_field, Upd, Key}) ->
|
||||||
Msg = io_lib:format("Conflicting updates for field '~s'\n", [Key]),
|
Msg = io_lib:format("Conflicting updates for field '~s'\n", [Key]),
|
||||||
mk_t_err(pos(Upd), Msg);
|
mk_t_err(pos(Upd), Msg);
|
||||||
|
mk_error({ambiguous_main_contract, Ann}) ->
|
||||||
|
Msg = "Could not deduce the main contract. You can point it out manually with the `main` keyword.",
|
||||||
|
mk_t_err(pos(Ann), Msg);
|
||||||
|
mk_error({main_contract_undefined, Ann}) ->
|
||||||
|
Msg = "No contract defined.\n",
|
||||||
|
mk_t_err(pos(Ann), Msg);
|
||||||
|
mk_error({multiple_main_contracts, Ann}) ->
|
||||||
|
Msg = "Only one main contract can be defined.\n",
|
||||||
|
mk_t_err(pos(Ann), Msg);
|
||||||
|
mk_error({unify_varargs, When}) ->
|
||||||
|
Msg = "Cannot unify variable argument list.\n",
|
||||||
|
{Pos, Ctxt} = pp_when(When),
|
||||||
|
mk_t_err(Pos, Msg, Ctxt);
|
||||||
|
mk_error({clone_no_contract, Ann}) ->
|
||||||
|
Msg = "Chain.clone requires `ref` named argument of contract type.\n",
|
||||||
|
mk_t_err(pos(Ann), Msg);
|
||||||
|
mk_error({contract_lacks_definition, Type, When}) ->
|
||||||
|
Msg = io_lib:format(
|
||||||
|
"~s is not implemented.\n",
|
||||||
|
[pp_type(Type)]
|
||||||
|
),
|
||||||
|
{Pos, Ctxt} = pp_when(When),
|
||||||
|
mk_t_err(Pos, Msg, Ctxt);
|
||||||
mk_error(Err) ->
|
mk_error(Err) ->
|
||||||
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
|
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
|
||||||
mk_t_err(pos(0, 0), Msg).
|
mk_t_err(pos(0, 0), Msg).
|
||||||
@ -2833,6 +3021,12 @@ pp_when({field_constraint, FieldType0, InferredType0, Fld}) ->
|
|||||||
InferredType = instantiate(InferredType0),
|
InferredType = instantiate(InferredType0),
|
||||||
{pos(Fld),
|
{pos(Fld),
|
||||||
case Fld of
|
case Fld of
|
||||||
|
{var_args, _Ann, _Fun} ->
|
||||||
|
io_lib:format("when checking contract construction of type\n~s (at ~s)\nagainst the expected type\n~s\n",
|
||||||
|
[pp_type(" ", FieldType),
|
||||||
|
pp_loc(Fld),
|
||||||
|
pp_type(" ", InferredType)
|
||||||
|
]);
|
||||||
{field, _Ann, LV, Id, E} ->
|
{field, _Ann, LV, Id, E} ->
|
||||||
io_lib:format("when checking the assignment of the field\n~s (at ~s)\nto the old value ~s and the new value\n~s\n",
|
io_lib:format("when checking the assignment of the field\n~s (at ~s)\nto the old value ~s and the new value\n~s\n",
|
||||||
[pp_typed(" ", {lvalue, [], LV}, FieldType),
|
[pp_typed(" ", {lvalue, [], LV}, FieldType),
|
||||||
@ -2855,6 +3049,13 @@ pp_when({record_constraint, RecType0, InferredType0, Fld}) ->
|
|||||||
InferredType = instantiate(InferredType0),
|
InferredType = instantiate(InferredType0),
|
||||||
{Pos, WhyRec} = pp_why_record(Fld),
|
{Pos, WhyRec} = pp_why_record(Fld),
|
||||||
case Fld of
|
case Fld of
|
||||||
|
{var_args, _Ann, _Fun} ->
|
||||||
|
{Pos,
|
||||||
|
io_lib:format("when checking that contract construction of type\n~s\n~s\n"
|
||||||
|
"matches the expected type\n~s\n",
|
||||||
|
[pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)]
|
||||||
|
)
|
||||||
|
};
|
||||||
{field, _Ann, _LV, _Id, _E} ->
|
{field, _Ann, _LV, _Id, _E} ->
|
||||||
{Pos,
|
{Pos,
|
||||||
io_lib:format("when checking that the record type\n~s\n~s\n"
|
io_lib:format("when checking that the record type\n~s\n~s\n"
|
||||||
@ -2908,17 +3109,42 @@ pp_when({check_named_arg_constraint, C}) ->
|
|||||||
Err = io_lib:format("when checking named argument\n~s\nagainst inferred type\n~s",
|
Err = io_lib:format("when checking named argument\n~s\nagainst inferred type\n~s",
|
||||||
[pp_typed(" ", Arg, Type), pp_type(" ", C#named_argument_constraint.type)]),
|
[pp_typed(" ", Arg, Type), pp_type(" ", C#named_argument_constraint.type)]),
|
||||||
{pos(Arg), Err};
|
{pos(Arg), Err};
|
||||||
|
pp_when({checking_init_args, Ann, Con0, ArgTypes0}) ->
|
||||||
|
Con = instantiate(Con0),
|
||||||
|
ArgTypes = instantiate(ArgTypes0),
|
||||||
|
{pos(Ann),
|
||||||
|
io_lib:format("when checking arguments of ~s's init entrypoint to match\n(~s)",
|
||||||
|
[pp_type(Con), string:join([pp_type(A) || A <- ArgTypes], ", ")])
|
||||||
|
};
|
||||||
|
pp_when({return_contract, App, Con0}) ->
|
||||||
|
Con = instantiate(Con0),
|
||||||
|
{pos(App)
|
||||||
|
, io_lib:format("when checking that expression returns contract of type\n~s", [pp_type(" ", Con)])
|
||||||
|
};
|
||||||
|
pp_when({arg_name, Id1, Id2, When}) ->
|
||||||
|
{Pos, Ctx} = pp_when(When),
|
||||||
|
{Pos
|
||||||
|
, io_lib:format("when unifying names of named arguments: ~s and ~s\n~s", [pp_expr(Id1), pp_expr(Id2), Ctx])
|
||||||
|
};
|
||||||
|
pp_when({var_args, Ann, Fun}) ->
|
||||||
|
{pos(Ann)
|
||||||
|
, io_lib:format("when resolving arguments of variadic function\n~s\n", [pp_expr(" ", Fun)])
|
||||||
|
};
|
||||||
pp_when(unknown) -> {pos(0,0), ""}.
|
pp_when(unknown) -> {pos(0,0), ""}.
|
||||||
|
|
||||||
-spec pp_why_record(why_record()) -> {pos(), iolist()}.
|
-spec pp_why_record(why_record()) -> {pos(), iolist()}.
|
||||||
pp_why_record(Fld = {field, _Ann, LV, _Id, _E}) ->
|
pp_why_record({var_args, Ann, Fun}) ->
|
||||||
{pos(Fld),
|
{pos(Ann),
|
||||||
io_lib:format("arising from an assignment of the field ~s (at ~s)",
|
io_lib:format("arising from resolution of variadic function ~s (at ~s)",
|
||||||
[pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])};
|
[pp_expr(Fun), pp_loc(Fun)])};
|
||||||
pp_why_record(Fld = {field, _Ann, LV, _E}) ->
|
pp_why_record(Fld = {field, _Ann, LV, _E}) ->
|
||||||
{pos(Fld),
|
{pos(Fld),
|
||||||
io_lib:format("arising from an assignment of the field ~s (at ~s)",
|
io_lib:format("arising from an assignment of the field ~s (at ~s)",
|
||||||
[pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])};
|
[pp_expr({lvalue, [], LV}), pp_loc(Fld)])};
|
||||||
|
pp_why_record(Fld = {field, _Ann, LV, _Alias, _E}) ->
|
||||||
|
{pos(Fld),
|
||||||
|
io_lib:format("arising from an assignment of the field ~s (at ~s)",
|
||||||
|
[pp_expr({lvalue, [], LV}), pp_loc(Fld)])};
|
||||||
pp_why_record({proj, _Ann, Rec, FldName}) ->
|
pp_why_record({proj, _Ann, Rec, FldName}) ->
|
||||||
{pos(Rec),
|
{pos(Rec),
|
||||||
io_lib:format("arising from the projection of the field ~s (at ~s)",
|
io_lib:format("arising from the projection of the field ~s (at ~s)",
|
||||||
@ -2938,9 +3164,13 @@ pp_typed(Label, {typed, _, Expr, _}, Type) ->
|
|||||||
pp_typed(Label, Expr, Type) ->
|
pp_typed(Label, Expr, Type) ->
|
||||||
pp_expr(Label, {typed, [], Expr, Type}).
|
pp_expr(Label, {typed, [], Expr, Type}).
|
||||||
|
|
||||||
|
pp_expr(Expr) ->
|
||||||
|
pp_expr("", Expr).
|
||||||
pp_expr(Label, Expr) ->
|
pp_expr(Label, Expr) ->
|
||||||
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:expr(Expr, [show_generated]))).
|
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:expr(Expr, [show_generated]))).
|
||||||
|
|
||||||
|
pp_type(Type) ->
|
||||||
|
pp_type("", Type).
|
||||||
pp_type(Label, Type) ->
|
pp_type(Label, Type) ->
|
||||||
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated]))).
|
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated]))).
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
-export([ast_to_fcode/2, format_fexpr/1]).
|
-export([ast_to_fcode/2, format_fexpr/1]).
|
||||||
-export_type([fcode/0, fexpr/0, fun_def/0]).
|
-export_type([fcode/0, fexpr/0, fun_def/0]).
|
||||||
|
|
||||||
|
-include("aeso_utils.hrl").
|
||||||
|
|
||||||
%% -- Type definitions -------------------------------------------------------
|
%% -- Type definitions -------------------------------------------------------
|
||||||
|
|
||||||
-type option() :: term().
|
-type option() :: term().
|
||||||
@ -53,6 +55,7 @@
|
|||||||
| {oracle_pubkey, binary()}
|
| {oracle_pubkey, binary()}
|
||||||
| {oracle_query_id, binary()}
|
| {oracle_query_id, binary()}
|
||||||
| {bool, false | true}
|
| {bool, false | true}
|
||||||
|
| {contract_code, string()} %% for CREATE, by name
|
||||||
| {typerep, ftype()}.
|
| {typerep, ftype()}.
|
||||||
|
|
||||||
-type fexpr() :: {lit, flit()}
|
-type fexpr() :: {lit, flit()}
|
||||||
@ -136,24 +139,27 @@
|
|||||||
-type type_env() :: #{ sophia_name() => type_def() }.
|
-type type_env() :: #{ sophia_name() => type_def() }.
|
||||||
-type fun_env() :: #{ sophia_name() => {fun_name(), non_neg_integer()} }.
|
-type fun_env() :: #{ sophia_name() => {fun_name(), non_neg_integer()} }.
|
||||||
-type con_env() :: #{ sophia_name() => con_tag() }.
|
-type con_env() :: #{ sophia_name() => con_tag() }.
|
||||||
-type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none} }.
|
-type child_con_env() :: #{sophia_name() => fcode()}.
|
||||||
|
-type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none | variable} }.
|
||||||
|
|
||||||
-type context() :: {main_contract, string()}
|
-type context() :: {contract_def, string()}
|
||||||
| {namespace, string()}
|
| {namespace, string()}
|
||||||
| {abstract_contract, string()}.
|
| {abstract_contract, string()}.
|
||||||
|
|
||||||
-type state_layout() :: {tuple, [state_layout()]} | {reg, state_reg()}.
|
-type state_layout() :: {tuple, [state_layout()]} | {reg, state_reg()}.
|
||||||
|
|
||||||
-type env() :: #{ type_env := type_env(),
|
-type env() :: #{ type_env := type_env(),
|
||||||
fun_env := fun_env(),
|
fun_env := fun_env(),
|
||||||
con_env := con_env(),
|
con_env := con_env(),
|
||||||
event_type => aeso_syntax:typedef(),
|
child_con_env := child_con_env(),
|
||||||
builtins := builtins(),
|
event_type => aeso_syntax:typedef(),
|
||||||
options := [option()],
|
builtins := builtins(),
|
||||||
state_layout => state_layout(),
|
options := [option()],
|
||||||
context => context(),
|
state_layout => state_layout(),
|
||||||
vars => [var_name()],
|
context => context(),
|
||||||
functions := #{ fun_name() => fun_def() } }.
|
vars => [var_name()],
|
||||||
|
functions := #{ fun_name() => fun_def() }
|
||||||
|
}.
|
||||||
|
|
||||||
-define(HASH_BYTES, 32).
|
-define(HASH_BYTES, 32).
|
||||||
|
|
||||||
@ -161,17 +167,26 @@
|
|||||||
|
|
||||||
%% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2
|
%% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2
|
||||||
%% and produces Fate intermediate code.
|
%% and produces Fate intermediate code.
|
||||||
-spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> fcode().
|
-spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> {env(), fcode()}.
|
||||||
ast_to_fcode(Code, Options) ->
|
ast_to_fcode(Code, Options) ->
|
||||||
Verbose = lists:member(pp_fcode, Options),
|
|
||||||
init_fresh_names(),
|
init_fresh_names(),
|
||||||
FCode1 = to_fcode(init_env(Options), Code),
|
{Env1, FCode1} = to_fcode(init_env(Options), Code),
|
||||||
|
FCode2 = optimize(FCode1, Options),
|
||||||
|
Env2 = Env1#{ child_con_env :=
|
||||||
|
maps:map(
|
||||||
|
fun (_, FC) -> optimize(FC, Options) end,
|
||||||
|
maps:get(child_con_env, Env1)
|
||||||
|
)},
|
||||||
|
clear_fresh_names(),
|
||||||
|
{Env2, FCode2}.
|
||||||
|
|
||||||
|
optimize(FCode1, Options) ->
|
||||||
|
Verbose = lists:member(pp_fcode, Options),
|
||||||
[io:format("-- Before lambda lifting --\n~s\n\n", [format_fcode(FCode1)]) || Verbose],
|
[io:format("-- Before lambda lifting --\n~s\n\n", [format_fcode(FCode1)]) || Verbose],
|
||||||
FCode2 = optimize_fcode(FCode1),
|
FCode2 = optimize_fcode(FCode1),
|
||||||
[ io:format("-- After optimization --\n~s\n\n", [format_fcode(FCode2)]) || Verbose, FCode2 /= FCode1 ],
|
[ io:format("-- After optimization --\n~s\n\n", [format_fcode(FCode2)]) || Verbose, FCode2 /= FCode1 ],
|
||||||
FCode3 = lambda_lift(FCode2),
|
FCode3 = lambda_lift(FCode2),
|
||||||
[ io:format("-- After lambda lifting --\n~s\n\n", [format_fcode(FCode3)]) || Verbose, FCode3 /= FCode2 ],
|
[ io:format("-- After lambda lifting --\n~s\n\n", [format_fcode(FCode3)]) || Verbose, FCode3 /= FCode2 ],
|
||||||
clear_fresh_names(),
|
|
||||||
FCode3.
|
FCode3.
|
||||||
|
|
||||||
%% -- Environment ------------------------------------------------------------
|
%% -- Environment ------------------------------------------------------------
|
||||||
@ -182,6 +197,7 @@ init_env(Options) ->
|
|||||||
#{ type_env => init_type_env(),
|
#{ type_env => init_type_env(),
|
||||||
fun_env => #{},
|
fun_env => #{},
|
||||||
builtins => builtins(),
|
builtins => builtins(),
|
||||||
|
child_con_env => #{},
|
||||||
con_env => #{["None"] => #con_tag{ tag = 0, arities = [0, 1] },
|
con_env => #{["None"] => #con_tag{ tag = 0, arities = [0, 1] },
|
||||||
["Some"] => #con_tag{ tag = 1, arities = [0, 1] },
|
["Some"] => #con_tag{ tag = 1, arities = [0, 1] },
|
||||||
["RelativeTTL"] => #con_tag{ tag = 0, arities = [1, 1] },
|
["RelativeTTL"] => #con_tag{ tag = 0, arities = [1, 1] },
|
||||||
@ -217,7 +233,8 @@ init_env(Options) ->
|
|||||||
["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities }
|
["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities }
|
||||||
},
|
},
|
||||||
options => Options,
|
options => Options,
|
||||||
functions => #{} }.
|
functions => #{}
|
||||||
|
}.
|
||||||
|
|
||||||
-spec builtins() -> builtins().
|
-spec builtins() -> builtins().
|
||||||
builtins() ->
|
builtins() ->
|
||||||
@ -227,7 +244,7 @@ builtins() ->
|
|||||||
Scopes = [{[], [{"abort", 1}, {"require", 2}]},
|
Scopes = [{[], [{"abort", 1}, {"require", 2}]},
|
||||||
{["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none},
|
{["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none},
|
||||||
{"timestamp", none}, {"block_height", none}, {"difficulty", none},
|
{"timestamp", none}, {"block_height", none}, {"difficulty", none},
|
||||||
{"gas_limit", none}]},
|
{"gas_limit", none}, {"bytecode_hash", 1}, {"create", variable}, {"clone", variable}]},
|
||||||
{["Contract"], [{"address", none}, {"balance", none}, {"creator", none}]},
|
{["Contract"], [{"address", none}, {"balance", none}, {"creator", none}]},
|
||||||
{["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none},
|
{["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none},
|
||||||
{"gas_left", 0}]},
|
{"gas_left", 0}]},
|
||||||
@ -307,31 +324,44 @@ get_option(Opt, Env, Default) ->
|
|||||||
|
|
||||||
%% -- Compilation ------------------------------------------------------------
|
%% -- Compilation ------------------------------------------------------------
|
||||||
|
|
||||||
-spec to_fcode(env(), aeso_syntax:ast()) -> fcode().
|
-spec to_fcode(env(), aeso_syntax:ast()) -> {env(), fcode()}.
|
||||||
to_fcode(Env, [{contract, Attrs, MainCon = {con, _, Main}, Decls}]) ->
|
to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest])
|
||||||
#{ builtins := Builtins } = Env,
|
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||||
MainEnv = Env#{ context => {main_contract, Main},
|
case Contract =:= contract_interface of
|
||||||
builtins => Builtins#{[Main, "state"] => {get_state, none},
|
false ->
|
||||||
[Main, "put"] => {set_state, 1},
|
#{ builtins := Builtins } = Env,
|
||||||
[Main, "Chain", "event"] => {chain_event, 1}} },
|
ConEnv = Env#{ context => {contract_def, Name},
|
||||||
#{ functions := Funs } = Env1 =
|
builtins => Builtins#{[Name, "state"] => {get_state, none},
|
||||||
decls_to_fcode(MainEnv, Decls),
|
[Name, "put"] => {set_state, 1},
|
||||||
StateType = lookup_type(Env1, [Main, "state"], [], {tuple, []}),
|
[Name, "Chain", "event"] => {chain_event, 1}} },
|
||||||
EventType = lookup_type(Env1, [Main, "event"], [], none),
|
#{ functions := PrevFuns } = ConEnv,
|
||||||
StateLayout = state_layout(Env1),
|
#{ functions := Funs } = Env1 =
|
||||||
Payable = proplists:get_value(payable, Attrs, false),
|
decls_to_fcode(ConEnv, Decls),
|
||||||
#{ contract_name => Main,
|
StateType = lookup_type(Env1, [Name, "state"], [], {tuple, []}),
|
||||||
state_type => StateType,
|
EventType = lookup_type(Env1, [Name, "event"], [], none),
|
||||||
state_layout => StateLayout,
|
StateLayout = state_layout(Env1),
|
||||||
event_type => EventType,
|
Payable = proplists:get_value(payable, Attrs, false),
|
||||||
payable => Payable,
|
ConFcode = #{ contract_name => Name,
|
||||||
functions => add_init_function(Env1, MainCon, StateType,
|
state_type => StateType,
|
||||||
add_event_function(Env1, EventType, Funs)) };
|
state_layout => StateLayout,
|
||||||
to_fcode(_Env, [NotContract]) ->
|
event_type => EventType,
|
||||||
fcode_error({last_declaration_must_be_contract, NotContract});
|
payable => Payable,
|
||||||
to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) ->
|
functions => add_init_function(
|
||||||
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls),
|
Env1, Con, StateType,
|
||||||
to_fcode(Env1, Code);
|
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]) ->
|
to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) ->
|
||||||
Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls),
|
Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls),
|
||||||
to_fcode(Env1, Code).
|
to_fcode(Env1, Code).
|
||||||
@ -341,13 +371,11 @@ decls_to_fcode(Env, Decls) ->
|
|||||||
%% First compute mapping from Sophia names to fun_names and add it to the
|
%% First compute mapping from Sophia names to fun_names and add it to the
|
||||||
%% environment.
|
%% environment.
|
||||||
Env1 = add_fun_env(Env, Decls),
|
Env1 = add_fun_env(Env, Decls),
|
||||||
lists:foldl(fun(D, E) ->
|
lists:foldl(fun(D, E) -> decl_to_fcode(E, D)
|
||||||
R = decl_to_fcode(E, D),
|
|
||||||
R
|
|
||||||
end, Env1, Decls).
|
end, Env1, Decls).
|
||||||
|
|
||||||
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
|
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
|
||||||
decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, _, Id, _}) ->
|
decl_to_fcode(Env = #{context := {contract_def, _}}, {fun_decl, _, Id, _}) ->
|
||||||
case is_no_code(Env) of
|
case is_no_code(Env) of
|
||||||
false -> fcode_error({missing_definition, Id});
|
false -> fcode_error({missing_definition, Id});
|
||||||
true -> Env
|
true -> Env
|
||||||
@ -410,7 +438,7 @@ typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) ->
|
|||||||
Env3 = compute_state_layout(Env2, Name, FDef),
|
Env3 = compute_state_layout(Env2, Name, FDef),
|
||||||
bind_type(Env3, Q, FDef).
|
bind_type(Env3, Q, FDef).
|
||||||
|
|
||||||
compute_state_layout(Env = #{ context := {main_contract, _} }, "state", Type) ->
|
compute_state_layout(Env = #{ context := {contract_def, _} }, "state", Type) ->
|
||||||
NoLayout = get_option(no_flatten_state, Env),
|
NoLayout = get_option(no_flatten_state, Env),
|
||||||
Layout =
|
Layout =
|
||||||
case Type([]) of
|
case Type([]) of
|
||||||
@ -436,7 +464,7 @@ compute_state_layout(R, [H | T]) ->
|
|||||||
compute_state_layout(R, _) ->
|
compute_state_layout(R, _) ->
|
||||||
{R + 1, {reg, R}}.
|
{R + 1, {reg, R}}.
|
||||||
|
|
||||||
check_state_and_event_types(#{ context := {main_contract, _} }, Id, [_ | _]) ->
|
check_state_and_event_types(#{ context := {contract_def, _} }, Id, [_ | _]) ->
|
||||||
case Id of
|
case Id of
|
||||||
{id, _, "state"} -> fcode_error({parameterized_state, Id});
|
{id, _, "state"} -> fcode_error({parameterized_state, Id});
|
||||||
{id, _, "event"} -> fcode_error({parameterized_event, Id});
|
{id, _, "event"} -> fcode_error({parameterized_event, Id});
|
||||||
@ -461,8 +489,12 @@ type_to_fcode(Env, Sub, {record_t, Fields}) ->
|
|||||||
type_to_fcode(Env, Sub, {tuple_t, [], lists:map(FieldType, Fields)});
|
type_to_fcode(Env, Sub, {tuple_t, [], lists:map(FieldType, Fields)});
|
||||||
type_to_fcode(_Env, _Sub, {bytes_t, _, N}) ->
|
type_to_fcode(_Env, _Sub, {bytes_t, _, N}) ->
|
||||||
{bytes, N};
|
{bytes, N};
|
||||||
|
type_to_fcode(_Env, _Sub, {tvar, Ann, "void"}) ->
|
||||||
|
fcode_error({found_void, Ann});
|
||||||
type_to_fcode(_Env, Sub, {tvar, _, X}) ->
|
type_to_fcode(_Env, Sub, {tvar, _, X}) ->
|
||||||
maps:get(X, Sub, {tvar, X});
|
maps:get(X, Sub, {tvar, X});
|
||||||
|
type_to_fcode(_Env, _Sub, {fun_t, Ann, _, var_args, _}) ->
|
||||||
|
fcode_error({var_args_not_set, {id, Ann, "a very suspicious function"}});
|
||||||
type_to_fcode(Env, Sub, {fun_t, _, Named, Args, Res}) ->
|
type_to_fcode(Env, Sub, {fun_t, _, Named, Args, Res}) ->
|
||||||
FNamed = [type_to_fcode(Env, Sub, Arg) || {named_arg_t, _, _, Arg, _} <- Named],
|
FNamed = [type_to_fcode(Env, Sub, Arg) || {named_arg_t, _, _, Arg, _} <- Named],
|
||||||
FArgs = [type_to_fcode(Env, Sub, Arg) || Arg <- Args],
|
FArgs = [type_to_fcode(Env, Sub, Arg) || Arg <- Args],
|
||||||
@ -678,14 +710,31 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
%% Function calls
|
%% Function calls
|
||||||
expr_to_fcode(Env, _Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) ->
|
expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, _}}, Args}) ->
|
||||||
Args1 = get_named_args(NamedArgsT, Args),
|
Args1 = get_named_args(NamedArgsT, Args),
|
||||||
FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1],
|
FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1],
|
||||||
case expr_to_fcode(Env, Fun) of
|
case expr_to_fcode(Env, Fun) of
|
||||||
{builtin_u, B, _Ar, TypeArgs} -> builtin_to_fcode(state_layout(Env), B, FArgs ++ TypeArgs);
|
{builtin_u, B, _Ar, TypeArgs} -> builtin_to_fcode(state_layout(Env), B, FArgs ++ TypeArgs);
|
||||||
|
{builtin_u, chain_clone, _Ar} ->
|
||||||
|
case ArgsT of
|
||||||
|
var_args -> fcode_error({var_args_not_set, FunE});
|
||||||
|
_ ->
|
||||||
|
%% Here we little cheat on the typechecker, but this inconsistency
|
||||||
|
%% is to be solved in `aeso_fcode_to_fate:type_to_scode/1`
|
||||||
|
FInitArgsT = aeb_fate_data:make_typerep([type_to_fcode(Env, T) || T <- ArgsT]),
|
||||||
|
builtin_to_fcode(state_layout(Env), chain_clone, [{lit, FInitArgsT}|FArgs])
|
||||||
|
end;
|
||||||
|
{builtin_u, chain_create, _Ar} ->
|
||||||
|
case {ArgsT, Type} of
|
||||||
|
{var_args, _} -> fcode_error({var_args_not_set, FunE});
|
||||||
|
{_, {con, _, Contract}} ->
|
||||||
|
FInitArgsT = aeb_fate_data:make_typerep([type_to_fcode(Env, T) || T <- ArgsT]),
|
||||||
|
builtin_to_fcode(state_layout(Env), chain_create, [{lit, {contract_code, Contract}}, {lit, FInitArgsT}|FArgs]);
|
||||||
|
{_, _} -> fcode_error({not_a_contract_type, Type})
|
||||||
|
end;
|
||||||
{builtin_u, B, _Ar} -> builtin_to_fcode(state_layout(Env), B, FArgs);
|
{builtin_u, B, _Ar} -> builtin_to_fcode(state_layout(Env), B, FArgs);
|
||||||
{def_u, F, _Ar} -> {def, F, FArgs};
|
{def_u, F, _Ar} -> {def, F, FArgs};
|
||||||
{remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs};
|
{remote_u, RArgsT, RRetT, Ct, RFun} -> {remote, RArgsT, RRetT, Ct, RFun, FArgs};
|
||||||
FFun ->
|
FFun ->
|
||||||
%% FFun is a closure, with first component the function name and
|
%% FFun is a closure, with first component the function name and
|
||||||
%% second component the environment
|
%% second component the environment
|
||||||
@ -1614,6 +1663,10 @@ bind_constructors(Env = #{ con_env := ConEnv }, NewCons) ->
|
|||||||
|
|
||||||
%% -- Names --
|
%% -- Names --
|
||||||
|
|
||||||
|
-spec add_child_con(env(), sophia_name(), fcode()) -> env().
|
||||||
|
add_child_con(Env = #{child_con_env := CEnv}, Name, Fcode) ->
|
||||||
|
Env#{ child_con_env := CEnv#{Name => Fcode} }.
|
||||||
|
|
||||||
-spec add_fun_env(env(), [aeso_syntax:decl()]) -> env().
|
-spec add_fun_env(env(), [aeso_syntax:decl()]) -> env().
|
||||||
add_fun_env(Env = #{ context := {abstract_contract, _} }, _) -> Env; %% no functions from abstract contracts
|
add_fun_env(Env = #{ context := {abstract_contract, _} }, _) -> Env; %% no functions from abstract contracts
|
||||||
add_fun_env(Env = #{ fun_env := FunEnv }, Decls) ->
|
add_fun_env(Env = #{ fun_env := FunEnv }, Decls) ->
|
||||||
@ -1628,7 +1681,7 @@ add_fun_env(Env = #{ fun_env := FunEnv }, Decls) ->
|
|||||||
make_fun_name(#{ context := Context }, Ann, Name) ->
|
make_fun_name(#{ context := Context }, Ann, Name) ->
|
||||||
Entrypoint = proplists:get_value(entrypoint, Ann, false),
|
Entrypoint = proplists:get_value(entrypoint, Ann, false),
|
||||||
case Context of
|
case Context of
|
||||||
{main_contract, Main} ->
|
{contract_def, Main} ->
|
||||||
if Entrypoint -> {entrypoint, list_to_binary(Name)};
|
if Entrypoint -> {entrypoint, list_to_binary(Name)};
|
||||||
true -> {local_fun, [Main, Name]}
|
true -> {local_fun, [Main, Name]}
|
||||||
end;
|
end;
|
||||||
@ -1640,7 +1693,7 @@ make_fun_name(#{ context := Context }, Ann, Name) ->
|
|||||||
current_namespace(#{ context := Cxt }) ->
|
current_namespace(#{ context := Cxt }) ->
|
||||||
case Cxt of
|
case Cxt of
|
||||||
{abstract_contract, Con} -> Con;
|
{abstract_contract, Con} -> Con;
|
||||||
{main_contract, Con} -> Con;
|
{contract_def, Con} -> Con;
|
||||||
{namespace, NS} -> NS
|
{namespace, NS} -> NS
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -1987,8 +2040,11 @@ internal_error(Error) ->
|
|||||||
%% -- Pretty printing --------------------------------------------------------
|
%% -- Pretty printing --------------------------------------------------------
|
||||||
|
|
||||||
format_fcode(#{ functions := Funs }) ->
|
format_fcode(#{ functions := Funs }) ->
|
||||||
prettypr:format(pp_above(
|
prettypr:format(format_funs(Funs)).
|
||||||
[ pp_fun(Name, Def) || {Name, Def} <- maps:to_list(Funs) ])).
|
|
||||||
|
format_funs(Funs) ->
|
||||||
|
pp_above(
|
||||||
|
[ pp_fun(Name, Def) || {Name, Def} <- maps:to_list(Funs) ]).
|
||||||
|
|
||||||
format_fexpr(E) ->
|
format_fexpr(E) ->
|
||||||
prettypr:format(pp_fexpr(E)).
|
prettypr:format(pp_fexpr(E)).
|
||||||
@ -2105,7 +2161,9 @@ pp_fexpr({set_state, R, A}) ->
|
|||||||
pp_call(pp_text("set_state"), [{lit, {int, R}}, A]);
|
pp_call(pp_text("set_state"), [{lit, {int, R}}, A]);
|
||||||
pp_fexpr({get_state, R}) ->
|
pp_fexpr({get_state, R}) ->
|
||||||
pp_call(pp_text("get_state"), [{lit, {int, R}}]);
|
pp_call(pp_text("get_state"), [{lit, {int, R}}]);
|
||||||
pp_fexpr({switch, Split}) -> pp_split(Split).
|
pp_fexpr({switch, Split}) -> pp_split(Split);
|
||||||
|
pp_fexpr({contract_code, Contract}) ->
|
||||||
|
pp_beside(pp_text("contract "), pp_text(Contract)).
|
||||||
|
|
||||||
pp_call(Fun, Args) ->
|
pp_call(Fun, Args) ->
|
||||||
pp_beside(Fun, pp_fexpr({tuple, Args})).
|
pp_beside(Fun, pp_fexpr({tuple, Args})).
|
||||||
|
@ -14,12 +14,13 @@
|
|||||||
|
|
||||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||||
-include("aeso_icode.hrl").
|
-include("aeso_icode.hrl").
|
||||||
|
-include("aeso_utils.hrl").
|
||||||
|
|
||||||
-spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode().
|
-spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode().
|
||||||
convert_typed(TypedTree, Options) ->
|
convert_typed(TypedTree, Options) ->
|
||||||
{Payable, Name} =
|
{Payable, Name} =
|
||||||
case lists:last(TypedTree) of
|
case lists:last(TypedTree) of
|
||||||
{contract, Attrs, {con, _, Con}, _} ->
|
{Contr, Attrs, {con, _, Con}, _} when ?IS_CONTRACT_HEAD(Contr) ->
|
||||||
{proplists:get_value(payable, Attrs, false), Con};
|
{proplists:get_value(payable, Attrs, false), Con};
|
||||||
Decl ->
|
Decl ->
|
||||||
gen_error({last_declaration_must_be_contract, Decl})
|
gen_error({last_declaration_must_be_contract, Decl})
|
||||||
@ -29,7 +30,8 @@ convert_typed(TypedTree, Options) ->
|
|||||||
Icode = code(TypedTree, NewIcode, Options),
|
Icode = code(TypedTree, NewIcode, Options),
|
||||||
deadcode_elimination(Icode).
|
deadcode_elimination(Icode).
|
||||||
|
|
||||||
code([{contract, _Attribs, Con, Code}|Rest], Icode, Options) ->
|
code([{Contract, _Attribs, Con, Code}|Rest], Icode, Options)
|
||||||
|
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||||
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)),
|
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)),
|
||||||
code(Rest, NewIcode, Options);
|
code(Rest, NewIcode, Options);
|
||||||
code([{namespace, _Ann, Name, Code}|Rest], Icode, Options) ->
|
code([{namespace, _Ann, Name, Code}|Rest], Icode, Options) ->
|
||||||
|
@ -10,9 +10,9 @@
|
|||||||
|
|
||||||
-export([format/1, pos/1]).
|
-export([format/1, pos/1]).
|
||||||
|
|
||||||
format({last_declaration_must_be_contract, Decl = {namespace, _, {con, _, C}, _}}) ->
|
format({last_declaration_must_be_main_contract, Decl = {Kind, _, {con, _, C}, _}}) ->
|
||||||
Msg = io_lib:format("Expected a contract as the last declaration instead of the namespace '~s'\n",
|
Msg = io_lib:format("Expected a main contract as the last declaration instead of the ~p '~s'\n",
|
||||||
[C]),
|
[Kind, C]),
|
||||||
mk_err(pos(Decl), Msg);
|
mk_err(pos(Decl), Msg);
|
||||||
format({missing_init_function, Con}) ->
|
format({missing_init_function, Con}) ->
|
||||||
Msg = io_lib:format("Missing init function for the contract '~s'.\n", [pp_expr(Con)]),
|
Msg = io_lib:format("Missing init function for the contract '~s'.\n", [pp_expr(Con)]),
|
||||||
@ -87,6 +87,12 @@ format({higher_order_state, {type_def, Ann, _, _, State}}) ->
|
|||||||
Msg = io_lib:format("Invalid state type\n~s\n", [pp_type(2, State)]),
|
Msg = io_lib:format("Invalid state type\n~s\n", [pp_type(2, State)]),
|
||||||
Cxt = "The state cannot contain functions in the AEVM. Use FATE if you need this.\n",
|
Cxt = "The state cannot contain functions in the AEVM. Use FATE if you need this.\n",
|
||||||
mk_err(pos(Ann), Msg, Cxt);
|
mk_err(pos(Ann), Msg, Cxt);
|
||||||
|
format({var_args_not_set, Expr}) ->
|
||||||
|
mk_err( pos(Expr), "Could not deduce type of variable arguments list"
|
||||||
|
, "When compiling " ++ pp_expr(Expr)
|
||||||
|
);
|
||||||
|
format({found_void, Ann}) ->
|
||||||
|
mk_err(pos(Ann), "Found a void-typed value.", "`void` is a restricted, uninhabited type. Did you mean `unit`?");
|
||||||
|
|
||||||
format(Err) ->
|
format(Err) ->
|
||||||
mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])).
|
mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])).
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||||
-include("aeso_icode.hrl").
|
-include("aeso_icode.hrl").
|
||||||
|
-include("aeso_utils.hrl").
|
||||||
|
|
||||||
|
|
||||||
-type option() :: pp_sophia_code
|
-type option() :: pp_sophia_code
|
||||||
@ -137,8 +138,9 @@ from_string1(aevm, ContractString, Options) ->
|
|||||||
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)};
|
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)};
|
||||||
from_string1(fate, ContractString, Options) ->
|
from_string1(fate, ContractString, Options) ->
|
||||||
#{ fcode := FCode
|
#{ fcode := FCode
|
||||||
|
, fcode_env := #{child_con_env := ChildContracts}
|
||||||
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
|
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
|
||||||
FateCode = aeso_fcode_to_fate:compile(FCode, Options),
|
FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, Options),
|
||||||
pp_assembler(fate, FateCode, Options),
|
pp_assembler(fate, FateCode, Options),
|
||||||
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||||
{ok, Version} = version(),
|
{ok, Version} = version(),
|
||||||
@ -178,8 +180,9 @@ string_to_code(ContractString, Options) ->
|
|||||||
, type_env => TypeEnv
|
, type_env => TypeEnv
|
||||||
, ast => Ast };
|
, ast => Ast };
|
||||||
fate ->
|
fate ->
|
||||||
Fcode = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, Options),
|
{Env, Fcode} = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, [{original_src, ContractString}|Options]),
|
||||||
#{ fcode => Fcode
|
#{ fcode => Fcode
|
||||||
|
, fcode_env => Env
|
||||||
, unfolded_typed_ast => UnfoldedTypedAst
|
, unfolded_typed_ast => UnfoldedTypedAst
|
||||||
, folded_typed_ast => FoldedTypedAst
|
, folded_typed_ast => FoldedTypedAst
|
||||||
, type_env => TypeEnv
|
, type_env => TypeEnv
|
||||||
@ -468,7 +471,7 @@ error_missing_call_function() ->
|
|||||||
Msg = "Internal error: missing '__call'-function",
|
Msg = "Internal error: missing '__call'-function",
|
||||||
aeso_errors:throw(aeso_errors:new(internal_error, Msg)).
|
aeso_errors:throw(aeso_errors:new(internal_error, Msg)).
|
||||||
|
|
||||||
get_call_type([{contract, _, _, Defs}]) ->
|
get_call_type([{Contract, _, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
|
||||||
case [ {lists:last(QFunName), FunType}
|
case [ {lists:last(QFunName), FunType}
|
||||||
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
|
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
|
||||||
{typed, _,
|
{typed, _,
|
||||||
@ -482,7 +485,7 @@ get_call_type([_ | Contracts]) ->
|
|||||||
get_call_type(Contracts).
|
get_call_type(Contracts).
|
||||||
|
|
||||||
-dialyzer({nowarn_function, get_decode_type/2}).
|
-dialyzer({nowarn_function, get_decode_type/2}).
|
||||||
get_decode_type(FunName, [{contract, Ann, _, Defs}]) ->
|
get_decode_type(FunName, [{Contract, Ann, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
|
||||||
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}];
|
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}];
|
||||||
({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
|
({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
|
||||||
(_) -> [] end,
|
(_) -> [] end,
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
-module(aeso_fcode_to_fate).
|
-module(aeso_fcode_to_fate).
|
||||||
|
|
||||||
-export([compile/2, term_to_fate/1]).
|
-export([compile/2, compile/3, term_to_fate/1, term_to_fate/2]).
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
-export([optimize_fun/4, to_basic_blocks/1]).
|
-export([optimize_fun/4, to_basic_blocks/1]).
|
||||||
@ -45,7 +45,7 @@
|
|||||||
-define(s(N), {store, N}).
|
-define(s(N), {store, N}).
|
||||||
-define(void, {var, 9999}).
|
-define(void, {var, 9999}).
|
||||||
|
|
||||||
-record(env, { contract, vars = [], locals = [], current_function, tailpos = true }).
|
-record(env, { contract, vars = [], locals = [], current_function, tailpos = true, child_contracts = #{}, options = []}).
|
||||||
|
|
||||||
%% -- Debugging --------------------------------------------------------------
|
%% -- Debugging --------------------------------------------------------------
|
||||||
|
|
||||||
@ -70,9 +70,11 @@ code_error(Err) ->
|
|||||||
|
|
||||||
%% @doc Main entry point.
|
%% @doc Main entry point.
|
||||||
compile(FCode, Options) ->
|
compile(FCode, Options) ->
|
||||||
|
compile(#{}, FCode, Options).
|
||||||
|
compile(ChildContracts, FCode, Options) ->
|
||||||
#{ contract_name := ContractName,
|
#{ contract_name := ContractName,
|
||||||
functions := Functions } = FCode,
|
functions := Functions } = FCode,
|
||||||
SFuns = functions_to_scode(ContractName, Functions, Options),
|
SFuns = functions_to_scode(ChildContracts, ContractName, Functions, Options),
|
||||||
SFuns1 = optimize_scode(SFuns, Options),
|
SFuns1 = optimize_scode(SFuns, Options),
|
||||||
FateCode = to_basic_blocks(SFuns1),
|
FateCode = to_basic_blocks(SFuns1),
|
||||||
?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]),
|
?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]),
|
||||||
@ -85,19 +87,20 @@ make_function_name(event) -> <<"Chain.event">>;
|
|||||||
make_function_name({entrypoint, Name}) -> Name;
|
make_function_name({entrypoint, Name}) -> Name;
|
||||||
make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, ".")).
|
make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, ".")).
|
||||||
|
|
||||||
functions_to_scode(ContractName, Functions, Options) ->
|
functions_to_scode(ChildContracts, ContractName, Functions, Options) ->
|
||||||
FunNames = maps:keys(Functions),
|
FunNames = maps:keys(Functions),
|
||||||
maps:from_list(
|
maps:from_list(
|
||||||
[ {make_function_name(Name), function_to_scode(ContractName, FunNames, Name, Attrs, Args, Body, Type, Options)}
|
[ {make_function_name(Name), function_to_scode(ChildContracts, ContractName, FunNames, Name, Attrs, Args, Body, Type, Options)}
|
||||||
|| {Name, #{args := Args,
|
|| {Name, #{args := Args,
|
||||||
body := Body,
|
body := Body,
|
||||||
attrs := Attrs,
|
attrs := Attrs,
|
||||||
return := Type}} <- maps:to_list(Functions)]).
|
return := Type}} <- maps:to_list(Functions)]).
|
||||||
|
|
||||||
function_to_scode(ContractName, Functions, Name, Attrs0, Args, Body, ResType, _Options) ->
|
function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, Options) ->
|
||||||
{ArgTypes, ResType1} = typesig_to_scode(Args, ResType),
|
{ArgTypes, ResType1} = typesig_to_scode(Args, ResType),
|
||||||
Attrs = Attrs0 -- [stateful], %% Only track private and payable from here.
|
Attrs = Attrs0 -- [stateful], %% Only track private and payable from here.
|
||||||
SCode = to_scode(init_env(ContractName, Functions, Name, Args), Body),
|
Env = init_env(ChildContracts, ContractName, Functions, Name, Args, Options),
|
||||||
|
SCode = to_scode(Env, Body),
|
||||||
{Attrs, {ArgTypes, ResType1}, SCode}.
|
{Attrs, {ArgTypes, ResType1}, SCode}.
|
||||||
|
|
||||||
-define(tvars, '$tvars').
|
-define(tvars, '$tvars').
|
||||||
@ -133,7 +136,9 @@ type_to_scode({tvar, X}) ->
|
|||||||
put(?tvars, {I + 1, Vars#{ X => I }}),
|
put(?tvars, {I + 1, Vars#{ X => I }}),
|
||||||
{tvar, I};
|
{tvar, I};
|
||||||
J -> {tvar, J}
|
J -> {tvar, J}
|
||||||
end.
|
end;
|
||||||
|
type_to_scode(L) when is_list(L) -> {tuple, types_to_scode(L)}.
|
||||||
|
|
||||||
|
|
||||||
types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
|
types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
|
||||||
|
|
||||||
@ -142,11 +147,13 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
|
|||||||
|
|
||||||
%% -- Environment functions --
|
%% -- Environment functions --
|
||||||
|
|
||||||
init_env(ContractName, FunNames, Name, Args) ->
|
init_env(ChildContracts, ContractName, FunNames, Name, Args, Options) ->
|
||||||
#env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ],
|
#env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ],
|
||||||
contract = ContractName,
|
contract = ContractName,
|
||||||
|
child_contracts = ChildContracts,
|
||||||
locals = FunNames,
|
locals = FunNames,
|
||||||
current_function = Name,
|
current_function = Name,
|
||||||
|
options = Options,
|
||||||
tailpos = true }.
|
tailpos = true }.
|
||||||
|
|
||||||
next_var(#env{ vars = Vars }) ->
|
next_var(#env{ vars = Vars }) ->
|
||||||
@ -169,7 +176,7 @@ lookup_var(#env{vars = Vars}, X) ->
|
|||||||
|
|
||||||
%% -- The compiler --
|
%% -- The compiler --
|
||||||
|
|
||||||
lit_to_fate(L) ->
|
lit_to_fate(Env, L) ->
|
||||||
case L of
|
case L of
|
||||||
{int, N} -> aeb_fate_data:make_integer(N);
|
{int, N} -> aeb_fate_data:make_integer(N);
|
||||||
{string, S} -> aeb_fate_data:make_string(S);
|
{string, S} -> aeb_fate_data:make_string(S);
|
||||||
@ -179,63 +186,80 @@ lit_to_fate(L) ->
|
|||||||
{contract_pubkey, K} -> aeb_fate_data:make_contract(K);
|
{contract_pubkey, K} -> aeb_fate_data:make_contract(K);
|
||||||
{oracle_pubkey, K} -> aeb_fate_data:make_oracle(K);
|
{oracle_pubkey, K} -> aeb_fate_data:make_oracle(K);
|
||||||
{oracle_query_id, H} -> aeb_fate_data:make_oracle_query(H);
|
{oracle_query_id, H} -> aeb_fate_data:make_oracle_query(H);
|
||||||
|
{contract_code, C} ->
|
||||||
|
Options = Env#env.options,
|
||||||
|
FCode = maps:get(C, Env#env.child_contracts),
|
||||||
|
FateCode = compile(Env#env.child_contracts, FCode, Options),
|
||||||
|
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||||
|
{ok, Version} = aeso_compiler:version(),
|
||||||
|
OriginalSourceCode = proplists:get_value(original_src, Options, ""),
|
||||||
|
Code = #{byte_code => ByteCode,
|
||||||
|
compiler_version => Version,
|
||||||
|
source_hash => crypto:hash(sha256, OriginalSourceCode ++ [0] ++ C),
|
||||||
|
type_info => [],
|
||||||
|
abi_version => aeb_fate_abi:abi_version(),
|
||||||
|
payable => maps:get(payable, FCode)
|
||||||
|
},
|
||||||
|
Serialized = aeser_contract_code:serialize(Code),
|
||||||
|
aeb_fate_data:make_contract_bytearray(Serialized);
|
||||||
{typerep, T} -> aeb_fate_data:make_typerep(type_to_scode(T))
|
{typerep, T} -> aeb_fate_data:make_typerep(type_to_scode(T))
|
||||||
end.
|
end.
|
||||||
|
|
||||||
term_to_fate(E) -> term_to_fate(#{}, E).
|
term_to_fate(E) -> term_to_fate(#env{}, #{}, E).
|
||||||
|
term_to_fate(GlobEnv, E) -> term_to_fate(GlobEnv, #{}, E).
|
||||||
|
|
||||||
term_to_fate(_Env, {lit, L}) ->
|
term_to_fate(GlobEnv, _Env, {lit, L}) ->
|
||||||
lit_to_fate(L);
|
lit_to_fate(GlobEnv, L);
|
||||||
%% negative literals are parsed as 0 - N
|
%% negative literals are parsed as 0 - N
|
||||||
term_to_fate(_Env, {op, '-', [{lit, {int, 0}}, {lit, {int, N}}]}) ->
|
term_to_fate(_GlobEnv, _Env, {op, '-', [{lit, {int, 0}}, {lit, {int, N}}]}) ->
|
||||||
aeb_fate_data:make_integer(-N);
|
aeb_fate_data:make_integer(-N);
|
||||||
term_to_fate(_Env, nil) ->
|
term_to_fate(_GlobEnv, _Env, nil) ->
|
||||||
aeb_fate_data:make_list([]);
|
aeb_fate_data:make_list([]);
|
||||||
term_to_fate(Env, {op, '::', [Hd, Tl]}) ->
|
term_to_fate(GlobEnv, Env, {op, '::', [Hd, Tl]}) ->
|
||||||
%% The Tl will translate into a list, because FATE lists are just lists
|
%% The Tl will translate into a list, because FATE lists are just lists
|
||||||
[term_to_fate(Env, Hd) | term_to_fate(Env, Tl)];
|
[term_to_fate(GlobEnv, Env, Hd) | term_to_fate(GlobEnv, Env, Tl)];
|
||||||
term_to_fate(Env, {tuple, As}) ->
|
term_to_fate(GlobEnv, Env, {tuple, As}) ->
|
||||||
aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(Env, A) || A<-As]));
|
aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(GlobEnv, Env, A) || A<-As]));
|
||||||
term_to_fate(Env, {con, Ar, I, As}) ->
|
term_to_fate(GlobEnv, Env, {con, Ar, I, As}) ->
|
||||||
FateAs = [ term_to_fate(Env, A) || A <- As ],
|
FateAs = [ term_to_fate(GlobEnv, Env, A) || A <- As ],
|
||||||
aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs));
|
aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs));
|
||||||
term_to_fate(_Env, {builtin, bits_all, []}) ->
|
term_to_fate(_GlobEnv, _Env, {builtin, bits_all, []}) ->
|
||||||
aeb_fate_data:make_bits(-1);
|
aeb_fate_data:make_bits(-1);
|
||||||
term_to_fate(_Env, {builtin, bits_none, []}) ->
|
term_to_fate(_GlobEnv, _Env, {builtin, bits_none, []}) ->
|
||||||
aeb_fate_data:make_bits(0);
|
aeb_fate_data:make_bits(0);
|
||||||
term_to_fate(_Env, {op, bits_set, [B, I]}) ->
|
term_to_fate(GlobEnv, _Env, {op, bits_set, [B, I]}) ->
|
||||||
{bits, N} = term_to_fate(B),
|
{bits, N} = term_to_fate(GlobEnv, B),
|
||||||
J = term_to_fate(I),
|
J = term_to_fate(GlobEnv, I),
|
||||||
{bits, N bor (1 bsl J)};
|
{bits, N bor (1 bsl J)};
|
||||||
term_to_fate(_Env, {op, bits_clear, [B, I]}) ->
|
term_to_fate(GlobEnv, _Env, {op, bits_clear, [B, I]}) ->
|
||||||
{bits, N} = term_to_fate(B),
|
{bits, N} = term_to_fate(GlobEnv, B),
|
||||||
J = term_to_fate(I),
|
J = term_to_fate(GlobEnv, I),
|
||||||
{bits, N band bnot (1 bsl J)};
|
{bits, N band bnot (1 bsl J)};
|
||||||
term_to_fate(Env, {'let', X, E, Body}) ->
|
term_to_fate(GlobEnv, Env, {'let', X, E, Body}) ->
|
||||||
Env1 = Env#{ X => term_to_fate(Env, E) },
|
Env1 = Env#{ X => term_to_fate(GlobEnv, Env, E) },
|
||||||
term_to_fate(Env1, Body);
|
term_to_fate(GlobEnv, Env1, Body);
|
||||||
term_to_fate(Env, {var, X}) ->
|
term_to_fate(_GlobEnv, Env, {var, X}) ->
|
||||||
case maps:get(X, Env, undefined) of
|
case maps:get(X, Env, undefined) of
|
||||||
undefined -> throw(not_a_fate_value);
|
undefined -> throw(not_a_fate_value);
|
||||||
V -> V
|
V -> V
|
||||||
end;
|
end;
|
||||||
term_to_fate(_Env, {builtin, map_empty, []}) ->
|
term_to_fate(_GlobEnv, _Env, {builtin, map_empty, []}) ->
|
||||||
aeb_fate_data:make_map(#{});
|
aeb_fate_data:make_map(#{});
|
||||||
term_to_fate(Env, {op, map_set, [M, K, V]}) ->
|
term_to_fate(GlobEnv, Env, {op, map_set, [M, K, V]}) ->
|
||||||
Map = term_to_fate(Env, M),
|
Map = term_to_fate(GlobEnv, Env, M),
|
||||||
Map#{term_to_fate(Env, K) => term_to_fate(Env, V)};
|
Map#{term_to_fate(GlobEnv, Env, K) => term_to_fate(GlobEnv, Env, V)};
|
||||||
term_to_fate(_Env, _) ->
|
term_to_fate(_GlobEnv, _Env, _) ->
|
||||||
throw(not_a_fate_value).
|
throw(not_a_fate_value).
|
||||||
|
|
||||||
to_scode(Env, T) ->
|
to_scode(Env, T) ->
|
||||||
try term_to_fate(T) of
|
try term_to_fate(Env, T) of
|
||||||
V -> [push(?i(V))]
|
V -> [push(?i(V))]
|
||||||
catch throw:not_a_fate_value ->
|
catch throw:not_a_fate_value ->
|
||||||
to_scode1(Env, T)
|
to_scode1(Env, T)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
to_scode1(_Env, {lit, L}) ->
|
to_scode1(Env, {lit, L}) ->
|
||||||
[push(?i(lit_to_fate(L)))];
|
[push(?i(lit_to_fate(Env, L)))];
|
||||||
|
|
||||||
to_scode1(_Env, nil) ->
|
to_scode1(_Env, nil) ->
|
||||||
[aeb_fate_ops:nil(?a)];
|
[aeb_fate_ops:nil(?a)];
|
||||||
@ -555,7 +579,27 @@ builtin_to_scode(Env, aens_lookup, [_Name] = Args) ->
|
|||||||
builtin_to_scode(_Env, auth_tx_hash, []) ->
|
builtin_to_scode(_Env, auth_tx_hash, []) ->
|
||||||
[aeb_fate_ops:auth_tx_hash(?a)];
|
[aeb_fate_ops:auth_tx_hash(?a)];
|
||||||
builtin_to_scode(_Env, auth_tx, []) ->
|
builtin_to_scode(_Env, auth_tx, []) ->
|
||||||
[aeb_fate_ops:auth_tx(?a)].
|
[aeb_fate_ops:auth_tx(?a)];
|
||||||
|
builtin_to_scode(Env, chain_bytecode_hash, [_Addr] = Args) ->
|
||||||
|
call_to_scode(Env, aeb_fate_ops:bytecode_hash(?a, ?a), Args);
|
||||||
|
builtin_to_scode(Env, chain_clone,
|
||||||
|
[InitArgsT, GasCap, Value, Prot, Contract | InitArgs]) ->
|
||||||
|
case GasCap of
|
||||||
|
{builtin, call_gas_left, _} ->
|
||||||
|
call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a),
|
||||||
|
[Contract, InitArgsT, Value, Prot | InitArgs]
|
||||||
|
);
|
||||||
|
_ ->
|
||||||
|
call_to_scode(Env, aeb_fate_ops:clone_g(?a, ?a, ?a, ?a, ?a),
|
||||||
|
[Contract, InitArgsT, Value, GasCap, Prot | InitArgs]
|
||||||
|
)
|
||||||
|
end;
|
||||||
|
|
||||||
|
builtin_to_scode(Env, chain_create,
|
||||||
|
[ Code, InitArgsT, Value | InitArgs]) ->
|
||||||
|
call_to_scode(Env, aeb_fate_ops:create(?a, ?a, ?a),
|
||||||
|
[Code, InitArgsT, Value | InitArgs]
|
||||||
|
).
|
||||||
|
|
||||||
%% -- Operators --
|
%% -- Operators --
|
||||||
|
|
||||||
@ -941,6 +985,10 @@ attributes(I) ->
|
|||||||
{'STR_TO_LOWER', A, B} -> Pure(A, [B]);
|
{'STR_TO_LOWER', A, B} -> Pure(A, [B]);
|
||||||
{'CHAR_TO_INT', A, B} -> Pure(A, [B]);
|
{'CHAR_TO_INT', A, B} -> Pure(A, [B]);
|
||||||
{'CHAR_FROM_INT', A, B} -> Pure(A, [B]);
|
{'CHAR_FROM_INT', A, B} -> Pure(A, [B]);
|
||||||
|
{'CREATE', A, B, C} -> Impure(?a, [A, B, C]);
|
||||||
|
{'CLONE', A, B, C, D} -> Impure(?a, [A, B, C, D]);
|
||||||
|
{'CLONE_G', A, B, C, D, E} -> Impure(?a, [A, B, C, D, E]);
|
||||||
|
{'BYTECODE_HASH', A, B} -> Impure(A, [B]);
|
||||||
{'ABORT', A} -> Impure(pc, A);
|
{'ABORT', A} -> Impure(pc, A);
|
||||||
{'EXIT', A} -> Impure(pc, A);
|
{'EXIT', A} -> Impure(pc, A);
|
||||||
'NOP' -> Pure(none, [])
|
'NOP' -> Pure(none, [])
|
||||||
@ -1694,6 +1742,9 @@ split_calls(Ref, [I | Code], Acc, Blocks) when element(1, I) == 'CALL';
|
|||||||
element(1, I) == 'CALL_R';
|
element(1, I) == 'CALL_R';
|
||||||
element(1, I) == 'CALL_GR';
|
element(1, I) == 'CALL_GR';
|
||||||
element(1, I) == 'CALL_PGR';
|
element(1, I) == 'CALL_PGR';
|
||||||
|
element(1, I) == 'CREATE';
|
||||||
|
element(1, I) == 'CLONE';
|
||||||
|
element(1, I) == 'CLONE_G';
|
||||||
element(1, I) == 'jumpif' ->
|
element(1, I) == 'jumpif' ->
|
||||||
split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]);
|
split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]);
|
||||||
split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) ->
|
split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) ->
|
||||||
|
@ -93,8 +93,20 @@ decl() ->
|
|||||||
?LAZY_P(
|
?LAZY_P(
|
||||||
choice(
|
choice(
|
||||||
%% Contract declaration
|
%% Contract declaration
|
||||||
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
|
[ ?RULE(token(main), keyword(contract),
|
||||||
, ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5}))
|
con(), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5})
|
||||||
|
, ?RULE(keyword(contract),
|
||||||
|
con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4})
|
||||||
|
, ?RULE(keyword(contract), token(interface),
|
||||||
|
con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5})
|
||||||
|
, ?RULE(token(payable), token(main), keyword(contract),
|
||||||
|
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6}))
|
||||||
|
, ?RULE(token(payable), keyword(contract),
|
||||||
|
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5}))
|
||||||
|
, ?RULE(token(payable), keyword(contract), token(interface),
|
||||||
|
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6}))
|
||||||
|
|
||||||
|
|
||||||
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
|
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
|
||||||
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
|
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
|
||||||
, pragma()
|
, pragma()
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
|
|
||||||
-export_type([options/0]).
|
-export_type([options/0]).
|
||||||
|
|
||||||
|
-include("aeso_utils.hrl").
|
||||||
|
|
||||||
-type doc() :: prettypr:document().
|
-type doc() :: prettypr:document().
|
||||||
-type options() :: [{indent, non_neg_integer()} | show_generated].
|
-type options() :: [{indent, non_neg_integer()} | show_generated].
|
||||||
|
|
||||||
@ -131,6 +133,10 @@ typed(A, Type) ->
|
|||||||
false -> follow(hsep(A, text(":")), type(Type))
|
false -> follow(hsep(A, text(":")), type(Type))
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
contract_head(contract_main) -> text("main contract");
|
||||||
|
contract_head(contract_child) -> text("contract");
|
||||||
|
contract_head(contract_interface) -> text("contract interface").
|
||||||
|
|
||||||
%% -- Exports ----------------------------------------------------------------
|
%% -- Exports ----------------------------------------------------------------
|
||||||
|
|
||||||
-spec decls([aeso_syntax:decl()], options()) -> doc().
|
-spec decls([aeso_syntax:decl()], options()) -> doc().
|
||||||
@ -145,11 +151,11 @@ decl(D, Options) ->
|
|||||||
with_options(Options, fun() -> decl(D) end).
|
with_options(Options, fun() -> decl(D) end).
|
||||||
|
|
||||||
-spec decl(aeso_syntax:decl()) -> doc().
|
-spec decl(aeso_syntax:decl()) -> doc().
|
||||||
decl({contract, Attrs, C, Ds}) ->
|
decl({Con, Attrs, C, Ds}) when ?IS_CONTRACT_HEAD(Con) ->
|
||||||
Mod = fun({Mod, true}) when Mod == payable ->
|
Mod = fun({Mod, true}) when Mod == payable ->
|
||||||
text(atom_to_list(Mod));
|
text(atom_to_list(Mod));
|
||||||
(_) -> empty() end,
|
(_) -> empty() end,
|
||||||
block(follow( hsep(lists:map(Mod, Attrs) ++ [text("contract")])
|
block(follow( hsep(lists:map(Mod, Attrs) ++ [contract_head(Con)])
|
||||||
, hsep(name(C), text("="))), decls(Ds));
|
, hsep(name(C), text("="))), decls(Ds));
|
||||||
decl({namespace, _, C, Ds}) ->
|
decl({namespace, _, C, Ds}) ->
|
||||||
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
|
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
|
||||||
|
@ -44,7 +44,9 @@ lexer() ->
|
|||||||
, {"[^/*]+|[/*]", skip()} ],
|
, {"[^/*]+|[/*]", skip()} ],
|
||||||
|
|
||||||
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
||||||
"stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace"],
|
"stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace",
|
||||||
|
"interface", "main"
|
||||||
|
],
|
||||||
KW = string:join(Keywords, "|"),
|
KW = string:join(Keywords, "|"),
|
||||||
|
|
||||||
Rules =
|
Rules =
|
||||||
|
@ -25,7 +25,8 @@
|
|||||||
-type ann_origin() :: system | user.
|
-type ann_origin() :: system | user.
|
||||||
-type ann_format() :: '?:' | hex | infix | prefix | elif.
|
-type ann_format() :: '?:' | hex | infix | prefix | elif.
|
||||||
|
|
||||||
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} | stateful | private].
|
-type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}
|
||||||
|
| stateful | private | payable | main | interface].
|
||||||
|
|
||||||
-type name() :: string().
|
-type name() :: string().
|
||||||
-type id() :: {id, ann(), name()}.
|
-type id() :: {id, ann(), name()}.
|
||||||
@ -34,7 +35,9 @@
|
|||||||
-type qcon() :: {qcon, ann(), [name()]}.
|
-type qcon() :: {qcon, ann(), [name()]}.
|
||||||
-type tvar() :: {tvar, ann(), name()}.
|
-type tvar() :: {tvar, ann(), name()}.
|
||||||
|
|
||||||
-type decl() :: {contract, ann(), con(), [decl()]}
|
-type decl() :: {contract_main, ann(), con(), [decl()]}
|
||||||
|
| {contract_child, ann(), con(), [decl()]}
|
||||||
|
| {contract_interface, ann(), con(), [decl()]}
|
||||||
| {namespace, ann(), con(), [decl()]}
|
| {namespace, ann(), con(), [decl()]}
|
||||||
| {pragma, ann(), pragma()}
|
| {pragma, ann(), pragma()}
|
||||||
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
|
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
|
||||||
|
6
src/aeso_utils.hrl
Normal file
6
src/aeso_utils.hrl
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-define(IS_CONTRACT_HEAD(X),
|
||||||
|
(X =:= contract_main orelse
|
||||||
|
X =:= contract_interface orelse
|
||||||
|
X =:= contract_child
|
||||||
|
)
|
||||||
|
).
|
@ -190,7 +190,7 @@ parameterized_contract(ExtraCode, FunName, Types) ->
|
|||||||
lists:flatten(
|
lists:flatten(
|
||||||
["contract Remote =\n"
|
["contract Remote =\n"
|
||||||
" entrypoint bla : () => unit\n\n"
|
" entrypoint bla : () => unit\n\n"
|
||||||
"contract Dummy =\n",
|
"main contract Dummy =\n",
|
||||||
ExtraCode, "\n",
|
ExtraCode, "\n",
|
||||||
" type an_alias('a) = string * 'a\n"
|
" type an_alias('a) = string * 'a\n"
|
||||||
" record r = {x : an_alias(int), y : variant}\n"
|
" record r = {x : an_alias(int), y : variant}\n"
|
||||||
|
@ -22,7 +22,8 @@ test_cases(1) ->
|
|||||||
MapACI = #{contract =>
|
MapACI = #{contract =>
|
||||||
#{name => <<"C">>,
|
#{name => <<"C">>,
|
||||||
type_defs => [],
|
type_defs => [],
|
||||||
payable => true,
|
payable => true,
|
||||||
|
kind => contract_main,
|
||||||
functions =>
|
functions =>
|
||||||
[#{name => <<"a">>,
|
[#{name => <<"a">>,
|
||||||
arguments =>
|
arguments =>
|
||||||
@ -31,56 +32,57 @@ test_cases(1) ->
|
|||||||
returns => <<"int">>,
|
returns => <<"int">>,
|
||||||
stateful => true,
|
stateful => true,
|
||||||
payable => true}]}},
|
payable => true}]}},
|
||||||
DecACI = <<"payable contract C =\n"
|
DecACI = <<"payable main contract C =\n"
|
||||||
" payable entrypoint a : (int) => int\n">>,
|
" payable entrypoint a : (int) => int\n">>,
|
||||||
{Contract,MapACI,DecACI};
|
{Contract,MapACI,DecACI};
|
||||||
|
|
||||||
test_cases(2) ->
|
test_cases(2) ->
|
||||||
Contract = <<"contract C =\n"
|
Contract = <<"main contract C =\n"
|
||||||
" type allan = int\n"
|
" type allan = int\n"
|
||||||
" entrypoint a(i : allan) = i+1\n">>,
|
" entrypoint a(i : allan) = i+1\n">>,
|
||||||
MapACI = #{contract =>
|
MapACI = #{contract =>
|
||||||
#{name => <<"C">>, payable => false,
|
#{name => <<"C">>, payable => false,
|
||||||
type_defs =>
|
kind => contract_main,
|
||||||
[#{name => <<"allan">>,
|
type_defs =>
|
||||||
typedef => <<"int">>,
|
[#{name => <<"allan">>,
|
||||||
vars => []}],
|
typedef => <<"int">>,
|
||||||
functions =>
|
vars => []}],
|
||||||
[#{arguments =>
|
functions =>
|
||||||
[#{name => <<"i">>,
|
[#{arguments =>
|
||||||
type => <<"C.allan">>}],
|
[#{name => <<"i">>,
|
||||||
name => <<"a">>,
|
type => <<"C.allan">>}],
|
||||||
returns => <<"int">>,
|
name => <<"a">>,
|
||||||
stateful => false,
|
returns => <<"int">>,
|
||||||
payable => false}]}},
|
stateful => false,
|
||||||
DecACI = <<"contract C =\n"
|
payable => false}]}},
|
||||||
|
DecACI = <<"main contract C =\n"
|
||||||
" type allan = int\n"
|
" type allan = int\n"
|
||||||
" entrypoint a : (C.allan) => int\n">>,
|
" entrypoint a : (C.allan) => int\n">>,
|
||||||
{Contract,MapACI,DecACI};
|
{Contract,MapACI,DecACI};
|
||||||
test_cases(3) ->
|
test_cases(3) ->
|
||||||
Contract = <<"contract C =\n"
|
Contract = <<"main contract C =\n"
|
||||||
" type state = unit\n"
|
" type state = unit\n"
|
||||||
" datatype event = SingleEventDefined\n"
|
" datatype event = SingleEventDefined\n"
|
||||||
" datatype bert('a) = Bin('a)\n"
|
" datatype bert('a) = Bin('a)\n"
|
||||||
" entrypoint a(i : bert(string)) = 1\n">>,
|
" entrypoint a(i : bert(string)) = 1\n">>,
|
||||||
MapACI = #{contract =>
|
MapACI = #{contract =>
|
||||||
#{functions =>
|
#{functions =>
|
||||||
[#{arguments =>
|
[#{arguments =>
|
||||||
[#{name => <<"i">>,
|
[#{name => <<"i">>,
|
||||||
type =>
|
type =>
|
||||||
#{<<"C.bert">> => [<<"string">>]}}],
|
#{<<"C.bert">> => [<<"string">>]}}],
|
||||||
name => <<"a">>,returns => <<"int">>,
|
name => <<"a">>,returns => <<"int">>,
|
||||||
stateful => false, payable => false}],
|
stateful => false, payable => false}],
|
||||||
name => <<"C">>, payable => false,
|
name => <<"C">>, payable => false, kind => contract_main,
|
||||||
event => #{variant => [#{<<"SingleEventDefined">> => []}]},
|
event => #{variant => [#{<<"SingleEventDefined">> => []}]},
|
||||||
state => <<"unit">>,
|
state => <<"unit">>,
|
||||||
type_defs =>
|
type_defs =>
|
||||||
[#{name => <<"bert">>,
|
[#{name => <<"bert">>,
|
||||||
typedef =>
|
typedef =>
|
||||||
#{variant =>
|
#{variant =>
|
||||||
[#{<<"Bin">> => [<<"'a">>]}]},
|
[#{<<"Bin">> => [<<"'a">>]}]},
|
||||||
vars => [#{name => <<"'a">>}]}]}},
|
vars => [#{name => <<"'a">>}]}]}},
|
||||||
DecACI = <<"contract C =\n"
|
DecACI = <<"main contract C =\n"
|
||||||
" type state = unit\n"
|
" type state = unit\n"
|
||||||
" datatype event = SingleEventDefined\n"
|
" datatype event = SingleEventDefined\n"
|
||||||
" datatype bert('a) = Bin('a)\n"
|
" datatype bert('a) = Bin('a)\n"
|
||||||
@ -101,17 +103,24 @@ aci_test_contract(Name) ->
|
|||||||
true -> [debug_mode];
|
true -> [debug_mode];
|
||||||
false -> []
|
false -> []
|
||||||
end ++ [{include, {file_system, [aeso_test_utils:contract_path()]}}],
|
end ++ [{include, {file_system, [aeso_test_utils:contract_path()]}}],
|
||||||
{ok, JSON} = aeso_aci:contract_interface(json, String, Opts),
|
JSON = case aeso_aci:contract_interface(json, String, Opts) of
|
||||||
{ok, #{aci := JSON1}} = aeso_compiler:from_string(String, [{aci, json}, {backend, fate} | Opts]),
|
{ok, J} -> J;
|
||||||
?assertEqual(JSON, JSON1),
|
{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]),
|
io:format("STUB:\n~s\n", [ContractStub]),
|
||||||
{ok, ContractStub} = aeso_aci:render_aci_json(JSON),
|
check_stub(ContractStub, [{src_file, Name}]),
|
||||||
|
|
||||||
io:format("STUB:\n~s\n", [ContractStub]),
|
ok;
|
||||||
check_stub(ContractStub, [{src_file, Name}]),
|
{error, ErrorString} when is_binary(ErrorString) -> error(ErrorString);
|
||||||
|
{error, Error} -> aeso_compiler_tests:print_and_throw(Error)
|
||||||
ok.
|
end.
|
||||||
|
|
||||||
check_stub(Stub, Options) ->
|
check_stub(Stub, Options) ->
|
||||||
try aeso_parser:string(binary_to_list(Stub), Options) of
|
try aeso_parser:string(binary_to_list(Stub), Options) of
|
||||||
|
@ -59,8 +59,8 @@ calldata_aci_test_() ->
|
|||||||
end} || {ContractName, Fun, Args} <- compilable_contracts()].
|
end} || {ContractName, Fun, Args} <- compilable_contracts()].
|
||||||
|
|
||||||
parse_args(Fun, Args) ->
|
parse_args(Fun, Args) ->
|
||||||
[{contract, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] =
|
[{contract_main, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] =
|
||||||
aeso_parser:string("contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
|
aeso_parser:string("main contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
|
||||||
strip_ann(AST).
|
strip_ann(AST).
|
||||||
|
|
||||||
strip_ann(T) when is_tuple(T) ->
|
strip_ann(T) when is_tuple(T) ->
|
||||||
|
@ -34,9 +34,7 @@ simple_compile_test_() ->
|
|||||||
#{fate_code := Code} when Backend == fate ->
|
#{fate_code := Code} when Backend == fate ->
|
||||||
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
|
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
|
||||||
?assertMatch({X, X}, {Code1, Code});
|
?assertMatch({X, X}, {Code1, Code});
|
||||||
ErrBin ->
|
Error -> io:format("\n\n~p\n\n", [Error]), print_and_throw(Error)
|
||||||
io:format("\n~s", [ErrBin]),
|
|
||||||
error(ErrBin)
|
|
||||||
end
|
end
|
||||||
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
|
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
|
||||||
not lists:member(ContractName, not_compilable_on(Backend))] ++
|
not lists:member(ContractName, not_compilable_on(Backend))] ++
|
||||||
@ -179,17 +177,16 @@ compilable_contracts() ->
|
|||||||
"lhs_matching",
|
"lhs_matching",
|
||||||
"more_strings",
|
"more_strings",
|
||||||
"protected_call",
|
"protected_call",
|
||||||
"hermetization_turnoff"
|
"hermetization_turnoff",
|
||||||
|
"multiple_contracts",
|
||||||
|
"clone",
|
||||||
|
"clone_simple",
|
||||||
|
"create",
|
||||||
|
"test" % Custom general-purpose test file. Keep it last on the list.
|
||||||
].
|
].
|
||||||
|
|
||||||
not_compilable_on(fate) -> [];
|
not_compilable_on(fate) -> [];
|
||||||
not_compilable_on(aevm) ->
|
not_compilable_on(aevm) -> compilable_contracts().
|
||||||
[ "stdlib_include", "manual_stdlib_include", "pairing_crypto"
|
|
||||||
, "aens_update", "basic_auth_tx", "more_strings"
|
|
||||||
, "unapplied_builtins", "bytes_to_x", "state_handling", "protected_call"
|
|
||||||
, "hermetization_turnoff"
|
|
||||||
|
|
||||||
].
|
|
||||||
|
|
||||||
debug_mode_contracts() ->
|
debug_mode_contracts() ->
|
||||||
["hermetization_turnoff"].
|
["hermetization_turnoff"].
|
||||||
@ -635,9 +632,9 @@ failing_contracts() ->
|
|||||||
<<?Pos(2, 1)
|
<<?Pos(2, 1)
|
||||||
"Cannot compile with this version of the compiler,\n"
|
"Cannot compile with this version of the compiler,\n"
|
||||||
"because it does not satisfy the constraint ", Version/binary, " == 9.9.9">>])
|
"because it does not satisfy the constraint ", Version/binary, " == 9.9.9">>])
|
||||||
, ?TYPE_ERROR(multiple_contracts,
|
, ?TYPE_ERROR(interface_with_defs,
|
||||||
[<<?Pos(2, 3)
|
[<<?Pos(2, 3)
|
||||||
"Only the main contract can contain defined functions or entrypoints.\n"
|
"Contract interfaces cannot contain defined functions or entrypoints.\n"
|
||||||
"Fix: replace the definition of 'foo' by a type signature.">>])
|
"Fix: replace the definition of 'foo' by a type signature.">>])
|
||||||
, ?TYPE_ERROR(contract_as_namespace,
|
, ?TYPE_ERROR(contract_as_namespace,
|
||||||
[<<?Pos(5, 28)
|
[<<?Pos(5, 28)
|
||||||
@ -733,6 +730,39 @@ failing_contracts() ->
|
|||||||
, ?TYPE_ERROR(bad_state,
|
, ?TYPE_ERROR(bad_state,
|
||||||
[<<?Pos(4, 16)
|
[<<?Pos(4, 16)
|
||||||
"Conflicting updates for field 'foo'">>])
|
"Conflicting updates for field 'foo'">>])
|
||||||
|
, ?TYPE_ERROR(factories_type_errors,
|
||||||
|
[<<?Pos(10,18)
|
||||||
|
"Chain.clone requires `ref` named argument of contract type.">>,
|
||||||
|
<<?Pos(11,18)
|
||||||
|
"Cannot unify (gas : int, value : int, protected : bool) => if(protected, option(void), void)\n and (gas : int, value : int, protected : bool, int, bool) => 'b\n"
|
||||||
|
"when checking contract construction of type\n (gas : int, value : int, protected : bool) =>\n if(protected, option(void), void) (at line 11, column 18)\nagainst the expected type\n (gas : int, value : int, protected : bool, int, bool) => 'b">>,
|
||||||
|
<<?Pos(12,37)
|
||||||
|
"Cannot unify int\n and bool\n"
|
||||||
|
"when checking named argument\n gas : int\nagainst inferred type\n bool">>,
|
||||||
|
<<?Pos(13,18),
|
||||||
|
"Kaboom is not implemented.\n"
|
||||||
|
"when resolving arguments of variadic function\n Chain.create">>,
|
||||||
|
<<?Pos(18,18)
|
||||||
|
"Cannot unify (gas : int, value : int, protected : bool, int, bool) => if(protected, option(void), void)\n and (gas : int, value : int, protected : bool) => 'a\n"
|
||||||
|
"when checking contract construction of type\n (gas : int, value : int, protected : bool, int, bool) =>\n if(protected, option(void), void) (at line 18, column 18)\nagainst the expected type\n (gas : int, value : int, protected : bool) => 'a">>,
|
||||||
|
<<?Pos(19,42),
|
||||||
|
"Named argument protected (at line 19, column 42) is not one of the expected named arguments\n - value : int">>,
|
||||||
|
<<?Pos(20,42),
|
||||||
|
"Cannot unify int\n and bool\n"
|
||||||
|
"when checking named argument\n value : int\nagainst inferred type\n bool">>
|
||||||
|
])
|
||||||
|
, ?TYPE_ERROR(ambiguous_main,
|
||||||
|
[<<?Pos(1,1)
|
||||||
|
"Could not deduce the main contract. You can point it out manually with the `main` keyword.">>
|
||||||
|
])
|
||||||
|
, ?TYPE_ERROR(no_main_contract,
|
||||||
|
[<<?Pos(0,0)
|
||||||
|
"No contract defined.">>
|
||||||
|
])
|
||||||
|
, ?TYPE_ERROR(multiple_main_contracts,
|
||||||
|
[<<?Pos(1,6)
|
||||||
|
"Only one main contract can be defined.">>
|
||||||
|
])
|
||||||
].
|
].
|
||||||
|
|
||||||
-define(Path(File), "code_errors/" ??File).
|
-define(Path(File), "code_errors/" ??File).
|
||||||
@ -746,9 +776,7 @@ failing_contracts() ->
|
|||||||
{fate, ?Msg(File, Line, Col, ErrFATE)}]}).
|
{fate, ?Msg(File, Line, Col, ErrFATE)}]}).
|
||||||
|
|
||||||
failing_code_gen_contracts() ->
|
failing_code_gen_contracts() ->
|
||||||
[ ?SAME(last_declaration_must_be_contract, 1, 1,
|
[ ?SAME(missing_definition, 2, 14,
|
||||||
"Expected a contract as the last declaration instead of the namespace 'LastDeclarationIsNotAContract'")
|
|
||||||
, ?SAME(missing_definition, 2, 14,
|
|
||||||
"Missing definition of function 'foo'.")
|
"Missing definition of function 'foo'.")
|
||||||
, ?AEVM(polymorphic_entrypoint, 2, 17,
|
, ?AEVM(polymorphic_entrypoint, 2, 17,
|
||||||
"The argument\n"
|
"The argument\n"
|
||||||
@ -846,6 +874,8 @@ failing_code_gen_contracts() ->
|
|||||||
"Invalid state type\n"
|
"Invalid state type\n"
|
||||||
" {f : (int) => int}\n"
|
" {f : (int) => int}\n"
|
||||||
"The state cannot contain functions in the AEVM. Use FATE if you need this.")
|
"The state cannot contain functions in the AEVM. Use FATE if you need this.")
|
||||||
|
, ?FATE(child_with_decls, 2, 14,
|
||||||
|
"Missing definition of function 'f'.")
|
||||||
].
|
].
|
||||||
|
|
||||||
validation_test_() ->
|
validation_test_() ->
|
||||||
@ -883,14 +913,26 @@ validation_fails() ->
|
|||||||
"Byte code contract is not payable, but source code contract is.">>]}].
|
"Byte code contract is not payable, but source code contract is.">>]}].
|
||||||
|
|
||||||
validate(Contract1, Contract2) ->
|
validate(Contract1, Contract2) ->
|
||||||
ByteCode = #{ fate_code := FCode } = compile(fate, Contract1),
|
case compile(fate, Contract1) of
|
||||||
FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)),
|
ByteCode = #{ fate_code := FCode } ->
|
||||||
Source = aeso_test_utils:read_contract(Contract2),
|
FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)),
|
||||||
aeso_compiler:validate_byte_code(
|
Source = aeso_test_utils:read_contract(Contract2),
|
||||||
ByteCode#{ byte_code := FCode1 }, Source,
|
aeso_compiler:validate_byte_code(
|
||||||
case lists:member(Contract2, debug_mode_contracts()) of
|
ByteCode#{ byte_code := FCode1 }, Source,
|
||||||
true -> [debug_mode];
|
case lists:member(Contract2, debug_mode_contracts()) of
|
||||||
false -> []
|
true -> [debug_mode];
|
||||||
end ++
|
false -> []
|
||||||
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]).
|
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,
|
fun(_) -> ok end,
|
||||||
[{"Parse a contract with an identity function.",
|
[{"Parse a contract with an identity function.",
|
||||||
fun() ->
|
fun() ->
|
||||||
Text = "contract Identity =\n"
|
Text = "main contract Identity =\n"
|
||||||
" function id(x) = x\n",
|
" function id(x) = x\n",
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
[{contract, _, {con, _, "Identity"},
|
[{contract_main, _, {con, _, "Identity"},
|
||||||
[{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"},
|
[{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"},
|
||||||
{id, _, "x"}}]}], parse_string(Text)),
|
{id, _, "x"}}]}], parse_string(Text)),
|
||||||
ok
|
ok
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
contract Identity =
|
contract Identity =
|
||||||
function main (x:int) = x
|
function main_fun (x:int) = x
|
||||||
|
|
||||||
function __call() = 12
|
function __call() = 12
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
contract Remote =
|
contract interface Remote =
|
||||||
entrypoint main : (int) => unit
|
entrypoint main_fun : (int) => unit
|
||||||
|
|
||||||
contract AddrChain =
|
contract AddrChain =
|
||||||
type o_type = oracle(string, map(string, int))
|
type o_type = oracle(string, map(string, int))
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
contract Remote =
|
contract interface Remote =
|
||||||
entrypoint foo : () => unit
|
entrypoint foo : () => unit
|
||||||
|
|
||||||
contract AddressLiterals =
|
contract AddressLiterals =
|
||||||
|
5
test/contracts/ambiguous_main.aes
Normal file
5
test/contracts/ambiguous_main.aes
Normal file
@ -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
|
entrypoint foo : () => unit
|
||||||
|
|
||||||
contract AddressLiterals =
|
contract AddressLiterals =
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
contract Remote =
|
contract interface Remote =
|
||||||
entrypoint id : int => int
|
entrypoint id : int => int
|
||||||
|
|
||||||
contract ProtectedCall =
|
contract ProtectedCall =
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
function square(x) = x ^ 2
|
function square(x) = x ^ 2
|
||||||
contract Main =
|
contract Main =
|
||||||
entrypoint main() = square(10)
|
entrypoint main_fun() = square(10)
|
||||||
|
28
test/contracts/clone.aes
Normal file
28
test/contracts/clone.aes
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
contract interface HigherOrderState =
|
||||||
|
entrypoint init : () => void
|
||||||
|
entrypoint apply : int => int
|
||||||
|
stateful entrypoint inc : int => unit
|
||||||
|
|
||||||
|
contract interface LowerDisorderAnarchy =
|
||||||
|
entrypoint init : (int) => void
|
||||||
|
|
||||||
|
|
||||||
|
main contract C =
|
||||||
|
// both `s` and `l` should be of type `HigherOrderState` in this test
|
||||||
|
stateful entrypoint run_clone(s : HigherOrderState, l : LowerDisorderAnarchy) : HigherOrderState =
|
||||||
|
let s1 = Chain.clone(ref=s)
|
||||||
|
let Some(s2) = Chain.clone(ref=s, protected=true)
|
||||||
|
let None = Chain.clone(ref=s, protected=true, gas=1)
|
||||||
|
let None = Chain.clone(ref=l, protected=true, 123) // since it should be HigherOrderState underneath
|
||||||
|
let s3 = Chain.clone(ref=s1)
|
||||||
|
require(s1.apply(2137) == 2137, "APPLY_S1_0")
|
||||||
|
require(s2.apply(2137) == 2137, "APPLY_S2_0")
|
||||||
|
require(s3.apply(2137) == 2137, "APPLY_S3_0")
|
||||||
|
s1.inc(1)
|
||||||
|
s2.inc(1)
|
||||||
|
s1.inc(1)
|
||||||
|
require(s1.apply(2137) == 2139, "APPLY_S1_2")
|
||||||
|
require(s2.apply(2137) == 2138, "APPLY_S2_1")
|
||||||
|
require(s3.apply(2137) == 2137, "APPLY_S3_0")
|
||||||
|
s1
|
7
test/contracts/clone_simple.aes
Normal file
7
test/contracts/clone_simple.aes
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
contract interface I =
|
||||||
|
entrypoint init : () => void
|
||||||
|
|
||||||
|
contract C =
|
||||||
|
stateful entrypoint f(i : I) =
|
||||||
|
let Some(c1) = Chain.clone(ref=i, protected = true)
|
||||||
|
2
|
@ -5,5 +5,5 @@ contract BadAENSresolve =
|
|||||||
function fail() : t(int) =
|
function fail() : t(int) =
|
||||||
AENS.resolve("foo.aet", "whatever")
|
AENS.resolve("foo.aet", "whatever")
|
||||||
|
|
||||||
entrypoint main() = ()
|
entrypoint main_fun() = ()
|
||||||
|
|
||||||
|
5
test/contracts/code_errors/child_with_decls.aes
Normal file
5
test/contracts/code_errors/child_with_decls.aes
Normal file
@ -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}
|
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 ) =
|
stateful function foo(o) : oracle_query(_, string ) =
|
||||||
Oracle.query(o, (x) => x + 1, 100, RelativeTTL(100), RelativeTTL(100))
|
Oracle.query(o, (x) => x + 1, 100, RelativeTTL(100), RelativeTTL(100))
|
||||||
|
|
||||||
entrypoint main() = ()
|
entrypoint main_fun() = ()
|
||||||
|
@ -2,4 +2,4 @@ contract HigherOrderResponseType =
|
|||||||
stateful function foo(o, q : oracle_query(string, _)) =
|
stateful function foo(o, q : oracle_query(string, _)) =
|
||||||
Oracle.respond(o, q, (x) => x + 1)
|
Oracle.respond(o, q, (x) => x + 1)
|
||||||
|
|
||||||
entrypoint main() = ()
|
entrypoint main_fun() = ()
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
namespace LastDeclarationIsNotAContract =
|
|
||||||
function add(x, y) = x + y
|
|
@ -1,3 +1,3 @@
|
|||||||
contract MissingDefinition =
|
contract MissingDefinition =
|
||||||
entrypoint foo : int => int
|
entrypoint foo : int => int
|
||||||
entrypoint main() = foo(0)
|
entrypoint main_fun() = foo(0)
|
||||||
|
@ -3,5 +3,5 @@ contract PolymorphicAENSresolve =
|
|||||||
function fail() : option('a) =
|
function fail() : option('a) =
|
||||||
AENS.resolve("foo.aet", "whatever")
|
AENS.resolve("foo.aet", "whatever")
|
||||||
|
|
||||||
entrypoint main() = ()
|
entrypoint main_fun() = ()
|
||||||
|
|
||||||
|
@ -3,4 +3,4 @@ contract MapAsMapKey =
|
|||||||
|
|
||||||
function foo(m) : t('a) = {[m] = 0}
|
function foo(m) : t('a) = {[m] = 0}
|
||||||
|
|
||||||
entrypoint main() = ()
|
entrypoint main_fun() = ()
|
||||||
|
@ -2,4 +2,4 @@ contract PolymorphicQueryType =
|
|||||||
stateful function is_oracle(o) =
|
stateful function is_oracle(o) =
|
||||||
Oracle.check(o)
|
Oracle.check(o)
|
||||||
|
|
||||||
entrypoint main() = ()
|
entrypoint main_fun() = ()
|
||||||
|
@ -2,4 +2,4 @@ contract PolymorphicResponseType =
|
|||||||
function is_oracle(o : oracle(string, 'r)) =
|
function is_oracle(o : oracle(string, 'r)) =
|
||||||
Oracle.check(o)
|
Oracle.check(o)
|
||||||
|
|
||||||
entrypoint main(o : oracle(string, int)) = is_oracle(o)
|
entrypoint main_fun(o : oracle(string, int)) = is_oracle(o)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
contract Remote =
|
contract interface Remote =
|
||||||
entrypoint foo : int => int
|
entrypoint foo : int => int
|
||||||
|
|
||||||
contract UnappliedContractCall =
|
contract UnappliedContractCall =
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
contract UnappliedNamedArgBuiltin =
|
contract UnappliedNamedArgBuiltin =
|
||||||
// Allowed in FATE, but not AEVM
|
// Allowed in FATE, but not AEVM
|
||||||
stateful entrypoint main(s) =
|
stateful entrypoint main_fun(s) =
|
||||||
let reg = Oracle.register
|
let reg = Oracle.register
|
||||||
reg(signature = s, Contract.address, 100, RelativeTTL(100)) : oracle(int, int)
|
reg(signature = s, Contract.address, 100, RelativeTTL(100)) : oracle(int, int)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
contract Remote =
|
contract interface Remote =
|
||||||
entrypoint up_to : (int) => list(int)
|
entrypoint up_to : (int) => list(int)
|
||||||
entrypoint sum : (list(int)) => int
|
entrypoint sum : (list(int)) => int
|
||||||
entrypoint some_string : () => string
|
entrypoint some_string : () => string
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
contract Foo =
|
contract interface Foo =
|
||||||
entrypoint foo : () => int
|
entrypoint foo : () => int
|
||||||
|
|
||||||
contract Fail =
|
contract Fail =
|
||||||
|
28
test/contracts/create.aes
Normal file
28
test/contracts/create.aes
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
contract IntegerAdder =
|
||||||
|
entrypoint init() = ()
|
||||||
|
entrypoint addIntegers(x, y) = x + y
|
||||||
|
|
||||||
|
contract IntegerAdderHolder =
|
||||||
|
type state = IntegerAdder
|
||||||
|
stateful entrypoint init() = Chain.create() : IntegerAdder
|
||||||
|
entrypoint get() = state
|
||||||
|
|
||||||
|
contract IntegerAdderFactory =
|
||||||
|
entrypoint init() = ()
|
||||||
|
stateful entrypoint new() =
|
||||||
|
let i = Chain.create() : IntegerAdderHolder
|
||||||
|
i.get()
|
||||||
|
|
||||||
|
payable contract ValueAdder =
|
||||||
|
entrypoint init() = ()
|
||||||
|
stateful entrypoint addValue(x) =
|
||||||
|
let integerAdderFactory = Chain.create()
|
||||||
|
let adder = integerAdderFactory.new()
|
||||||
|
adder.addIntegers(x, Contract.balance)
|
||||||
|
|
||||||
|
main contract EnterpriseContract =
|
||||||
|
entrypoint init() = ()
|
||||||
|
stateful payable entrypoint increaseByThree(x) =
|
||||||
|
require(Call.value >= 3, "Price for addition = 3AEtto, insufficient funds")
|
||||||
|
let threeAdder = Chain.create(value = 3)
|
||||||
|
threeAdder.addValue(x)
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
// Testing primitives for accessing the block chain environment
|
// Testing primitives for accessing the block chain environment
|
||||||
contract Interface =
|
contract interface Interface =
|
||||||
entrypoint contract_address : () => address
|
entrypoint contract_address : () => address
|
||||||
entrypoint call_origin : () => address
|
entrypoint call_origin : () => address
|
||||||
entrypoint call_caller : () => address
|
entrypoint call_caller : () => address
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
contract Remote =
|
contract interface Remote =
|
||||||
entrypoint dummy : () => unit
|
entrypoint dummy : () => unit
|
||||||
|
|
||||||
contract Events =
|
contract Events =
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// An implementation of the factorial function where each recursive
|
// An implementation of the factorial function where each recursive
|
||||||
// call is to another contract. Not the cheapest way to compute factorial.
|
// call is to another contract. Not the cheapest way to compute factorial.
|
||||||
contract FactorialServer =
|
contract interface FactorialServer =
|
||||||
entrypoint fac : (int) => int
|
entrypoint fac : (int) => int
|
||||||
|
|
||||||
contract Factorial =
|
contract Factorial =
|
||||||
|
24
test/contracts/factories_type_errors.aes
Normal file
24
test/contracts/factories_type_errors.aes
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
contract interface Kaboom =
|
||||||
|
entrypoint init : () => void
|
||||||
|
|
||||||
|
contract Bakoom =
|
||||||
|
type state = int
|
||||||
|
entrypoint init(x, b) = if(b) x else 0
|
||||||
|
|
||||||
|
main contract Test =
|
||||||
|
stateful entrypoint test(k : Kaboom) =
|
||||||
|
let k_bad1 = Chain.clone() : Kaboom
|
||||||
|
let k_bad2 = Chain.clone(ref=k, 123, true)
|
||||||
|
let k_bad3 = Chain.clone(ref=k, gas=true)
|
||||||
|
let k_bad4 = Chain.create() : Kaboom
|
||||||
|
let k_gud1 = Chain.clone(ref=k)
|
||||||
|
let Some(k_gud2) = Chain.clone(ref=k, protected=true)
|
||||||
|
let Some(k_gud3) = Chain.clone(ref=k, value=10, protected=true, gas=123)
|
||||||
|
|
||||||
|
let b_bad1 = Chain.create() : Bakoom
|
||||||
|
let b_bad2 = Chain.create(123, true, protected=true) : Bakoom
|
||||||
|
let b_bad3 = Chain.create(123, true, value=true) : Bakoom
|
||||||
|
let b_gud1 = Chain.create(123, true) : Bakoom
|
||||||
|
let b_gud2 = Chain.create(123, true, value=100) : Bakoom
|
||||||
|
|
||||||
|
b_gud1
|
@ -1,3 +1,2 @@
|
|||||||
|
main contract Identity =
|
||||||
contract Identity =
|
entrypoint main_fun (x:int) = x
|
||||||
entrypoint main (x:int) = x
|
|
||||||
|
5
test/contracts/interface_with_defs.aes
Normal file
5
test/contracts/interface_with_defs.aes
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
contract interface ContractOne =
|
||||||
|
entrypoint foo() = "foo"
|
||||||
|
|
||||||
|
contract ContractTwo =
|
||||||
|
entrypoint bar() = "bar"
|
@ -16,7 +16,7 @@ contract LHSMatching =
|
|||||||
let null(_ :: _) = false
|
let null(_ :: _) = false
|
||||||
!null(xs)
|
!null(xs)
|
||||||
|
|
||||||
entrypoint main() =
|
entrypoint main_fun() =
|
||||||
from_some(Some([0]))
|
from_some(Some([0]))
|
||||||
++ append([length([true]), 2, 3], [4, 5, 6])
|
++ append([length([true]), 2, 3], [4, 5, 6])
|
||||||
++ [7 | if (local_match([false]))]
|
++ [7 | if (local_match([false]))]
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
contract MissingEventType =
|
contract MissingEventType =
|
||||||
entrypoint main() =
|
entrypoint main_fun() =
|
||||||
Chain.event("MAIN")
|
Chain.event("MAIN")
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
contract ContractOne =
|
contract Child =
|
||||||
entrypoint foo() = "foo"
|
entrypoint
|
||||||
|
add2 : int => int
|
||||||
|
add2(x) = x + 2
|
||||||
|
|
||||||
contract ContractTwo =
|
main contract Main =
|
||||||
entrypoint bar() = "bar"
|
entrypoint add4(x, c : Child) = c.add2(x) + 2
|
5
test/contracts/multiple_main_contracts.aes
Normal file
5
test/contracts/multiple_main_contracts.aes
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
main contract C =
|
||||||
|
entrypoint f() = 123
|
||||||
|
|
||||||
|
main contract D =
|
||||||
|
entrypoint f() = 123
|
2
test/contracts/no_main_contract.aes
Normal file
2
test/contracts/no_main_contract.aes
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
contract interface C =
|
||||||
|
entrypoint f : () => unit
|
@ -1,4 +1,4 @@
|
|||||||
contract C1 =
|
contract interface C1 =
|
||||||
entrypoint f : int
|
entrypoint f : int
|
||||||
|
|
||||||
contract C =
|
contract C =
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
contract Remote =
|
contract interface Remote =
|
||||||
entrypoint id : int => int
|
entrypoint id : int => int
|
||||||
|
|
||||||
contract ProtectedCall =
|
contract ProtectedCall =
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
|
|
||||||
contract Remote1 =
|
contract interface Remote1 =
|
||||||
entrypoint main : (int) => int
|
entrypoint main_fun : (int) => int
|
||||||
|
|
||||||
contract Remote2 =
|
contract interface Remote2 =
|
||||||
entrypoint call : (Remote1, int) => int
|
entrypoint call : (Remote1, int) => int
|
||||||
|
|
||||||
contract Remote3 =
|
contract interface Remote3 =
|
||||||
entrypoint get : () => int
|
entrypoint get : () => int
|
||||||
entrypoint tick : () => unit
|
entrypoint tick : () => unit
|
||||||
|
|
||||||
contract RemoteCall =
|
contract RemoteCall =
|
||||||
|
|
||||||
stateful entrypoint call(r : Remote1, x : int) : int =
|
stateful entrypoint call(r : Remote1, x : int) : int =
|
||||||
r.main(gas = 10000, value = 10, x)
|
r.main_fun(gas = 10000, value = 10, x)
|
||||||
|
|
||||||
entrypoint staged_call(r1 : Remote1, r2 : Remote2, x : int) =
|
entrypoint staged_call(r1 : Remote1, r2 : Remote2, x : int) =
|
||||||
r2.call(r1, x)
|
r2.call(r1, x)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
contract SpendContract =
|
contract interface SpendContract =
|
||||||
entrypoint withdraw : (int) => int
|
entrypoint withdraw : (int) => int
|
||||||
|
|
||||||
contract SpendTest =
|
contract SpendTest =
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
include "String.aes"
|
include "String.aes"
|
||||||
contract Remote =
|
contract interface Remote =
|
||||||
record rstate = { i : int, s : string, m : map(int, int) }
|
record rstate = { i : int, s : string, m : map(int, int) }
|
||||||
|
|
||||||
entrypoint look_at : (rstate) => unit
|
entrypoint look_at : (rstate) => unit
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
contract Remote =
|
contract interface Remote =
|
||||||
stateful entrypoint remote_spend : (address, int) => unit
|
stateful entrypoint remote_spend : (address, int) => unit
|
||||||
entrypoint remote_pure : int => int
|
entrypoint remote_pure : int => int
|
||||||
|
|
||||||
|
@ -1,102 +1,12 @@
|
|||||||
|
// This is a custom test file if you need to run a compiler without
|
||||||
|
// changing aeso_compiler_tests.erl
|
||||||
|
|
||||||
contract Identity =
|
include "List.aes"
|
||||||
// type xy = {x:int, y:int}
|
|
||||||
// type xz = {x:int, z:int}
|
|
||||||
// type yz = {y:int, z:int}
|
|
||||||
record point = {x:int,y:int}
|
|
||||||
record cp('a) = {color: string, p:'a}
|
|
||||||
//type intpoint = point(int)
|
|
||||||
// //if (x==42) 1 else (x*x)
|
|
||||||
// }
|
|
||||||
//let baz() = {age:3, name:(4:int)}
|
|
||||||
//let foo(a,b,c) = c
|
|
||||||
// let rec fac(n) = if((n:int)==0) 1 else (n*fac(n-1))
|
|
||||||
// and main(x) = x::[x+1]
|
|
||||||
// let lentr(l) = lent(0,l)
|
|
||||||
// let rec len(l) =
|
|
||||||
// switch(l) {
|
|
||||||
// | [] => 0
|
|
||||||
// | x::xs => 1+len(xs)
|
|
||||||
// }
|
|
||||||
// let lent(n,l) =
|
|
||||||
// switch (l) {
|
|
||||||
// | [] => n
|
|
||||||
// | (x::xs) => lent(n+1,xs)
|
|
||||||
// }
|
|
||||||
// let rec app(a,b) =
|
|
||||||
// switch(a) {
|
|
||||||
// | [] => b
|
|
||||||
// | (x::xs) => x::app(xs,b)
|
|
||||||
// }
|
|
||||||
// let rec revt(l,r) =
|
|
||||||
// switch(l) {
|
|
||||||
// | [] => r
|
|
||||||
// | x::xs => revt(xs,x::r)
|
|
||||||
// }
|
|
||||||
// let rev(l) = revt(l,[])
|
|
||||||
// let main(x:int) = {
|
|
||||||
// switch(rev([1,2,3])) {
|
|
||||||
// | h::_ => h
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//let fac(n:int) = {
|
|
||||||
// if (n==0) 1 else (n*fac(n-1))
|
|
||||||
//}
|
|
||||||
//let main(x) = switch((12,34)) {
|
|
||||||
//| (13,_) => x
|
|
||||||
//| (_,a) => x+a
|
|
||||||
// | y => y+1
|
|
||||||
// }
|
|
||||||
//let main(x) = ({y:0>1, x:x==0}:point(bool))
|
|
||||||
//let main(x) = x
|
|
||||||
//let main(x) = len(1::2::[])
|
|
||||||
//let main(x) = ((x,x):list('a))
|
|
||||||
// let main(x) = switch("a") {
|
|
||||||
// | "b" => 0
|
|
||||||
// | "a" => 1
|
|
||||||
// | "c" => 2
|
|
||||||
// }
|
|
||||||
//let main(x) = x.color+1
|
|
||||||
//let main(x) = switch(({x:x, y:x+1}:cp(int))) {
|
|
||||||
// | {y:xx} => xx
|
|
||||||
// }
|
|
||||||
//let main(x) = {x:0, y:1, z:2}
|
|
||||||
// let id(x) = x
|
|
||||||
// let double(x) = x+x
|
|
||||||
// let pair(x) = (1,2)
|
|
||||||
// let unit(x) = ()
|
|
||||||
// let tuples(x) = ((1,x),(2,3,4))
|
|
||||||
// let singleton(x) = [x]
|
|
||||||
// let rec seq(n) = if (n==0) [] else (app(seq(n-1),[n]))
|
|
||||||
// let idString(s:string) = s
|
|
||||||
// let pairString(s:string) = (s,s)
|
|
||||||
// let revStrings(ss:list(string))=rev(ss)
|
|
||||||
// let makePoint(x,y) = {x:x, y:y}
|
|
||||||
// let getx(x) = x.x
|
|
||||||
// let updatex(p,x) = p{x:x}
|
|
||||||
// let quad(x) = {let y=x+x; let z=y+y; z;}
|
|
||||||
// let noblock(x) = {x; x}
|
|
||||||
// let unit(x) = ()
|
|
||||||
// let foo(x) = switch (x) {
|
|
||||||
// | y => y+1
|
|
||||||
// }
|
|
||||||
// let p(x) = {color:"blue", p:{x:x, y:x+1}}
|
|
||||||
//let twice(f,x) = f(f(x))
|
|
||||||
// let twice(f,x) = f(f(x))
|
|
||||||
// let double(x) = x+x
|
|
||||||
// let main(x) = twice((y=>y+y),x)
|
|
||||||
// let rec map(f,xs) = switch(xs) {
|
|
||||||
// | [] => []
|
|
||||||
// | (x::ys) => f(x)::map(f,ys)
|
|
||||||
// }
|
|
||||||
// let id(x) = x
|
|
||||||
// let main(xs) = map(double,xs)
|
|
||||||
function z(f,x) = x
|
|
||||||
function s(n) = (f,x)=>f(n(f,x))
|
|
||||||
function add(m,n) = (f,x)=>m(f,n(f,x))
|
|
||||||
|
|
||||||
entrypoint main() =
|
contract IntegerHolder =
|
||||||
let three=s(s(s(z)))
|
type state = int
|
||||||
add(three,three)
|
entrypoint init(x) = x
|
||||||
(((i)=>i+1),0)
|
entrypoint get() = state
|
||||||
|
|
||||||
|
main contract Test =
|
||||||
|
stateful entrypoint f(c) = Chain.clone(ref=c, 123)
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
contract Remote =
|
contract interface Remote =
|
||||||
|
|
||||||
type themap = map(int, string)
|
type themap = map(int, string)
|
||||||
entrypoint foo : () => themap
|
entrypoint foo : () => themap
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
// Oracle.extend
|
// Oracle.extend
|
||||||
include "String.aes"
|
include "String.aes"
|
||||||
contract UnappliedBuiltins =
|
contract UnappliedBuiltins =
|
||||||
entrypoint main() = ()
|
entrypoint main_fun() = ()
|
||||||
type o = oracle(int, int)
|
type o = oracle(int, int)
|
||||||
type t = list(int * string)
|
type t = list(int * string)
|
||||||
type m = map(int, int)
|
type m = map(int, int)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user