Docs
This commit is contained in:
parent
3ea2de8dbe
commit
ce4d1cf978
@ -94,16 +94,16 @@ To call a function in another contract you need the address to an instance of
|
||||
the contract. The type of the address must be a contract type, which consists
|
||||
of a number of type definitions and entrypoint declarations. For instance,
|
||||
|
||||
```javascript
|
||||
```sophia
|
||||
// A contract type
|
||||
contract VotingType =
|
||||
contract interface VotingType =
|
||||
entrypoint vote : string => unit
|
||||
```
|
||||
|
||||
Now given contract address of type `VotingType` you can call the `vote`
|
||||
entrypoint of that contract:
|
||||
|
||||
```javascript
|
||||
```sophia
|
||||
contract VoteTwice =
|
||||
entrypoint voteTwice(v : VotingType, alt : string) =
|
||||
v.vote(alt)
|
||||
@ -114,7 +114,7 @@ Contract calls take two optional named arguments `gas : int` and `value : int`
|
||||
that lets you set a gas limit and provide tokens to a contract call. If omitted
|
||||
the defaults are no gas limit and no tokens. Suppose there is a fee for voting:
|
||||
|
||||
```javascript
|
||||
```sophia
|
||||
entrypoint voteTwice(v : VotingType, fee : int, alt : string) =
|
||||
v.vote(value = fee, alt)
|
||||
v.vote(value = fee, alt)
|
||||
@ -136,7 +136,7 @@ To recover the underlying `address` of a contract instance there is a field
|
||||
`address : address`. For instance, to send tokens to the voting contract (given that it is payable)
|
||||
without calling it you can write
|
||||
|
||||
```javascript
|
||||
```sophia
|
||||
entrypoint pay(v : VotingType, amount : int) =
|
||||
Chain.spend(v.address, amount)
|
||||
```
|
||||
@ -154,7 +154,7 @@ If the call fails the result is `None`, otherwise it's `Some(r)` where `r` is
|
||||
the return value of the call.
|
||||
|
||||
```sophia
|
||||
contract VotingType =
|
||||
contract interface VotingType =
|
||||
entrypoint : vote : string => unit
|
||||
|
||||
contract Voter =
|
||||
@ -171,10 +171,42 @@ However, note that errors that would normally consume all the gas in the
|
||||
transaction still only uses up the gas spent running the contract.
|
||||
|
||||
|
||||
### Contract factories and child contracts
|
||||
|
||||
Since the version 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 details.
|
||||
|
||||
While `Chain.clone` requires only a `contract interface` and a living instance
|
||||
of a given contract on 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
|
||||
|
||||
Sophia does not have arbitrary mutable state, but only a limited form of
|
||||
state associated with each contract instance.
|
||||
Sophia does not have arbitrary mutable state, but only a limited form of state
|
||||
associated with each contract instance.
|
||||
|
||||
- Each contract defines a type `state` encapsulating its mutable state.
|
||||
The type `state` defaults to the `unit`.
|
||||
@ -200,7 +232,7 @@ Top-level functions and entrypoints must be annotated with the
|
||||
`stateful` keyword to be allowed to affect the state of the running contract.
|
||||
For instance,
|
||||
|
||||
```javascript
|
||||
```sophia
|
||||
stateful entrypoint set_state(s : state) =
|
||||
put(s)
|
||||
```
|
||||
@ -237,7 +269,7 @@ A concrete contract is by default *not* payable. Any attempt at spending to such
|
||||
a contract (either a `Chain.spend` or a normal spend transaction) will fail. If a
|
||||
contract shall be able to receive funds in this way it has to be declared `payable`:
|
||||
|
||||
```javascript
|
||||
```sophia
|
||||
// A payable contract
|
||||
payable contract ExampleContract =
|
||||
stateful entrypoint do_stuff() = ...
|
||||
@ -253,7 +285,7 @@ A contract entrypoint is by default *not* payable. Any call to such a function
|
||||
that has a non-zero `value` will fail. Contract entrypoints that should be called
|
||||
with a non-zero value should be declared `payable`.
|
||||
|
||||
```javascript
|
||||
```sophia
|
||||
payable stateful entrypoint buy(to : address) =
|
||||
if(Call.value > 42)
|
||||
transfer_item(to)
|
||||
@ -414,7 +446,7 @@ corresponding integer, so setting very high bits can be expensive).
|
||||
Type aliases can be introduced with the `type` keyword and can be
|
||||
parameterized. For instance
|
||||
|
||||
```
|
||||
```sophia
|
||||
type number = int
|
||||
type string_map('a) = map(string, 'a)
|
||||
```
|
||||
@ -434,7 +466,7 @@ datatype one_or_both('a, 'b) = Left('a) | Right('b) | Both('a, 'b)
|
||||
|
||||
Elements of data types can be pattern matched against, using the `switch` construct:
|
||||
|
||||
```
|
||||
```sophia
|
||||
function get_left(x : one_or_both('a, 'b)) : option('a) =
|
||||
switch(x)
|
||||
Left(x) => Some(x)
|
||||
@ -443,7 +475,7 @@ function get_left(x : one_or_both('a, 'b)) : option('a) =
|
||||
```
|
||||
|
||||
or directly in the left-hand side:
|
||||
```
|
||||
```sophia
|
||||
function
|
||||
get_left : one_or_both('a, 'b) => option('a)
|
||||
get_left(Left(x)) = Some(x)
|
||||
@ -461,7 +493,7 @@ elements of a list can be any of datatype but they must have the same
|
||||
type. The type of lists with elements of type `'e` is written
|
||||
`list('e)`. For example we can have the following lists:
|
||||
|
||||
```
|
||||
```sophia
|
||||
[1, 33, 2, 666] : list(int)
|
||||
[(1, "aaa"), (10, "jjj"), (666, "the beast")] : list(int * string)
|
||||
[{[1] = "aaa", [10] = "jjj"}, {[5] = "eee", [666] = "the beast"}] : list(map(int, string))
|
||||
@ -475,13 +507,13 @@ and returns the resulting list. So concatenating two lists
|
||||
|
||||
Sophia supports list comprehensions known from languages like Python, Haskell or Erlang.
|
||||
Example syntax:
|
||||
```
|
||||
```sophia
|
||||
[x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]]
|
||||
// yields [12,13,14,20,21,22,30,31,32]
|
||||
```
|
||||
|
||||
Lists can be constructed using the range syntax using special `..` operator:
|
||||
```
|
||||
```sophia
|
||||
[1..4] == [1,2,3,4]
|
||||
```
|
||||
The ranges are always ascending and have step equal to 1.
|
||||
@ -493,7 +525,7 @@ Please refer to the [standard library](sophia_stdlib.md#List) for the predefined
|
||||
|
||||
A Sophia record type is given by a fixed set of fields with associated,
|
||||
possibly different, types. For instance
|
||||
```
|
||||
```sophia
|
||||
record account = { name : string,
|
||||
balance : int,
|
||||
history : list(transaction) }
|
||||
@ -510,12 +542,12 @@ Please refer to the [standard library](sophia_stdlib.md#Map) for the predefined
|
||||
|
||||
A value of record type is constructed by giving a value for each of the fields.
|
||||
For the example above,
|
||||
```
|
||||
```sophia
|
||||
function new_account(name) =
|
||||
{name = name, balance = 0, history = []}
|
||||
```
|
||||
Maps are constructed similarly, with keys enclosed in square brackets
|
||||
```
|
||||
```sophia
|
||||
function example_map() : map(string, int) =
|
||||
{["key1"] = 1, ["key2"] = 2}
|
||||
```
|
||||
@ -524,7 +556,7 @@ The empty map is written `{}`.
|
||||
#### Accessing values
|
||||
|
||||
Record fields access is written `r.f` and map lookup `m[k]`. For instance,
|
||||
```
|
||||
```sophia
|
||||
function get_balance(a : address, accounts : map(address, account)) =
|
||||
accounts[a].balance
|
||||
```
|
||||
@ -549,14 +581,14 @@ in the map or execution fails, but a default value can be provided:
|
||||
`k` is not in the map.
|
||||
|
||||
Updates can be nested:
|
||||
```
|
||||
```sophia
|
||||
function clear_history(a : address, accounts : map(address, account)) : map(address, account) =
|
||||
accounts{ [a].history = [] }
|
||||
```
|
||||
This is equivalent to `accounts{ [a] @ acc = acc{ history = [] } }` and thus
|
||||
requires `a` to be present in the accounts map. To have `clear_history` create
|
||||
an account if `a` is not in the map you can write (given a function `empty_account`):
|
||||
```
|
||||
```sophia
|
||||
accounts{ [a = empty_account()].history = [] }
|
||||
```
|
||||
|
||||
@ -627,7 +659,7 @@ For a functionality documentation refer to the [standard library](sophia_stdlib.
|
||||
#### Example
|
||||
|
||||
Example for an oracle answering questions of type `string` with answers of type `int`:
|
||||
```
|
||||
```sophia
|
||||
contract Oracles =
|
||||
|
||||
stateful entrypoint registerOracle(acct : address,
|
||||
@ -698,7 +730,7 @@ an account with address `addr`. In order to allow a contract `ct` to handle
|
||||
|
||||
Armed with this information we can for example write a function that extends
|
||||
the name if it expires within 1000 blocks:
|
||||
```
|
||||
```sophia
|
||||
stateful entrypoint extend_if_necessary(addr : address, name : string, sig : signature) =
|
||||
switch(AENS.lookup(name))
|
||||
None => ()
|
||||
@ -709,7 +741,7 @@ the name if it expires within 1000 blocks:
|
||||
|
||||
And we can write functions that adds and removes keys from the pointers of the
|
||||
name:
|
||||
```
|
||||
```sophia
|
||||
stateful entrypoint add_key(addr : address, name : string, key : string,
|
||||
pt : AENS.pointee, sig : signature) =
|
||||
switch(AENS.lookup(name))
|
||||
@ -768,7 +800,7 @@ The fields can appear in any order.
|
||||
Events are emitted by using the `Chain.event` function. The following function
|
||||
will emit one Event of each kind in the example.
|
||||
|
||||
```
|
||||
```sophia
|
||||
entrypoint emit_events() : () =
|
||||
Chain.event(TheFirstEvent(42))
|
||||
Chain.event(AnotherEvent(Contract.address, "This is not indexed"))
|
||||
@ -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
|
||||
be placed in any position (and its value will end up in the `data` field), i.e.
|
||||
```
|
||||
```sophia
|
||||
AnotherEvent(string, indexed address)
|
||||
|
||||
...
|
||||
@ -837,7 +869,7 @@ and `*/` and can be nested.
|
||||
|
||||
```
|
||||
contract elif else entrypoint false function if import include let mod namespace
|
||||
private payable stateful switch true type record datatype
|
||||
private payable stateful switch true type record datatype main interface
|
||||
```
|
||||
|
||||
#### Tokens
|
||||
@ -941,7 +973,7 @@ Args ::= '(' Sep(Pattern, ',') ')'
|
||||
Contract declarations must appear at the top-level.
|
||||
|
||||
For example,
|
||||
```
|
||||
```sophia
|
||||
contract Test =
|
||||
type t = int
|
||||
entrypoint add (x : t, y : t) = x + y
|
||||
@ -1089,7 +1121,7 @@ In order of highest to lowest precedence.
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
```sophia
|
||||
/*
|
||||
* A simple crowd-funding example
|
||||
*/
|
||||
|
@ -765,6 +765,126 @@ Chain.gas_limit : int
|
||||
The gas limit of the current block.
|
||||
|
||||
|
||||
#### bytecode_hash
|
||||
```
|
||||
Chain.bytecode_hash : 'c => option(hash)
|
||||
```
|
||||
|
||||
Returns 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 is affine to the size of the serialized bytecode.
|
||||
|
||||
|
||||
#### 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 is affine to
|
||||
the size of the compiled child contract's bytecode. The `source_hash` onchain
|
||||
entry of the newly created contract will be a SHA256 hash over concatenation of
|
||||
|
||||
- whole contract source code
|
||||
- single null byte
|
||||
- name of the child 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 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.
|
||||
The resulting contract's public key can be predicted and in case it happens to
|
||||
have some funds before its creation, its balance will remain or be increased by
|
||||
the `value` parameter. **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. This operation is significantly cheaper than
|
||||
`Chain.create` as it costs a fixed amount of gas.
|
||||
|
||||
|
||||
The `gas` argument (default `Call.gas_left`) limits the gas supply for the
|
||||
`init` call of the cloned contract.
|
||||
|
||||
|
||||
The `value` argument (default `0`) is equivalent to the value in the contract
|
||||
creation transaction – it sets the initial value of the newly created contract
|
||||
charging the calling contract. Note that this won't be visible in `Call.value`
|
||||
in the `init` call of the new contract. It will be included in
|
||||
`Contract.balance`, however.
|
||||
|
||||
|
||||
The `protected` argument (default `false`) works identically as in remote calls.
|
||||
If set to `true` it will change the return type to `option('c)` and will catch
|
||||
all errors such as `abort`, out of gas and wrong arguments. Note that it can
|
||||
only take a boolean *literal*, so other expressions such as variables will be
|
||||
rejected by the compiler.
|
||||
|
||||
|
||||
The type `'c` must be instantiated with a contract.
|
||||
|
||||
|
||||
Example usage:
|
||||
|
||||
```
|
||||
payable contract interface Auction =
|
||||
entrypoint init : (int, string) => void
|
||||
stateful payable entrypoint buy : (int) => ()
|
||||
stateful entrypoint sell : (int) => ()
|
||||
|
||||
main contract Market =
|
||||
type state = list(Auction)
|
||||
entrypoint init() = []
|
||||
stateful entrypoint new_of(template : Auction, name : string) =
|
||||
switch(Chain.clone(ref=template, protected=true, 0, name))
|
||||
None => abort("Bad auction!")
|
||||
Some(new_auction) =>
|
||||
put(new_auction::state)
|
||||
```
|
||||
|
||||
When cloning by an interface, `init` entrypoint declaration is required. It is a
|
||||
good practice to set its return type to `void` in order to indicate that this
|
||||
function is not supposed to be called and is state agnostic. Trivia: internal
|
||||
implementation of the `init` function does not actually return `state`, but
|
||||
calls `put` instead. Moreover, FATE prevents even handcrafted calls to `init`.
|
||||
|
||||
|
||||
#### event
|
||||
```
|
||||
Chain.event(e : event) : unit
|
||||
|
Loading…
x
Reference in New Issue
Block a user