diff --git a/docs/sophia.md b/docs/sophia.md index 98f0a43..f3d8363 100644 --- a/docs/sophia.md +++ b/docs/sophia.md @@ -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 */ diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index a39c630..78c3cd0 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -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