Compare commits

..

1 Commits

Author SHA1 Message Date
Ulf Wiger
b116d77883 Changed deps, rename modules, remove oracles 2025-03-10 13:09:30 +01:00
52 changed files with 480 additions and 286 deletions

53
.circleci/config.yml Normal file
View File

@ -0,0 +1,53 @@
version: 2.1
executors:
aebuilder:
docker:
- image: aeternity/builder:bionic-otp24
user: builder
working_directory: ~/aesophia
jobs:
verify_rebar_lock:
executor: aebuilder
steps:
- checkout
- run:
name: Ensure lock file is up-to-date
command: |
./rebar3 upgrade
git diff --quiet -- rebar.lock || (echo "rebar.lock is not up-to-date" && exit 1)
build:
executor: aebuilder
steps:
- checkout
- restore_cache:
keys:
- dialyzer-cache-v2-{{ .Branch }}-{{ .Revision }}
- dialyzer-cache-v2-{{ .Branch }}-
- dialyzer-cache-v2-
- run:
name: Build
command: ./rebar3 compile
- run:
name: Static Analysis
command: ./rebar3 dialyzer
- run:
name: Eunit
command: ./rebar3 eunit
- run:
name: Common Tests
command: ./rebar3 ct
- save_cache:
key: dialyzer-cache-v2-{{ .Branch }}-{{ .Revision }}
paths:
- _build/default/rebar3_20.3.8_plt
- store_artifacts:
path: _build/test/logs
workflows:
version: 2
build_test:
jobs:
- build
- verify_rebar_lock

View File

@ -1,14 +0,0 @@
name: Sophia Tests
run-name: ${{ gitea.actor }} testing Sophia
on: [push, workflow_dispatch]
jobs:
tests:
runs-on: linux_amd64
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: test
run: |
. /home/act_runner/.erts/27.2.1/activate
./rebar3 eunit

21
.github/workflows/docs-develop.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: Publish development docs
on:
push:
branches: ['master']
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-python@v2
with:
python-version: 3.8
- run: pip3 install -r .github/workflows/requirements.txt -U
- run: git config --global user.email "github-action@users.noreply.github.com"
- run: git config --global user.name "GitHub Action"
- run: |
cd .docssite
mike deploy --push master

22
.github/workflows/docs-release.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Publish release docs
on:
release:
types: [released]
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-python@v2
with:
python-version: 3.8
- run: pip3 install -r .github/workflows/requirements.txt -U
- run: git config --global user.email "github-action@users.noreply.github.com"
- run: git config --global user.name "GitHub Action"
- run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV
- run: |
cd .docssite
mike deploy --push --update-aliases $RELEASE_VERSION latest

5
.github/workflows/requirements.txt vendored Normal file
View File

@ -0,0 +1,5 @@
mkdocs==1.4.2
mkdocs-simple-hooks==0.1.5
mkdocs-material==9.0.9
mike==1.1.2
pygments==2.17.2

2
.gitignore vendored
View File

@ -18,7 +18,7 @@ _build
rebar3.crashdump
*.erl~
*.aes~
sophia
aesophia
.qcci
current_counterexample.eqc
test/contracts/test.aes

View File

@ -11,12 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
### Removed
## [9.0.0]
### Changed
- stdlib dir discovery now works by finding a relative path from the loaded Sophia installation
###Removed
- Oracles
## [8.0.1]
### Changed
- Upgrade aebytecode to v3.4.1 to fix C warnings

View File

@ -1 +0,0 @@
{"src/*", [debug_info, {i, "include/"}, {outdir, "ebin/"}]}.

View File

@ -1,6 +1,5 @@
ISC License
Copyright (c) 2025, QPQ AG
Copyright (c) 2017, æternity developers
Permission to use, copy, modify, and/or distribute this software for any

View File

@ -1,11 +1,11 @@
# The Sophia smart contract language
# aesophia
This is the __sophia__ compiler which compiles contracts written in __sophia__ to [FATE](https://git.qpq.swiss/QPQ-AG/protocol/src/branch/master/contracts/fate.md) instructions.
This is the __sophia__ compiler for the æternity system which compiles contracts written in __sophia__ to [FATE](https://github.com/aeternity/protocol/blob/master/contracts/fate.md) instructions.
The compiler is currently being used three places
- [The command line compiler](https://git.qpq.swiss/QPQ-AG/sophia_cli)
- [Desktop wallet](https://git.qpq.swiss/QPQ-AG/GajuDesk)
- In the [Gajumaru core node](https://git.qpq.swiss/QPQ-AG/gajumaru) tests
- [The command line compiler](https://github.com/aeternity/aesophia_cli)
- [The HTTP compiler](https://github.com/aeternity/aesophia_http)
- In [æternity node](https://github.com/aeternity/aeternity) tests
## Documentation
@ -16,7 +16,7 @@ The compiler is currently being used three places
* [Contract examples](docs/sophia_examples.md)
* [Contributing](CONTRIBUTING.md)
Additionally you can check out the [contracts section](https://git.qpq.swiss/QPQ-AG/protocol/src/branch/master/contracts) of the Gajumaru blockchain specification.
Additionally you can check out the [contracts section](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md) of the æternity blockchain specification.
## Versioning
@ -31,5 +31,5 @@ Versioning should follow the [semantic versioning](https://semver.org/spec/v2.0.
The basic modules for interfacing the compiler:
* [so_compiler: the Sophia compiler](docs/so_compiler.md)
* [so_aci: the ACI interface](docs/so_aci.md)
* [aeso_compiler: the Sophia compiler](docs/aeso_compiler.md)
* [aeso_aci: the ACI interface](docs/aeso_aci.md)

View File

@ -1,8 +1,8 @@
# so_aci
# aeso_aci
### Module
### so_aci
### aeso_aci
The ACI interface encoder and decoder.
@ -123,7 +123,7 @@ be included inside another contract.
``` erlang
1> {ok,Contract} = file:read_file("aci_test.aes").
{ok,<<"contract Answers =\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful function"...>>}
2> {ok,JsonACI} = so_aci:contract_interface(json, Contract).
2> {ok,JsonACI} = aeso_aci:contract_interface(json, Contract).
{ok,[#{contract =>
#{functions =>
[#{arguments => [],name => <<"init">>,
@ -144,7 +144,7 @@ be included inside another contract.
vars => []}]}}]}
3> file:write_file("aci_test.aci", jsx:encode(JsonACI)).
ok
4> {ok,InterfaceStub} = so_aci:render_aci_json(JsonACI).
4> {ok,InterfaceStub} = aeso_aci:render_aci_json(JsonACI).
{ok,<<"contract Answers =\n record state = {a : Answers.answers}\n type answers = map(string, int)\n function init "...>>}
5> file:write_file("aci_test.include", InterfaceStub).
ok

View File

@ -1,8 +1,8 @@
# so_compiler
# aeso_compiler
### Module
### so_compiler
### aeso_compiler
The Sophia compiler

View File

@ -1,9 +1,12 @@
# Introduction
Sophia is a functional language designed for smart contract development.
It is strongly typed and has restricted mutable state.
Sophia is a functional language designed for smart contract development. It is strongly typed and has
restricted mutable state.
Sophia is customized for smart contracts, which can be published to a blockchain.
Thus some features of conventional languages (such as floating point arithmetic) are not present in Sophia,
and some blockchain specific primitives, constructions and types have been added.
Sophia is customized for smart contracts, which can be published
to a blockchain. Thus some features of conventional
languages, such as floating point arithmetic, are not present in Sophia, and
some [æternity blockchain](https://aeternity.com) specific primitives, constructions and types have been added.
The file extension used for Sophia source files is ".aes", reflecting Sophia's Aeternity heritage.
!!! Note
- For rapid prototyping of smart contracts check out [AEstudio](https://studio.aepps.com/)!
- For playing around and diving deeper into the language itself check out the [REPL](https://repl.aeternity.io/)!

View File

@ -65,3 +65,9 @@ contract FundMe =
amount = state.contributions[to]})
put(state{ contributions @ c = Map.delete(to, c) })
```
## Repositories
This is a list with repositories that include smart contracts written in Sophia:
- [aepp-sophia-examples](https://github.com/aeternity/aepp-sophia-examples)
- A repository that contains lots of different examples. The functionality of these examples is - to some extent - also covered by tests written in JavaScript.

View File

@ -274,6 +274,12 @@ counterpart in `Args1`.
- A map type `map(A1, A2)` is a subtype of `map(B1, B2)` if `A1` is a subtype
of `B1`, and `A2` is a subtype of `B2`.
- An oracle type `oracle(A1, A2)` is a subtype of `oracle(B1, B2)` if `B1` is
a subtype of `A1`, and `A2` is a subtype of `B2`.
- An oracle_query type `oracle_query(A1, A2)` is a subtype of `oracle_query(B1, B2)`
if `A1` is a subtype of `B1`, and `A2` is a subtype of `B2`.
- A user-defined datatype `t(Args1)` is a subtype of `t(Args2)`
- When a user-defined type `t('a)` is covariant in `'a`, then `t(A)` is a
@ -334,6 +340,10 @@ Without the `stateful` annotation the compiler does not allow the call to
* Use a stateful primitive function. These are
- `put`
- `Chain.spend`
- `Oracle.register`
- `Oracle.query`
- `Oracle.respond`
- `Oracle.extend`
- `AENS.preclaim`
- `AENS.claim`
- `AENS.transfer`
@ -546,6 +556,8 @@ Sophia has the following types:
| hash | A 32-byte hash - equivalent to `bytes(32)` | |
| signature | A signature - equivalent to `bytes(64)` | |
| Chain.ttl | Time-to-live (fixed height or relative to current block) | ```FixedTTL(1050)``` ```RelativeTTL(50)``` |
| oracle('a, 'b) | And oracle answering questions of type 'a with answers of type 'b | ```Oracle.register(acct, qfee, ttl)``` |
| oracle_query('a, 'b) | A specific oracle query | ```Oracle.query(o, q, qfee, qttl, rttl)``` |
| contract | A user defined, typed, contract address | ```function call_remote(r : RemoteContract) = r.fun()``` |
## Literals
@ -568,6 +580,8 @@ Sophia has the following types:
| hash | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` |
| signature | `sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj`, `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` |
| Chain.ttl | `FixedTTL(1050)`, `RelativeTTL(50)` |
| oracle('a, 'b) | `ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5` |
| oracle_query('a, 'b) | `oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY` |
| contract | `ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ` |
## Hole expression
@ -877,9 +891,77 @@ wrapping a transaction.) The transaction and the transaction hash is available i
`Auth.tx` and `Auth.tx_hash` respectively, they are *only* available during authentication if invoked by a
normal contract call they return `None`.
## Oracle interface
You can attach an oracle to the current contract and you can interact with oracles
through the Oracle interface.
For a full description of how Oracle works see
[Oracles](https://github.com/aeternity/protocol/blob/master/oracles/oracles.md#oracles).
For a functionality documentation refer to the [standard library](sophia_stdlib.md#oracle).
### Example
Example for an oracle answering questions of type `string` with answers of type `int`:
```sophia
contract Oracles =
stateful entrypoint registerOracle(acct : address,
sign : signature, // Signed network id + oracle address + contract address
qfee : int,
ttl : Chain.ttl) : oracle(string, int) =
Oracle.register(acct, signature = sign, qfee, ttl)
entrypoint queryFee(o : oracle(string, int)) : int =
Oracle.query_fee(o)
payable stateful entrypoint createQuery(o : oracle_query(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : int) : oracle_query(string, int) =
require(qfee =< Call.value, "insufficient value for qfee")
Oracle.query(o, q, qfee, qttl, RelativeTTL(rttl))
stateful entrypoint extendOracle(o : oracle(string, int),
ttl : Chain.ttl) : unit =
Oracle.extend(o, ttl)
stateful entrypoint signExtendOracle(o : oracle(string, int),
sign : signature, // Signed network id + oracle address + contract address
ttl : Chain.ttl) : unit =
Oracle.extend(o, signature = sign, ttl)
stateful entrypoint respond(o : oracle(string, int),
q : oracle_query(string, int),
sign : signature, // Signed network id + oracle query id + contract address
r : int) =
Oracle.respond(o, q, signature = sign, r)
entrypoint getQuestion(o : oracle(string, int),
q : oracle_query(string, int)) : string =
Oracle.get_question(o, q)
entrypoint hasAnswer(o : oracle(string, int),
q : oracle_query(string, int)) =
switch(Oracle.get_answer(o, q))
None => false
Some(_) => true
entrypoint getAnswer(o : oracle(string, int),
q : oracle_query(string, int)) : option(int) =
Oracle.get_answer(o, q)
```
### Sanity checks
When an Oracle literal is passed to a contract, no deep checks are performed.
For extra safety [Oracle.check](sophia_stdlib.md#check) and [Oracle.check_query](sophia_stdlib.md#check_query)
functions are provided.
## AENS interface
Contracts can interact with the [æternity naming system](https://git.qpq.swiss/QPQ-AG/protocol/src/branch/master/AENS.md). For this
Contracts can interact with the [æternity naming
system](https://github.com/aeternity/protocol/blob/master/AENS.md). For this
purpose the [AENS](sophia_stdlib.md#aens) and later the
[AENSv2](sophia_stdlib.md#aensv2) library was exposed.
@ -927,9 +1009,10 @@ automatically removed so they will not appear in the pointers map.
## Events
Sophia contracts log structured messages to an event log in the resulting blockchain transaction.
The event log is quite similar to [Events in Solidity](https://solidity.readthedocs.io/en/v0.4.24/contracts.html#events).
Events are further discussed in the [protocol](https://git.qpq.swiss/QPQ-AG/protocol/src/branch/master/contracts/events.md).
Sophia contracts log structured messages to an event log in the resulting
blockchain transaction. The event log is quite similar to [Events in
Solidity](https://solidity.readthedocs.io/en/v0.4.24/contracts.html#events).
Events are further discussed in the [protocol](https://github.com/aeternity/protocol/blob/master/contracts/events.md).
To use events a contract must declare a datatype `event`, and events are then
@ -949,6 +1032,8 @@ field is indexed if it fits in a 32-byte word, i.e.
- `int`
- `bits`
- `address`
- `oracle(_, _)`
- `oracle_query(_, _)`
- contract types
- `bytes(n)` for `n` ≤ 32, in particular `hash`
@ -1028,15 +1113,34 @@ however is in the gas consumption — while `abort` returns unused gas, a call t
## Delegation signature
Some chain operations (`AENSv2.<operation>`) have an
Some chain operations (`Oracle.<operation>` and `AENSv2.<operation>`) have an
optional delegation signature. This is typically used when a user/accounts
would like to allow a contract to act on it's behalf.
There are five different delegation signatures:
### From Ceres
From the Ceres protocol version the delegation signatures have more structure,
including a unique tag, `network_id` and identifiers; there are five different
delegation signatures:
- AENS wildcard - the user signs: `owner account + contract`
- `AENS_PRECLAIM` - the user signs: `owner account + contract`
- `AENS_CLAIM, AENS_UPDATE, AENS_TRANSFER, AENS_REVOKE` - the user signs: `owner account + name hash + contract`
- `ORACLE_REGISTER, ORACLE_EXTEND` - the user signs: `owner account + contract`
- `ORACLE_RESPOND` - the user signs: `query id + contract`
See [Serialized signature data](https://git.qpq.swiss/QPQ-AG/protocol/src/branch/master/serializations.md)
See [Serialized signature
data](https://github.com/aeternity/protocol/blob/master/contracts/fate.md#from-ceres-serialized-signature-data)
for the exact structure used.
### Before ceres
The exact data to be signed varies for the different operations, but in all
cases you should prepend the signature data with the `network_id` (`ae_mainnet`
for the æternity mainnet, etc.).
There are four different delegation signatures:
- `AENS_PRECLAIM` - the user signs: owner `network_id + account + contract`
- `AENS_CLAIM, AENS_UPDATE, AENS_TRANSFER, AENS_REVOKE` - the user signs: `network_id + owner account + name hash + contract`
- `ORACLE_REGISTER, ORACLE_EXTEND` - the user signs: `network_id + owner account + contract`
- `ORACLE_RESPOND` - the user signs: `network_id + query id + contract`

View File

@ -25,6 +25,7 @@ The out-of-the-box namespaces are:
- [Crypto](#crypto)
- [Int](#int)
- [Map](#map)
- [Oracle](#oracle)
The following ones need to be included as regular files with `.aes` suffix, for example
```
@ -71,6 +72,14 @@ Address.is_contract(a : address) : bool
Is the address a contract
#### is_oracle
```
Address.is_oracle(a : address) : bool
```
Is the address a registered oracle
#### is_payable
```
Address.is_payable(a : address) : bool
@ -90,7 +99,7 @@ Cast address to contract type C (where `C` is a contract)
### AENS
The old AENS namespace, kept in the compiler to be able to interact with
contracts from before Ceres, compiled using sophia compiler version 7.x and
contracts from before Ceres, compiled using aesophia compiler version 7.x and
earlier. Used in [AENSCompat](#aenscompat) when converting between old and new
pointers.
@ -105,9 +114,8 @@ datatype name = Name(address, Chain.ttl, map(string, AENS.pointee))
##### pointee
```
datatype pointee = AccountPt(address)
| ContractPt(address)
| ChannelPt(address)
datatype pointee = AccountPt(address) | OraclePt(address)
| ContractPt(address) | ChannelPt(address)
```
### AENSv2
@ -119,7 +127,7 @@ naming system (AENS). If `owner` is equal to `Contract.address` the signature
`signature` is ignored, and can be left out since it is a named argument.
Otherwise we need a signature to prove that we are allowed to do AENS
operations on behalf of `owner`. The [signature is tied to a network
id](https://git.qpq.swiss/QPQ-AG/protocol/src/branch/master/consensus/README.md#transaction-signature),
id](https://github.com/aeternity/protocol/blob/iris/consensus/consensus.md#transaction-signature),
i.e. the signature material should be prefixed by the network id.
#### Types
@ -133,10 +141,8 @@ datatype name = Name(address, Chain.ttl, map(string, AENSv2.pointee))
##### pointee
```
datatype pointee = AccountPt(address)
| ContractPt(address)
| ChannelPt(address)
| DataPt(bytes())
datatype pointee = AccountPt(address) | OraclePt(address)
| ContractPt(address) | ChannelPt(address) | DataPt(bytes())
```
Note: on-chain there is a maximum length enforced for `DataPt`, it is 1024 bytes.
@ -179,7 +185,8 @@ The [signature](./sophia_features.md#delegation-signature) should be a
serialized structure containing `network id`, `owner address`, and
`Contract.address`.
The [signature](./sophia_features.md#delegation-signature) can also be generic
From Ceres (i.e. FATE VM version 3) the
[signature](./sophia_features.md#delegation-signature) can also be generic
(allowing _all_, existing and future, names to be delegated with one
signature), i.e. containing `network id`, `owner address`, `Contract.address`.
@ -280,6 +287,7 @@ namespace Chain =
datatype ga_meta_tx = GAMetaTx(address, int)
datatype paying_for_tx = PayingForTx(address, int)
datatype base_tx = SpendTx(address, int, string)
| OracleRegisterTx | OracleQueryTx | OracleResponseTx | OracleExtendTx
| NamePreclaimTx | NameClaimTx(hash) | NameUpdateTx(string)
| NameRevokeTx(hash) | NameTransferTx(address, string)
| ChannelCreateTx(address) | ChannelDepositTx(address, int) | ChannelWithdrawTx(address, int) |
@ -297,7 +305,7 @@ Auth.tx_hash : option(hash)
Gets the transaction hash during authentication. Note: `Auth.tx_hash`
computation differs between protocol versions (changed in Ceres!), see
[aeserialisation](https://git.qpq.swiss/QPQ-AG/protocol/src/branch/master/serializations.md)
[aeserialisation](https://github.com/aeternity/protocol/blob/master/serializations.md)
specification for details.
@ -529,6 +537,7 @@ datatype paying_for_tx = PayingForTx(address, int)
##### base_tx
```
datatype base_tx = SpendTx(address, int, string)
| OracleRegisterTx | OracleQueryTx | OracleResponseTx | OracleExtendTx
| NamePreclaimTx | NameClaimTx(hash) | NameUpdateTx(string)
| NameRevokeTx(hash) | NameTransferTx(address, string)
| ChannelCreateTx(address) | ChannelDepositTx(address, int) | ChannelWithdrawTx(address, int) |
@ -935,6 +944,118 @@ Returns a list containing pairs of keys and their respective elements.
Turns a list of pairs of form `(key, value)` into a map
### Oracle
#### register
```
Oracle.register(<signature : bytes(64)>, acct : address, qfee : int, ttl : Chain.ttl) : oracle('a, 'b)
```
Registers new oracle answering questions of type `'a` with answers of type `'b`.
* The `acct` is the address of the oracle to register (can be the same as the contract).
* The [signature](./sophia_features.md#delegation-signature) should be a
serialized structure containing `network id`, `account address`, and
`contract address`. Using the private key of `account address` for signing.
Proving you have the private key of the oracle to be. If the address is the same
as the contract `sign` is ignored and can be left out entirely.
* The `qfee` is the minimum query fee to be paid by a user when asking a question of the oracle.
* The `ttl` is the Time To Live for the oracle in key blocks, either relative to the current
key block height (`RelativeTTL(delta)`) or a fixed key block height (`FixedTTL(height)`).
* The type `'a` is the type of the question to ask.
* The type `'b` is the type of the oracle answers.
Examples:
```
Oracle.register(addr0, 25, RelativeTTL(400))
Oracle.register(addr1, 25, RelativeTTL(500), signature = sign1)
```
#### get\_question
```
Oracle.get_question(o : oracle('a, 'b), q : oracle_query('a, 'b)) : 'a
```
Checks what was the question of query `q` on oracle `o`
#### respond
```
Oracle.respond(<signature : bytes(64)>, o : oracle('a, 'b), q : oracle_query('a, 'b), 'b) : unit
```
Responds to the question `q` on `o`. Unless the contract address is the same
as the oracle address the `signature` (which is an optional, named argument)
needs to be provided. Proving that we have the private key of the oracle by
[signing](./sophia_features.md#delegation-signature) should be a serialized
structure containing `network id`, `oracle query id`, and `contract address`.
#### extend
```
Oracle.extend(<signature : bytes(64)>, o : oracle('a, 'b), ttl : Chain.ttl) : unit
```
Extends TTL of an oracle.
* `singature` is a named argument and thus optional. Must be the same as for `Oracle.register`
* `o` is the oracle being extended
* `ttl` must be `RelativeTTL`. The time to live of `o` will be extended by this value.
#### query\_fee
```
Oracle.query_fee(o : oracle('a, 'b)) : int
```
Returns the query fee of the oracle
#### query
```
Oracle.query(o : oracle('a, 'b), q : 'a, qfee : int, qttl : Chain.ttl, rttl : Chain.ttl) : oracle_query('a, 'b)
```
Asks the oracle a question.
* The `qfee` is the query fee debited to the contract account (`Contract.address`).
* The `qttl` controls the last height at which the oracle can submit a response
and can be either fixed or relative.
* The `rttl` must be relative and controls how long an answer is kept on the chain.
The call fails if the oracle could expire before an answer.
#### get\_answer
```
Oracle.get_answer(o : oracle('a, 'b), q : oracle_query('a, 'b)) : option('b)
```
Checks what is the optional query answer
#### expiry
```
Oracle.expiry(o : oracle('a, 'b)) : int
```
Ask the oracle when it expires. The result is the block height at which it will happen.
#### check
```
Oracle.check(o : oracle('a, 'b)) : bool
```
Returns `true` iff the oracle `o` exists and has correct type
#### check_query
```
Oracle.check_query(o : oracle('a, 'b), q : oracle_query('a, 'b)) : bool
```
It returns `true` iff the oracle query exist and has the expected type.
## Includable namespaces
These need to be explicitly included (with `.aes` suffix)
@ -1543,7 +1664,7 @@ point where the error would exceed the `loss` value.
For debugging. If it ever returns false in a code that doesn't call `frac` constructors or
accept arbitrary `frac`s from the surface you should report it as a
[bug](https://git.qpq.swiss/QPQ-AG/sophia/issues/new)
[bug](https://github.com/aeternity/aesophia/issues/new)
If you expect getting calls with malformed `frac`s in your contract, you should use
this function to verify the input.

View File

@ -28,6 +28,8 @@ interface main using as for hiding
- `Char` character literal enclosed in `'` with escape character `\`
- `AccountAddress` base58-encoded 32 byte account pubkey with `ak_` prefix
- `ContractAddress` base58-encoded 32 byte contract address with `ct_` prefix
- `OracleAddress` base58-encoded 32 byte oracle address with `ok_` prefix
- `OracleQueryId` base58-encoded 32 byte oracle query id with `oq_` prefix
- `Signature` base58-encoded 64 byte cryptographic signature with `sg_` prefix
Valid string escape codes are
@ -44,7 +46,7 @@ Valid string escape codes are
| `\xHexDigits` | *HexDigits* | |
See the [identifier encoding scheme](https://git.qpq.swiss/QPQ-AG/protocol/src/branch/master/node/api/api_encoding.md) for the
See the [identifier encoding scheme](https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md) for the
details on the base58 literals.
## Layout blocks
@ -237,6 +239,7 @@ Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x +
| Id | Con | QId | QCon // Identifiers x, None, Map.member, AELib.Token
| Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%'
| AccountAddress | ContractAddress // Chain identifiers
| OracleAddress | OracleQueryId // Chain identifiers
| Signature // Signature
| '???' // Hole expression 1 + ???

View File

@ -3,7 +3,6 @@ namespace AENSCompat =
function pointee_to_V2(p : AENS.pointee) : AENSv2.pointee =
switch(p)
AENS.AccountPt(a) => AENSv2.AccountPt(a)
AENS.OraclePt(a) => AENSv2.OraclePt(a)
AENS.ContractPt(a) => AENSv2.ContractPt(a)
AENS.ChannelPt(a) => AENSv2.ChannelPt(a)
@ -11,7 +10,6 @@ namespace AENSCompat =
function pointee_from_V2(p2 : AENSv2.pointee) : option(AENS.pointee) =
switch(p2)
AENSv2.AccountPt(a) => Some(AENS.AccountPt(a))
AENSv2.OraclePt(a) => Some(AENS.OraclePt(a))
AENSv2.ContractPt(a) => Some(AENS.ContractPt(a))
AENSv2.ChannelPt(a) => Some(AENS.ChannelPt(a))
AENSv2.DataPt(_) => None

View File

@ -2,9 +2,7 @@
{erl_opts, [debug_info]}.
{deps, [ {gmbytecode,
{git, "https://git.qpq.swiss/QPQ-AG/gmbytecode.git",
{ref, "97cea33be8f3a35d26055664da7aa59531ff5537"}}}
{deps, [ {gmbytecode, {git, "https://git.qpq.swiss/QPQ-AG/gmbytecode.git", {ref, "cc4fd04019"}}}
, {eblake2, "1.0.0"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}}
]}.
@ -15,11 +13,10 @@
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}.
{relx, [{release, {sophia, "9.0.0"},
{relx, [{release, {sophia, "8.0.1"},
[sophia, gmbytecode]},
{dev_mode, true},
{include_erts, false},
{extended_start_script, true}]}.

View File

@ -1,30 +1,31 @@
{"1.2.0",
[{<<"gmbytecode">>,
{git,"https://git.qpq.swiss/QPQ-AG/gmbytecode.git",
{ref, "97cea33be8f3a35d26055664da7aa59531ff5537"}},
0},
{<<"gmserialization">>,
{git,"https://git.qpq.swiss/QPQ-AG/gmserialization.git",
{ref,"ac64e01b0f675c1a34c70a827062f381920742db"}},
1},
{<<"base58">>,
[{<<"base58">>,
{git,"https://git.qpq.swiss/QPQ-AG/erl-base58.git",
{ref,"e6aa62eeae3d4388311401f06e4b939bf4e94b9c"}},
2},
{<<"eblake2">>,
{git,"https://git.qpq.swiss/QPQ-AG/eblake2.git",
{ref,"b29d585b8760746142014884007eb8441a3b6a14"}},
0},
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
{<<"enacl">>,
{git,"https://git.qpq.swiss/QPQ-AG/enacl.git",
{ref,"4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}},
2},
{<<"getopt">>,
{git,"https://git.qpq.swiss/QPQ-AG/getopt.git",
{ref,"dbab6262a2430809430deda9d8650f58f9d80898"}},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},1},
{<<"gmbytecode">>,
{git,"https://git.qpq.swiss/QPQ-AG/gmbytecode.git",
{ref,"cc4fd0401903e9ce902a99160ac200e64040217f"}},
0},
{<<"gmserialization">>,
{git,"https://git.qpq.swiss/QPQ-AG/gmserialization.git",
{ref,"9d2ecc00d32ea295309563e54a81636ecb597e96"}},
1},
{<<"jsx">>,
{git,"https://github.com/talentdeficit/jsx.git",
{ref,"3074d4865b3385a050badf7828ad31490d860df5"}},
0}]}.
[
{pkg_hash,[
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]},
{pkg_hash_ext,[
{<<"eblake2">>, <<"3C4D300A91845B25D501929A26AC2E6F7157480846FAB2347A4C11AE52E08A99">>},
{<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>}]}
].

BIN
rebar3

Binary file not shown.

View File

@ -1,14 +1,13 @@
%%%-------------------------------------------------------------------
%%% @author Robert Virding
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% ACI interface
%%% @end
%%% Created : 12 Jan 2019
%%%-------------------------------------------------------------------
-module(so_aci).
-vsn("9.0.0").
-export([ file/2
, file/3

View File

@ -1,5 +1,4 @@
-module(so_ast).
-vsn("9.0.0").
-export([int/2,
line/1,

View File

@ -1,5 +1,4 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc
%%% Type checker for Sophia.
@ -12,7 +11,6 @@
%%% instances of the compiler to be run in parallel.
-module(so_ast_infer_types).
-vsn("9.0.0").
-export([ infer/1
, infer/2
@ -96,10 +94,9 @@
| {add_bytes, so_syntax:ann(), concat | split | split_any, utype(), utype(), utype()}.
-type aens_resolve_constraint() :: {aens_resolve_type, utype()}.
-type oracle_type_constraint() :: {oracle_type, so_syntax:ann(), utype()}.
-type constraint() :: named_argument_constraint() | field_constraint() | byte_constraint()
| aens_resolve_constraint() | oracle_type_constraint().
| aens_resolve_constraint().
-record(field_info,
{ ann :: so_syntax:ann()
@ -572,8 +569,6 @@ global_env() ->
Hash = {id, Ann, "hash"},
Bits = {id, Ann, "bits"},
Bytes = fun(Len) -> {bytes_t, Ann, Len} end,
Oracle = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle"}, [Q, R]} end,
Query = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle_query"}, [Q, R]} end,
Unit = {tuple_t, Ann, []},
List = fun(T) -> {app_t, Ann, {id, Ann, "list"}, [T]} end,
Option = fun(T) -> {app_t, Ann, {id, Ann, "option"}, [T]} end,
@ -615,8 +610,7 @@ global_env() ->
TxType = {record_t, [FldT(N, T) || {N, T} <- TxFlds ]},
Stateful = fun(T) -> setelement(2, T, [stateful|element(2, T)]) end,
Fee = Int,
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
[A, K, V] = lists:map(TVar, ["a", "k", "v"]),
MkDefs = fun(Defs) -> [{X, {Ann, if is_integer(T) -> {builtin, T}; true -> T end}} || {X, T} <- Defs] end,
@ -639,8 +633,7 @@ global_env() ->
{"hash", {[], {alias_t, Bytes(32)}}},
{"signature", {[], {alias_t, Bytes(64)}}},
{"bits", 0},
{"option", 1}, {"list", 1}, {"map", 2},
{"oracle", 2}, {"oracle_query", 2}
{"option", 1}, {"list", 1}, {"map", 2}
]) },
ChainScope = #scope
@ -677,10 +670,6 @@ global_env() ->
{"GAMetaTx", Fun([Address, Int], GAMetaTx)},
{"PayingForTx", Fun([Address, Int], PayForTx)},
{"SpendTx", Fun([Address, Int, String], BaseTx)},
{"OracleRegisterTx", BaseTx},
{"OracleQueryTx", BaseTx},
{"OracleResponseTx", BaseTx},
{"OracleExtendTx", BaseTx},
{"NamePreclaimTx", BaseTx},
{"NameClaimTx", Fun([String], BaseTx)},
{"NameUpdateTx", Fun([Hash], BaseTx)},
@ -719,24 +708,10 @@ global_env() ->
{"gas_left", Fun([], Int)}])
},
OracleScope = #scope
{ funs = MkDefs(
[{"register", SignFun([Address, Fee, TTL], Oracle(Q, R))},
{"expiry", Fun([Oracle(Q, R)], Fee)},
{"query_fee", Fun([Oracle(Q, R)], Fee)},
{"query", StateFun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))},
{"get_question", Fun([Oracle(Q, R), Query(Q, R)], Q)},
{"respond", SignFun([Oracle(Q, R), Query(Q, R), R], Unit)},
{"extend", SignFun([Oracle(Q, R), TTL], Unit)},
{"get_answer", Fun([Oracle(Q, R), Query(Q, R)], option_t(Ann, R))},
{"check", Fun([Oracle(Q, R)], Bool)},
{"check_query", Fun([Oracle(Q,R), Query(Q, R)], Bool)}]) },
AENSScope = #scope
{ funs = MkDefs(
[%% AENS pointee constructors
{"AccountPt", Fun1(Address, Pointee)},
{"OraclePt", Fun1(Address, Pointee)},
{"ContractPt", Fun1(Address, Pointee)},
{"ChannelPt", Fun1(Address, Pointee)},
%% Name object constructor
@ -755,7 +730,6 @@ global_env() ->
{"lookup", Fun([String], option_t(Ann, AENSNameV2))},
%% AENS pointee constructors v2
{"AccountPt", Fun1(Address, PointeeV2)},
{"OraclePt", Fun1(Address, PointeeV2)},
{"ContractPt", Fun1(Address, PointeeV2)},
{"ChannelPt", Fun1(Address, PointeeV2)},
{"DataPt", Fun1(Bytes(any), PointeeV2)},
@ -881,7 +855,6 @@ global_env() ->
AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)},
{"to_bytes", Fun1(Address, Bytes(32))},
{"to_contract", FunC(address_to_contract, [Address], A)},
{"is_oracle", Fun1(Address, Bool)},
{"is_contract", Fun1(Address, Bool)},
{"is_payable", Fun1(Address, Bool)}]) },
@ -890,7 +863,6 @@ global_env() ->
, ["Chain"] => ChainScope
, ["Contract"] => ContractScope
, ["Call"] => CallScope
, ["Oracle"] => OracleScope
, ["AENS"] => AENSScope
, ["AENSv2"] => AENSv2Scope
, ["Map"] => MapScope
@ -942,8 +914,6 @@ infer(Contracts, Options) ->
ets_insert(type_vars_variance, {"list", [covariant]}),
ets_insert(type_vars_variance, {"option", [covariant]}),
ets_insert(type_vars_variance, {"map", [covariant, covariant]}),
ets_insert(type_vars_variance, {"oracle", [contravariant, covariant]}),
ets_insert(type_vars_variance, {"oracle_query", [covariant, covariant]}),
when_warning(warn_unused_functions, fun() -> create_unused_functions() end),
check_modifiers(Env, Contracts),
@ -1551,8 +1521,6 @@ check_event_con(Env, {constr_t, Ann, Con, Args}) ->
%% Not so nice.
is_word_type({id, _, Name}) ->
lists:member(Name, ["int", "address", "hash", "bits", "bool"]);
is_word_type({app_t, _, {id, _, Name}, [_, _]}) ->
lists:member(Name, ["oracle", "oracle_query"]);
is_word_type({bytes_t, _, N}) -> N =< 32;
is_word_type({con, _, _}) -> true;
is_word_type({qcon, _, _}) -> true;
@ -1879,14 +1847,6 @@ is_first_order(Ts) when is_list(Ts) -> lists:all(fun is_first_order/1, Ts);
is_first_order(Tup) when is_tuple(Tup) -> is_first_order(tuple_to_list(Tup));
is_first_order(_) -> true.
ensure_monomorphic(Type, Err) ->
is_monomorphic(Type) orelse type_error(Err).
is_monomorphic({tvar, _, _}) -> false;
is_monomorphic(Ts) when is_list(Ts) -> lists:all(fun is_monomorphic/1, Ts);
is_monomorphic(Tup) when is_tuple(Tup) -> is_monomorphic(tuple_to_list(Tup));
is_monomorphic(_) -> true.
check_state_init(Env) ->
Top = Env#env.namespace,
StateType = lookup_type(Env, {id, [{origin, system}], "state"}),
@ -1958,14 +1918,6 @@ infer_expr(_Env, Body={account_pubkey, As, _}) ->
{typed, As, Body, {id, As, "address"}};
infer_expr(_Env, Body={signature, As, Bin}) when byte_size(Bin) == 64 ->
{typed, As, Body, {bytes_t, As, 64}};
infer_expr(_Env, Body={oracle_pubkey, As, _}) ->
Q = fresh_uvar(As),
R = fresh_uvar(As),
{typed, As, Body, {app_t, As, {id, As, "oracle"}, [Q, R]}};
infer_expr(_Env, Body={oracle_query_id, As, _}) ->
Q = fresh_uvar(As),
R = fresh_uvar(As),
{typed, As, Body, {app_t, As, {id, As, "oracle_query"}, [Q, R]}};
infer_expr(_Env, Body={contract_pubkey, As, _}) ->
Con = fresh_uvar(As),
add_constraint([#is_contract_constraint{ contract_t = Con,
@ -2067,9 +2019,6 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
when_warning(warn_negative_spend, fun() -> warn_potential_negative_spend(Ann, NewFun1, NewArgs) end),
[ add_constraint({aens_resolve_type, GeneralResultType})
|| element(3, FunName) =:= ["AENSv2", "resolve"] ],
[ add_constraint({oracle_type, Ann, OType})
|| OType <- [get_oracle_type(FunName, ArgTypes, GeneralResultType)],
OType =/= false ],
add_constraint(
#dependent_type_constraint{ named_args_t = NamedArgsVar,
named_args = NamedArgs1,
@ -2198,10 +2147,6 @@ check_valid_const_expr({account_pubkey, _, _}) ->
true;
check_valid_const_expr({signature, _, _}) ->
true;
check_valid_const_expr({oracle_pubkey, _, _}) ->
true;
check_valid_const_expr({oracle_query_id, _, _}) ->
true;
check_valid_const_expr({contract_pubkey, _, _}) ->
true;
check_valid_const_expr({id, _, "_"}) ->
@ -2775,14 +2720,10 @@ destroy_and_report_unsolved_constraints(Env) ->
({add_bytes, _, _, _, _, _}) -> true;
(_) -> false
end, OtherCs3),
{AensResolveCs, OtherCs5} =
{AensResolveCs, _OtherCs5} =
lists:partition(fun({aens_resolve_type, _}) -> true;
(_) -> false
end, OtherCs4),
{OracleTypeCs, []} =
lists:partition(fun({oracle_type, _, _}) -> true;
(_) -> false
end, OtherCs5),
check_field_constraints(Env, FieldCs),
check_record_create_constraints(Env, CreateCs),
@ -2790,19 +2731,9 @@ destroy_and_report_unsolved_constraints(Env) ->
check_named_args_constraints(Env, NamedArgCs),
check_bytes_constraints(Env, BytesCs),
check_aens_resolve_constraints(Env, AensResolveCs),
check_oracle_type_constraints(Env, OracleTypeCs),
destroy_constraints().
get_oracle_type({qid, _, ["Oracle", "register"]}, _ , OType) -> OType;
get_oracle_type({qid, _, ["Oracle", "query"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "get_question"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "get_answer"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "check"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "check_query"]}, [OType| _], _ ) -> OType;
get_oracle_type({qid, _, ["Oracle", "respond"]}, [OType| _], _ ) -> OType;
get_oracle_type(_Fun, _Args, _Ret) -> false.
%% -- Named argument constraints --
%% True if solved (unified or type error), false otherwise
@ -2914,23 +2845,10 @@ check_aens_resolve_constraints(Env, [{aens_resolve_type, Type} | Rest]) ->
{id, _, "string"} -> ok;
{id, _, "address"} -> ok;
{con, _, _} -> ok;
{app_t, _, {id, _, "oracle"}, [_, _]} -> ok;
{app_t, _, {id, _, "oracle_query"}, [_, _]} -> ok;
_ -> type_error({invalid_aens_resolve_type, so_syntax:get_ann(Type), Type2})
end,
check_aens_resolve_constraints(Env, Rest).
check_oracle_type_constraints(_Env, []) ->
ok;
check_oracle_type_constraints(Env, [{oracle_type, Ann, OType} | Rest]) ->
Type = unfold_types_in_type(Env, instantiate(OType)),
{app_t, _, {id, _, "oracle"}, [QType, RType]} = Type,
ensure_monomorphic(QType, {invalid_oracle_type, polymorphic, query, Ann, Type}),
ensure_monomorphic(RType, {invalid_oracle_type, polymorphic, response, Ann, Type}),
ensure_first_order(QType, {invalid_oracle_type, higher_order, query, Ann, Type}),
ensure_first_order(RType, {invalid_oracle_type, higher_order, response, Ann, Type}),
check_oracle_type_constraints(Env, Rest).
%% -- Field constraints --
check_record_create_constraints(_, []) -> ok;
@ -4023,15 +3941,9 @@ mk_error({higher_order_entrypoint, Ann, {id, _, Name}, Thing}) ->
mk_error({invalid_aens_resolve_type, Ann, T}) ->
Msg = io_lib:format("Invalid return type of `AENSv2.resolve`:\n"
"~s`\n"
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)",
"It must be a `string` or a pubkey type (`address`, etc)",
[pp_type(" `", T)]),
mk_t_err(pos(Ann), Msg);
mk_error({invalid_oracle_type, Why, What, Ann, Type}) ->
WhyS = case Why of higher_order -> "higher-order (contain function types)";
polymorphic -> "polymorphic (contain type variables)" end,
Msg = io_lib:format("Invalid oracle type\n~s`", [pp_type(" `", Type)]),
Cxt = io_lib:format("The ~s type must not be ~s", [What, WhyS]),
mk_t_err(pos(Ann), Msg, Cxt);
mk_error({interface_implementation_conflict, Contract, I1, I2, Fun}) ->
Msg = io_lib:format("Both interfaces `~s` and `~s` implemented by "
"the contract `~s` have a function called `~s`",

View File

@ -1,13 +1,13 @@
%%%-------------------------------------------------------------------
%%% @author Ulf Norell
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Compiler from Sophia language to Fate intermediate code.
%%% Compiler from Aeterinty Sophia language to Fate intermediate code.
%%% @end
%%% Created : 26 Mar 2019
%%%
%%%-------------------------------------------------------------------
-module(so_ast_to_fcode).
-vsn("9.0.0").
-export([ast_to_fcode/2, format_fexpr/1]).
-export_type([fcode/0, fexpr/0, fun_def/0]).
@ -53,6 +53,8 @@
| {bytes, binary()}
| {account_pubkey, binary()}
| {contract_pubkey, binary()}
| {oracle_pubkey, binary()}
| {oracle_query_id, binary()}
| {signature, binary()}
| {bool, false | true}
| {contract_code, string()} %% for CREATE, by name
@ -113,6 +115,8 @@
| address
| {bytes, non_neg_integer()}
| contract
| {oracle, ftype(), ftype()} %% Query type, Response type
| oracle_query
| name
| channel
| bits
@ -301,7 +305,7 @@ builtins() ->
{"to_any_size", 1}, {"size", 1}, {"split_any", 2}]},
{["Int"], [{"to_str", 1}, {"to_bytes", 2}, {"mulmod", 2}]},
{["Address"], [{"to_str", 1}, {"to_bytes", 1}, {"to_contract", 1},
{"is_contract", 1}, {"is_payable", 1}]}
{"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]}
],
maps:from_list([ {NS ++ [Fun], {MkName(NS, Fun), Arity}}
|| {NS, Funs} <- Scopes,
@ -329,6 +333,8 @@ init_type_env() ->
["address"] => ?type(address),
["hash"] => ?type(hash),
["signature"] => ?type(signature),
["oracle"] => ?type(Q, R, {oracle, Q, R}),
["oracle_query"] => ?type(_, _, oracle_query), %% TODO: not in Fate
["list"] => ?type(T, {list, T}),
["map"] => ?type(K, V, {map, K, V}),
["option"] => ?type(T, {variant, [[], [T]]}),
@ -596,12 +602,25 @@ expr_to_fcode(_Env, _Type, {string, Ann, S}) -> {lit, to_fann(Ann), {st
expr_to_fcode(_Env, _Type, {account_pubkey, Ann, K}) -> {lit, to_fann(Ann), {account_pubkey, K}};
expr_to_fcode(_Env, _Type, {signature, Ann, K}) -> {lit, to_fann(Ann), {signature, K}};
expr_to_fcode(_Env, _Type, {contract_pubkey, Ann, K}) -> {lit, to_fann(Ann), {contract_pubkey, K}};
expr_to_fcode(_Env, _Type, {oracle_pubkey, Ann, K}) -> {lit, to_fann(Ann), {oracle_pubkey, K}};
expr_to_fcode(_Env, _Type, {oracle_query_id, Ann, K}) -> {lit, to_fann(Ann), {oracle_query_id, K}};
expr_to_fcode(_Env, _Type, {bytes, Ann, B}) -> {lit, to_fann(Ann), {bytes, B}};
%% Variables
expr_to_fcode(Env, _Type, {id, Ann, X}) -> resolve_var(Env, Ann, [X]);
expr_to_fcode(Env, Type, {qid, Ann, X}) ->
case resolve_var(Env, Ann, X) of
{builtin_u, FAnn, B, Ar} when B =:= oracle_query;
B =:= oracle_get_question;
B =:= oracle_get_answer;
B =:= oracle_respond;
B =:= oracle_register;
B =:= oracle_check;
B =:= oracle_check_query ->
OType = get_oracle_type(B, Type),
{oracle, QType, RType} = type_to_fcode(Env, OType),
TypeArgs = [{lit, FAnn, {typerep, QType}}, {lit, FAnn, {typerep, RType}}],
{builtin_u, FAnn, B, Ar, TypeArgs};
{builtin_u, FAnn, B = aens_resolve, Ar} ->
{fun_t, _, _, _, ResType} = Type,
AensType = type_to_fcode(Env, ResType),
@ -865,6 +884,17 @@ make_tuple_fpat(Ps) -> {tuple, Ps}.
strip_singleton_tuples({tuple, _, [T]}) -> strip_singleton_tuples(T);
strip_singleton_tuples(T) -> T.
-spec get_oracle_type(OracleFun, FunT) -> OracleType when
OracleFun :: atom(),
FunT :: so_syntax:type(),
OracleType :: so_syntax:type().
get_oracle_type(oracle_register, {fun_t, _, _, _, OType}) -> OType;
get_oracle_type(oracle_query, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_get_question, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_get_answer, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_check, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_check_query, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_respond, {fun_t, _, _, [OType | _], _}) -> OType.
%% -- Pattern matching --
@ -2421,6 +2451,7 @@ pp_ftype(T) when is_atom(T) -> pp_text(T);
pp_ftype(any) -> pp_text("_");
pp_ftype({tvar, X}) -> pp_text(X);
pp_ftype({bytes, N}) -> pp_call(pp_text("bytes"), [{lit, [], {int, N}}]);
pp_ftype({oracle, Q, R}) -> pp_call_t("oracle", [Q, R]);
pp_ftype({tuple, Ts}) ->
pp_parens(pp_par(pp_punctuate(pp_text(" *"), [pp_ftype(T) || T <- Ts])));
pp_ftype({list, T}) ->

View File

@ -1,13 +1,12 @@
%%%-------------------------------------------------------------------
%%% @author Happi (Erik Stenman)
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% Compiler from Sophia language to FATE.
%%% Compiler from Aeterinty Sophia language to FATE.
%%% @end
%%% Created : 12 Dec 2017
%%%-------------------------------------------------------------------
-module(so_compiler).
-vsn("9.0.0").
-export([ file/1
, file/2

View File

@ -1,5 +1,4 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc ADT for structured error messages + formatting.
%%%
@ -7,7 +6,6 @@
%%%-------------------------------------------------------------------
-module(so_errors).
-vsn("9.0.0").
-type src_file() :: no_file | iolist().

View File

@ -1,13 +1,13 @@
%%%-------------------------------------------------------------------
%%% @author Ulf Norell
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Fate backend for Sophia compiler
%%% @end
%%% Created : 11 Jan 2019
%%%
%%%-------------------------------------------------------------------
-module(so_fcode_to_fate).
-vsn("9.0.0").
-export([compile/3, compile/4, term_to_fate/1, term_to_fate/2]).
@ -140,6 +140,8 @@ type_to_scode(string) -> string;
type_to_scode(address) -> address;
type_to_scode({bytes, N}) -> {bytes, N};
type_to_scode(contract) -> contract;
type_to_scode({oracle, _, _}) -> oracle;
type_to_scode(oracle_query) -> oracle_query;
type_to_scode(name) -> name;
type_to_scode(channel) -> channel;
type_to_scode(bits) -> bits;
@ -235,6 +237,8 @@ lit_to_fate(Env, L) ->
{account_pubkey, K} -> gmb_fate_data:make_address(K);
{signature, S} -> gmb_fate_data:make_bytes(S);
{contract_pubkey, K} -> gmb_fate_data:make_contract(K);
{oracle_pubkey, K} -> gmb_fate_data:make_oracle(K);
{oracle_query_id, H} -> gmb_fate_data:make_oracle_query(H);
{contract_code, C} -> gmb_fate_data:make_contract_bytearray(serialize_contract_code(Env, C));
{typerep, T} -> gmb_fate_data:make_typerep(type_to_scode(T))
end.

View File

@ -1,6 +1,5 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc Parser combinators for the Sophia parser. Based on
%%% Koen Claessen. 2004. Parallel Parsing Processes. J. Functional
@ -8,7 +7,6 @@
%%% @end
%%%-------------------------------------------------------------------
-module(so_parse_lib).
-vsn("9.0.0").
-export([parse/2,
return/1, fail/0, fail/1, fail/2, map/2, bind/2,

View File

@ -3,7 +3,6 @@
%%% Description :
%%% Created : 1 Mar 2018 by Ulf Norell
-module(so_parser).
-vsn("9.0.0").
-compile({no_auto_import,[map_get/2]}).
-export([string/1,

View File

@ -1,13 +1,11 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc Pretty printer for Sophia.
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(so_pretty).
-vsn("9.0.0").
-import(prettypr, [text/1, sep/1, above/2, beside/2, nest/2, empty/0]).

View File

@ -1,18 +1,16 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc The Sophia lexer.
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(so_scan).
-vsn("9.0.0").
-export([scan/1, utf8_encode/1]).
-import(so_scan_lib, [token/1, token/2, symbol/0, skip/0,
override/2, push/2, pop/1]).
override/2, push/2, pop/1]).
lexer() ->
Number = fun(Digit) -> [Digit, "+(_", Digit, "+)*"] end,

View File

@ -1,12 +1,10 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc A customisable lexer.
%%% @end
%%%-------------------------------------------------------------------
-module(so_scan_lib).
-vsn("9.0.0").
-export([compile/1, string/3,
token/1, token/2, symbol/0, skip/0,

View File

@ -1,18 +1,17 @@
%%%-------------------------------------------------------------------
%%% @author Radosław Rowicki
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Standard library for Sophia
%%% @end
%%% Created : 6 July 2019
%%%
%%%-------------------------------------------------------------------
-module(so_stdlib).
-vsn("9.0.0").
-export([stdlib_include_path/0]).
stdlib_include_path() ->
{file, BEAM} = code:is_loaded(?MODULE),
filename:join(filename:dirname(filename:dirname(BEAM)), "priv/stdlib").
filename:join([code:priv_dir(sophia), "stdlib"]).

View File

@ -1,6 +1,5 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc Sophia abstract syntax types.
%%%
@ -8,7 +7,6 @@
%%%-------------------------------------------------------------------
-module(so_syntax).
-vsn("9.0.0").
-export([get_ann/1, get_ann/2, get_ann/3, set_ann/2, qualify/2]).

View File

@ -1,12 +1,10 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc
%%% Sophia syntax utilities.
%%% @end
%%%-------------------------------------------------------------------
-module(so_syntax_utils).
-vsn("9.0.0").
-export([used_ids/1, used_ids/2, used_types/2, used/1]).

View File

@ -1,12 +1,10 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc
%%% Sophia utility functions.
%%% @end
%%%-------------------------------------------------------------------
-module(so_utils).
-vsn("9.0.0").
-export([scc/1, canonical_dir/1]).

View File

@ -1,12 +1,10 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2025, QPQ AG
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc Decoding fate data to AST
%%% @end
%%%-------------------------------------------------------------------
-module(so_vm_decode).
-vsn("9.0.0").
-export([ from_fate/2 ]).
@ -17,6 +15,8 @@ from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin};
from_fate({id, _, "signature"}, ?FATE_BYTES(Bin)) -> {signature, [], Bin};
from_fate({id, _, "hash"}, ?FATE_BYTES(Bin)) -> {bytes, [], Bin};
from_fate({id, _, "unit"}, ?FATE_UNIT) -> {tuple, [], []};
from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin};
from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin};
from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin};
from_fate({bytes_t, _, any}, ?FATE_BYTES(Bin)) -> make_any_bytes(Bin);
from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin};

View File

@ -1,5 +1,4 @@
-module(so_warnings).
-vsn("9.0.0").
-record(warn, { pos :: so_errors:pos()
, message :: iolist()

View File

@ -1,6 +1,6 @@
{application, sophia,
[{description, "Compiler for Sophia language"},
{vsn, "9.0.0"},
[{description, "Compiler for the Sophia language"},
{vsn, "8.0.1"},
{registered, []},
{applications,
[kernel,

View File

@ -2,6 +2,7 @@ contract interface Remote =
entrypoint main_fun : (int) => unit
contract AddrChain =
entrypoint is_c(a : address) =
Address.is_contract(a)

View File

@ -8,14 +8,13 @@ contract interface OldAENSContract =
main contract AENSUpdate =
stateful entrypoint update_name(owner : address, name : string, b : bytes(2)) =
let p1 : AENSv2.pointee = AENSv2.AccountPt(Call.caller)
let p2 : AENSv2.pointee = AENSv2.OraclePt(Call.caller)
let p3 : AENSv2.pointee = AENSv2.ContractPt(Call.caller)
let p4 : AENSv2.pointee = AENSv2.ChannelPt(Call.caller)
let p5 : AENSv2.pointee = AENSv2.DataPt(String.to_bytes("any something will do"))
let p6 : AENSv2.pointee = AENSv2.DataPt(Int.to_bytes(1345, 4))
let p2 : AENSv2.pointee = AENSv2.ContractPt(Call.caller)
let p3 : AENSv2.pointee = AENSv2.ChannelPt(Call.caller)
let p4 : AENSv2.pointee = AENSv2.DataPt(String.to_bytes("any something will do"))
let p5 : AENSv2.pointee = AENSv2.DataPt(Int.to_bytes(1345, 4))
AENSv2.update(owner, name, None, None,
Some({ ["account_pubkey"] = p1,
["contract_pubkey"] = p3, ["misc"] = p4, ["data"] = p5, ["data2"] = p6 }))
["contract_pubkey"] = p2, ["misc"] = p3, ["data"] = p4, ["data2"] = p5 }))
stateful entrypoint old_interaction(c : OldAENSContract, owner : address, name : string) =
let p : AENS.pointee = c.lookup(name, "key1")

View File

@ -9,7 +9,6 @@
// datatype ga_meta_tx = GAMetaTx(address, int)
// datatype paying_for_tx = PayingForTx(address, int)
// datatype base_tx = SpendTx(address, int, string)
// | OracleRegisterTx | OracleQueryTx | OracleResponseTx | OracleExtendTx
// | NamePreclaimTx | NameClaimTx(hash) | NameUpdateTx(string)
// | NameRevokeTx(hash) | NameTransferTx(address, string)
// | ChannelCreateTx(address) | ChannelDepositTx(address, int) | ChannelWithdrawTx(address, int) |
@ -42,10 +41,6 @@ contract BasicAuthTx =
fee = 123, ttl = 0, actor = Call.caller }
switch(tx0.tx)
Chain.SpendTx(receiver, amount, payload) => verify(tx_hash, n, s)
Chain.OracleRegisterTx => false
Chain.OracleQueryTx => false
Chain.OracleResponseTx => false
Chain.OracleExtendTx => false
Chain.NamePreclaimTx => false
Chain.NameClaimTx(name) => false
Chain.NameUpdateTx(name) => false

View File

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

View File

@ -1,5 +1,5 @@
// This is a custom test file if you need to run a compiler without
// changing so_compiler_tests.erl
// changing aeso_compiler_tests.erl
include "List.aes"

View File

@ -28,11 +28,11 @@ contract C =
let c16 = #000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
let c17 = #000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
let c18 = RelativeTTL(50)
let c21 = ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ : C
let c22 = N.nsconst
let c23 = c01
let c24 = c11.name
let c25 : int = 1
let c19 = ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ : C
let c20 = N.nsconst
let c21 = c01
let c22 = c11.name
let c23 : int = 1
entrypoint f01() = c01
entrypoint f02() = c02
@ -52,9 +52,9 @@ contract C =
entrypoint f16() = c16
entrypoint f17() = c17
entrypoint f18() = c18
entrypoint f19() = c19
entrypoint f20() = c20
entrypoint f21() = c21
entrypoint f22() = c22
entrypoint f23() = c23
entrypoint f24() = c24
entrypoint f25() = c25
entrypoint fqual() = C.c01

View File

@ -97,7 +97,6 @@ compilable_contracts() ->
{"funargs", "singleton_rec", ["{x = 1000}"]},
{"funargs", "aens_name", ["AENS.Name(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, RelativeTTL(100), {[\"pt1\"] = AENS.AccountPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)})"]},
{"funargs", "aens_pointee", ["AENS.AccountPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
{"funargs", "aens_pointee", ["AENS.OraclePt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
{"funargs", "aens_pointee", ["AENS.ContractPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
{"funargs", "aens_pointee", ["AENS.ChannelPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
{"funargs", "chain_ga_meta_tx", ["Chain.GAMetaTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 42)"]},
@ -105,10 +104,6 @@ compilable_contracts() ->
{"funargs", "chain_base_tx", ["Chain.SpendTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 42,\"foo\")"]},
{"funargs", "chain_base_tx", ["Chain.ContractCreateTx(12234)"]},
{"funargs", "chain_base_tx", ["Chain.ContractCallTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 12234)"]},
{"funargs", "chain_base_tx", ["Chain.OracleRegisterTx"]},
{"funargs", "chain_base_tx", ["Chain.OracleQueryTx"]},
{"funargs", "chain_base_tx", ["Chain.OracleResponseTx"]},
{"funargs", "chain_base_tx", ["Chain.OracleExtendTx"]},
{"funargs", "chain_base_tx", ["Chain.NamePreclaimTx"]},
{"funargs", "chain_base_tx", ["Chain.NameClaimTx(\"acoolname.chain\")"]},
{"funargs", "chain_base_tx", ["Chain.NameUpdateTx(#ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]},

View File

@ -464,7 +464,8 @@ failing_contracts() ->
[<<?Pos(2, 12)
"Nested contracts are not allowed. Contract `Con` is not defined at top level.">>])
, ?TYPE_ERROR(bad_address_literals,
[<<?Pos(9, 5)
[
<<?Pos(9, 5)
"Cannot unify `address` and `Remote`\n"
"when checking the type of the expression `ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address` "
"against the expected type `Remote`">>,
@ -1051,19 +1052,19 @@ failing_contracts() ->
[<<?Pos(4,5)
"Invalid return type of `AENSv2.resolve`:\n"
" `'a`\n"
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)">>
"It must be a `string` or a pubkey type (`address`, etc)">>
])
, ?TYPE_ERROR(bad_aens_resolve,
[<<?Pos(6,5)
"Invalid return type of `AENSv2.resolve`:\n"
" `list(int)`\n"
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)">>
"It must be a `string` or a pubkey type (`address`, etc)">>
])
, ?TYPE_ERROR(bad_aens_resolve_using,
[<<?Pos(7,5)
"Invalid return type of `AENSv2.resolve`:\n"
" `list(int)`\n"
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)">>
"It must be a `string` or a pubkey type (`address`, etc)">>
])
, ?TYPE_ERROR(var_args_unify_let,
[<<?Pos(3,9)

View File

@ -1,20 +0,0 @@
{name,"Sophia Compiler"}.
{type,lib}.
{modules,[]}.
{prefix,none}.
{desc,"The Sophia smart contract language for the FATE VM"}.
{author,"QPQ AG"}.
{package_id,{"otpr","sophia",{9,0,0}}}.
{deps,[{"otpr","gmbytecode",{3,4,1}},
{"otpr","getopt",{1,0,2}},
{"otpr","eblake2",{1,0,0}},
{"otpr","zj",{1,1,0}}]}.
{key_name,none}.
{a_email,[]}.
{c_email,[]}.
{copyright,"(c) 2024 QPQ AG"}.
{file_exts,[]}.
{license,skip}.
{repo_url,"https://git.qpq.swiss/QPQ-AG/sophia"}.
{tags,["gaju","gajumaru","blockchain","sophia","crypto","compiler","puck"]}.
{ws_url,[]}.

View File

@ -1,19 +0,0 @@
#! /bin/bash
# This is a small pre-packaging source generation and include correction script that should be
# run before packaging this project for use with ZX/Zomp.
rm -rf _build
cd src
for f in $(ls *.erl)
do
echo "Updating includes in: $f"
sed -i 's/gmbytecode\/include\///g' "$f"
sed -i 's/\.\.\/include\///g' "$f"
sed -i 's/include_lib/include/g' "$f"
done
sed -i 's/gmb_opcodes\.hrl/\$gmbytecode_include\/gmb_opcodes\.hrl/' so_compiler.erl
sed -i 's/gmb_fate_data\.hrl/\$gmbytecode_include\/gmb_fate_data\.hrl/' so_vm_decode.erl
cd ..
rm -f ebin/*.beam
rm -f rebar*