Semant
Changed
Removed
Fixed
-[7.3.0]
+
Fixed
- Fixed a bug with polymorphism that allowed functions with the same name but different type to be considered as implementations for their corresponding interface function.
diff --git a/master/search/search_index.json b/master/search/search_index.json
index fa9d0e4..a5ed613 100644
--- a/master/search/search_index.json
+++ b/master/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Introduction","text":"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 \u00e6ternity blockchain specific primitives, constructions and types have been added.
Note
- For rapid prototyping of smart contracts check out AEstudio!
- For playing around and diving deeper into the language itself check out the REPL!
"},{"location":"CHANGELOG/","title":"Changelog","text":"All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
"},{"location":"CHANGELOG/#unreleased","title":"Unreleased","text":""},{"location":"CHANGELOG/#added","title":"Added","text":""},{"location":"CHANGELOG/#changed","title":"Changed","text":""},{"location":"CHANGELOG/#removed","title":"Removed","text":""},{"location":"CHANGELOG/#fixed","title":"Fixed","text":""},{"location":"CHANGELOG/#730","title":"[7.3.0]","text":""},{"location":"CHANGELOG/#fixed_1","title":"Fixed","text":" - Fixed a bug with polymorphism that allowed functions with the same name but different type to be considered as implementations for their corresponding interface function.
- Fixed a bug in the byte code optimization that incorrectly reordered dependent instructions.
"},{"location":"CHANGELOG/#721","title":"7.2.1","text":""},{"location":"CHANGELOG/#fixed_2","title":"Fixed","text":" - Fixed bugs with the newly added debugging symbols
"},{"location":"CHANGELOG/#720","title":"7.2.0","text":""},{"location":"CHANGELOG/#added_1","title":"Added","text":""},{"location":"CHANGELOG/#removed_1","title":"Removed","text":" - Remove the mapping from variables to FATE registers from the compilation output.
"},{"location":"CHANGELOG/#fixed_3","title":"Fixed","text":" - Warning about unused include when there is no include.
"},{"location":"CHANGELOG/#710","title":"7.1.0","text":""},{"location":"CHANGELOG/#added_2","title":"Added","text":""},{"location":"CHANGELOG/#changed_1","title":"Changed","text":" - Type definitions serialised to ACI as
typedefs
field instead of type_defs
to increase compatibility. - Check contracts and entrypoints modifiers when implementing interfaces.
- Contracts can no longer be used as namespaces.
- Do not show unused stateful warning for functions that call other contracts with a non-zero value argument.
"},{"location":"CHANGELOG/#fixed_4","title":"Fixed","text":" - Typechecker crashes if Chain.create or Chain.clone are used without arguments.
"},{"location":"CHANGELOG/#701","title":"7.0.1","text":""},{"location":"CHANGELOG/#added_3","title":"Added","text":" - Add CONTRIBUTING.md file.
"},{"location":"CHANGELOG/#changed_2","title":"Changed","text":" - Update Sophia syntax docs to include missing information about existing syntax.
"},{"location":"CHANGELOG/#fixed_5","title":"Fixed","text":" - 404 Contract polymorphism crashes on non-obvious child contract typing.
"},{"location":"CHANGELOG/#700","title":"7.0.0","text":""},{"location":"CHANGELOG/#added_4","title":"Added","text":" - Added support for
EXIT
opcode via exit : (string) => 'a
function (behaves same as ABORT
, but consumes all gas). - Compiler warnings for the following: shadowing, negative spends, division by zero, unused functions, unused includes, unused stateful annotations, unused variables, unused parameters, unused user-defined type, dead return value.
- The pipe operator |>
[1, 2, 3] |> List.first |> Option.is_some // Option.is_some(List.first([1, 2, 3]))\n
- Allow binary operators to be used as lambdas
function sum(l : list(int)) : int = foldl((+), 0, l)\nfunction logical_and(x, y) = (&&)(x, y)\n
- Contract interfaces polymorphism
"},{"location":"CHANGELOG/#changed_3","title":"Changed","text":" - Error messages have been restructured (less newlines) to provide more unified errors. Also
pp_oneline/1
has been added. - Ban empty record definitions (e.g.
record r = {}
would give an error).
"},{"location":"CHANGELOG/#removed_2","title":"Removed","text":" - Support for AEVM has been entirely wiped
"},{"location":"CHANGELOG/#610-2021-10-20","title":"6.1.0 - 2021-10-20","text":""},{"location":"CHANGELOG/#added_5","title":"Added","text":""},{"location":"CHANGELOG/#changed_4","title":"Changed","text":" - Fixed the ACI renderer, it shouldn't drop the
stateful
modifier
"},{"location":"CHANGELOG/#602-2021-07-05","title":"6.0.2 2021-07-05","text":""},{"location":"CHANGELOG/#changed_5","title":"Changed","text":" List.from_to_step
now forbids non-positive step (this change does not alter the behavior of the previously deployed contracts) - Fixed leaking state between contracts
"},{"location":"CHANGELOG/#601-2021-06-24","title":"6.0.1 2021-06-24","text":""},{"location":"CHANGELOG/#changed_6","title":"Changed","text":" - Fixed a bug in calldata encoding for contracts containing multiple contracts
- Fixed a missing
include
in the Frac
standard library
"},{"location":"CHANGELOG/#600-2021-05-26","title":"6.0.0 2021-05-26","text":""},{"location":"CHANGELOG/#added_6","title":"Added","text":" - Child contracts
Chain.clone
Chain.create
Chain.bytecode_hash
- Minor support for variadic functions
void
type that represents an empty type Call.fee
builtin
"},{"location":"CHANGELOG/#changed_7","title":"Changed","text":" - 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
"},{"location":"CHANGELOG/#removed_3","title":"Removed","text":""},{"location":"CHANGELOG/#500-2021-04-30","title":"5.0.0 2021-04-30","text":""},{"location":"CHANGELOG/#added_7","title":"Added","text":" There are also convenience functions split
, concat
, to_upper
, to_lower
, etc.
All String functions in FATEv2 operate on unicode code points. - Operations for pairing-based cryptography has been added the operations are in the standard library BLS12_381. With these operations it is possible to do Zero Knowledge-proofs, etc. The operations are for the BLS12-381 curve (as the name suggests). - Calls to functions in other contracts (i.e. remote calls) can now be protected
. If a contract call fails for any reason (for instance, the remote contract crashes or runs out of gas, or the entrypoint doesn't exist or has the wrong type) the parent call also fails. To make it possible to recover from failures, contract calls takes a named argument protected : bool
(default false
).
If protected = true
the result of the contract call is wrapped in an option
, and Some(value)
indicates a succesful execution and None
indicates that the contract call failed. Note: any gas consumed until the failure is still charged, but all side effects in the remote contract are rolled back on failure. - A new chain operation AENS.update
is supported. - New chain exploring operations AENS.lookup
and Oracle.expiry
to look up an AENS record and the expiry of an Oracle respectively, are added. - Transaction introspection (Auth.tx
) has been added. When a Generalized account is authorized, the authorization function needs access to the transaction (and the transaction hash) for the wrapped transaction. The transaction and the transaction hash is available Auth.tx
, it is only available during authentication if invoked by a normal contract call it returns None
. Example:
switch(Auth.tx)\n None => abort(\"Not in Auth context\")\n Some(tx0) =>\n switch(tx0.tx)\n Chain.SpendTx(_, amount, _) => amount > 400\n Chain.ContractCallTx(_, _) => true\n _ => false\n
- A debug mode is a added to the compiler. Right now its only use is to turn off hermetization."},{"location":"CHANGELOG/#changed_8","title":"Changed","text":" - The function
Chain.block_hash(height)
is now (in FATEv2) defined for the current height - this used to be an error. - Standard library: Sort is optimized to do
mergesort
and a contains
function is added. - Improved type errors and explicit errors for some syntax errors (empty code blocks, etc.).
- Compiler optimization: The ACI is generated alongside bytecode. This means that multiple compiler passes can be avoided.
- Compiler optimization: Improved parsing (less stack used when transpiled).
- A bug where constraints were handled out of order fixed.
- Fixed calldata decoding for singleton records.
- Improved the documentation w.r.t. signatures, especially stressing the fact that the network ID is a part of what is signed.
"},{"location":"CHANGELOG/#removed_4","title":"Removed","text":""},{"location":"CHANGELOG/#430","title":"4.3.0","text":""},{"location":"CHANGELOG/#added_8","title":"Added","text":" - Added documentation (moved from
protocol
) Frac.aes
\u2013 library for rational numbers - Added some more meaningful error messages
- Exported several parsing functionalities
- With option
keep_included
it is possible to see which files were included during the parse - There is a function
run_parser
that be used to evaluate any parsing rule - Exported parsers:
body
, type
and decl
"},{"location":"CHANGELOG/#changed_9","title":"Changed","text":" - Performance improvements in the standard library
- Fixed ACI encoder to handle
-
unary operator - Fixed including by absolute path
- Fixed variant type printing in the ACI error messages
- Fixed pretty printing of combined function clauses
"},{"location":"CHANGELOG/#removed_5","title":"Removed","text":" let
definitions are no longer supported in the toplevel of the contract - type declarations are no longer supported
"},{"location":"CHANGELOG/#420-2020-01-15","title":"4.2.0 - 2020-01-15","text":""},{"location":"CHANGELOG/#added_9","title":"Added","text":" - Allow separate entrypoint/function type signature and definition, and pattern matching in left-hand sides:
function\n length : list('a) => int\n length([]) = 0\n length(x :: xs) = 1 + length(xs)\n
- Allow pattern matching in list comprehension generators (filtering out match failures):
function somes(xs : list(option('a))) : list('a) =\n [ x | Some(x) <- xs ]\n
- Allow pattern matching in let-bindings (aborting on match failures):
function test(m : map(int, int)) =\n let Some(x) = Map.lookup(m, 0)\n x\n
"},{"location":"CHANGELOG/#changed_10","title":"Changed","text":" - FATE code generator improvements.
- Bug fix: Handle qualified constructors in patterns.
- Bug fix: Allow switching also on negative numbers.
"},{"location":"CHANGELOG/#removed_6","title":"Removed","text":""},{"location":"CHANGELOG/#410-2019-11-26","title":"4.1.0 - 2019-11-26","text":""},{"location":"CHANGELOG/#added_10","title":"Added","text":" - Support encoding and decoding bit fields in call arguments and results.
"},{"location":"CHANGELOG/#changed_11","title":"Changed","text":" - Various improvements to FATE code generator.
"},{"location":"CHANGELOG/#removed_7","title":"Removed","text":""},{"location":"CHANGELOG/#400-2019-10-11","title":"4.0.0 - 2019-10-11","text":""},{"location":"CHANGELOG/#added_11","title":"Added","text":" Address.to_contract
- casts an address to a (any) contract type. - Pragma to check compiler version, e.g.
@compiler >= 4.0
. - Handle numeric escapes, i.e.
\"\\x19Ethereum Signed Message:\\n\"
, and similar strings. Bytes.concat
and Bytes.split
are added to be able to (de-)construct byte arrays. [a..b]
language construct, returning the list of numbers between a
and b
(inclusive). Returns the empty list if a
> b
. - Standard libraries
- Checks that
init
is not called from other functions. - FATE backend - the compiler is able to produce VM code for both
AEVM
and FATE
. Many of the APIs now take {backend, aevm | fate}
to decide wich backend to produce artifacts for. - New builtin functions
Crypto.ecrecover_secp256k1: (hash, bytes(65)) => option(bytes(20))
and Crypto.ecverify_secp256k1 : (hash, bytes(20), bytes(65)) => bool
for recovering and verifying an Ethereum address for a message hash and a signature. - Sophia supports list comprehensions known from languages like Python, Haskell or Erlang. Example syntax:
[x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]]\n// yields [12,13,14,20,21,22,30,31,32]\n
- A new contract, and endpoint, modifier
payable
is introduced. Contracts, and enpoints, that shall be able to receive funds should be marked as payable. Address.is_payable(a)
can be used to check if an (contract) address is payable or not.
"},{"location":"CHANGELOG/#changed_12","title":"Changed","text":" - Nice type error if contract function is called as from a namespace.
- Fail on function definitions in contracts other than the main contract.
- Bug fix in variable optimization - don't discard writes to the store/state.
- Bug fixes in error reporting.
- Bug fix in variable liveness analysis for FATE.
- Error messages are changed into a uniform format, and more helpful messages have been added.
Crypto.<hash_fun>
and String.<hash_fun>
for byte arrays now only hash the actual byte array - not the internal ABI format. - More strict checks for polymorphic oracles and higher order oracles and entrypoints.
AENS.claim
is updated with a NameFee
field - to be able to do name auctions within contracts. - Fixed a bug in
Bytes.to_str
for AEVM. - New syntax for tuple types. Now 0-tuple type is encoded as
unit
instead of ()
and regular tuples are encoded by interspersing inner types with *
, for instance int * string
. Parens are not necessary. Note it only affects the types, values remain as their were before, so (1, \"a\") : int * string
- The
AENS.transfer
and AENS.revoke
functions have been updated to take a name string
instead of a name hash
. - Fixed a bug where the
AEVM
backend complained about a missing init
function when trying to generate calldata from an ACI-generated interface. - Compiler now returns the ABI-version in the compiler result map.
- Renamed
Crypto.ecverify
and Crypto.ecverify_secp256k1
into Crypto.verify_sig
and Crypto.verify_sig_secp256k1
respectively.
"},{"location":"CHANGELOG/#removed_8","title":"Removed","text":""},{"location":"CHANGELOG/#320-2019-06-28","title":"3.2.0 - 2019-06-28","text":""},{"location":"CHANGELOG/#added_12","title":"Added","text":" - New builtin function
require : (bool, string) => ()
. Defined as function require(b, err) = if(!b) abort(err)\n
- New builtin functions
Bytes.to_str : bytes(_) => string\nBytes.to_int : bytes(_) => int\n
for converting a byte array to a hex string and interpreting it as a big-endian encoded integer respectively.
"},{"location":"CHANGELOG/#changed_13","title":"Changed","text":""},{"location":"CHANGELOG/#removed_9","title":"Removed","text":""},{"location":"CHANGELOG/#310-2019-06-03","title":"3.1.0 - 2019-06-03","text":""},{"location":"CHANGELOG/#added_13","title":"Added","text":""},{"location":"CHANGELOG/#changed_14","title":"Changed","text":" - Keyword
indexed
is now optional for word typed (bool
, int
, address
, ...) event arguments. - State variable pretty printing now produce
'a, 'b, ...
instead of '1, '2, ...
. - ACI is restructured and improved:
state
and event
types (if present) now appear at the top level. - Namespaces and remote interfaces are no longer ignored.
- All type definitions are included in the interface rendering.
- API functions are renamed, new functions are
contract_interface
and render_aci_json
.
- Fixed a bug in
create_calldata
/to_sophia_value
- it can now handle negative literals.
"},{"location":"CHANGELOG/#removed_10","title":"Removed","text":""},{"location":"CHANGELOG/#300-2019-05-21","title":"3.0.0 - 2019-05-21","text":""},{"location":"CHANGELOG/#added_14","title":"Added","text":" stateful
annotations are now properly enforced. Functions must be marked stateful in order to update the state or spend tokens. - Primitives
Contract.creator
, Address.is_contract
, Address.is_oracle
, Oracle.check
and Oracle.check_query
has been added to Sophia. - A byte array type
bytes(N)
has been added to generalize hash (== bytes(32))
and signature (== bytes(64))
and allow for byte arrays of arbitrary fixed length. Crypto.ecverify_secp256k1
has been added.
"},{"location":"CHANGELOG/#changed_15","title":"Changed","text":" - Address literals (+ Oracle, Oracle query and remote contracts) have been changed from
#<hex>
to address as ak_<base58check>
, oracle ok_<base58check>
, oracle query oq_<base58check>
and remote contract ct_<base58check>
. - The compilation and typechecking of
letfun
(e.g. let m(f, xs) = map(f, xs)
) was not working properly and has been fixed.
"},{"location":"CHANGELOG/#removed_11","title":"Removed","text":" let rec
has been removed from the language, it has never worked. - The standalone CLI compiler is served in the repo
aeternity/aesophia_cli
and has been completely removed from aesophia
.
"},{"location":"CHANGELOG/#210-2019-04-11","title":"2.1.0 - 2019-04-11","text":""},{"location":"CHANGELOG/#added_15","title":"Added","text":" - Stubs (not yet wired up) for compilation to FATE
- Add functions specific for Calldata decoding
- Support for
Auth.tx_hash
, not available in AEVM until Fortuna release
"},{"location":"CHANGELOG/#changed_16","title":"Changed","text":" - Improvements to the ACI generator
"},{"location":"CHANGELOG/#200-2019-03-11","title":"2.0.0 - 2019-03-11","text":""},{"location":"CHANGELOG/#added_16","title":"Added","text":" - Add
Crypto.ecverify
to the compiler. - Add
Crypto.sha3
, Crypto.blake2
, Crypto.sha256
, String.blake2
and String.sha256
to the compiler. - Add the
bits
type for working with bit fields in Sophia. - Add Namespaces to Sophia in order to simplify using library contracts, etc.
- Add a missig type check on the
init
function - detects programmer errors earlier. - Add the ACI (Aeternity Contract Interface) generator.
"},{"location":"CHANGELOG/#changed_17","title":"Changed","text":" - Use native bit shift operations in builtin functions, reducing gas cost.
- Improve type checking of
record
fields - generates more understandable error messages. - Improved, more coherent, error messages.
- Simplify calldata creation - instead of passing a compiled contract, simply pass a (stubbed) contract string.
"},{"location":"aeso_aci/","title":"aeso_aci","text":""},{"location":"aeso_aci/#module","title":"Module","text":""},{"location":"aeso_aci/#aeso_aci_1","title":"aeso_aci","text":"The ACI interface encoder and decoder.
"},{"location":"aeso_aci/#description","title":"Description","text":"This module provides an interface to generate and convert between Sophia contracts and a suitable JSON encoding of contract interface. As yet the interface is very basic.
Encoding this contract:
contract Answers =\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful function init() = { a = {} }\n private function the_answer() = 42\n function new_answer(q : string, a : int) : answers() = { [q] = a }\n
generates the following JSON structure representing the contract interface:
{\n\"contract\": {\n\"functions\": [\n{\n\"arguments\": [],\n\"name\": \"init\",\n\"returns\": \"Answers.state\",\n\"stateful\": true\n},\n{\n\"arguments\": [\n{\n\"name\": \"q\",\n\"type\": \"string\"\n},\n{\n\"name\": \"a\",\n\"type\": \"int\"\n}\n],\n\"name\": \"new_answer\",\n\"returns\": {\n\"map\": [\n\"string\",\n\"int\"\n]\n},\n\"stateful\": false\n}\n],\n\"name\": \"Answers\",\n\"state\": {\n\"record\": [\n{\n\"name\": \"a\",\n\"type\": \"Answers.answers\"\n}\n]\n},\n\"typedefs\": [\n{\n\"name\": \"answers\",\n\"typedef\": {\n\"map\": [\n\"string\",\n\"int\"\n]\n},\n\"vars\": []\n}\n]\n}\n}\n
When that encoding is decoded the following include definition is generated:
contract Answers =\n record state = {a : Answers.answers}\n type answers = map(string, int)\n function init : () => Answers.state\n function new_answer : (string, int) => map(string, int)\n
"},{"location":"aeso_aci/#types","title":"Types","text":"-type aci_type() :: json | string.\n-type json() :: jsx:json_term().\n-type json_text() :: binary().\n
"},{"location":"aeso_aci/#exports","title":"Exports","text":""},{"location":"aeso_aci/#contract_interfaceaci_type-string-ok-json-string-error-term","title":"contract_interface(aci_type(), string()) -> {ok, json() | string()} | {error, term()}","text":"Generate the JSON encoding of the interface to a contract. The type definitions and non-private functions are included in the JSON string.
"},{"location":"aeso_aci/#render_aci_jsonjson-json_text-string","title":"render_aci_json(json() | json_text()) -> string().","text":"Take a JSON encoding of a contract interface and generate a contract interface that can be included in another contract.
"},{"location":"aeso_aci/#example-run","title":"Example run","text":"This is an example of using the ACI generator from an Erlang shell. The file called aci_test.aes
contains the contract in the description from which we want to generate files aci_test.json
which is the JSON encoding of the contract interface and aci_test.include
which is the contract definition to be included inside another contract.
1> {ok,Contract} = file:read_file(\"aci_test.aes\").\n{ok,<<\"contract Answers =\\n record state = { a : answers }\\n type answers() = map(string, int)\\n\\n stateful function\"...>>}\n2> {ok,JsonACI} = aeso_aci:contract_interface(json, Contract).\n{ok,[#{contract =>\n#{functions =>\n[#{arguments => [],name => <<\"init\">>,\nreturns => <<\"Answers.state\">>,stateful => true},\n#{arguments =>\n[#{name => <<\"q\">>,type => <<\"string\">>},\n#{name => <<\"a\">>,type => <<\"int\">>}],\nname => <<\"new_answer\">>,\nreturns => #{<<\"map\">> => [<<\"string\">>,<<\"int\">>]},\nstateful => false}],\nname => <<\"Answers\">>,\nstate =>\n#{record =>\n[#{name => <<\"a\">>,type => <<\"Answers.answers\">>}]},\ntypedefs =>\n[#{name => <<\"answers\">>,\ntypedef => #{<<\"map\">> => [<<\"string\">>,<<\"int\">>]},\nvars => []}]}}]}\n3> file:write_file(\"aci_test.aci\", jsx:encode(JsonACI)).\nok\n4> {ok,InterfaceStub} = aeso_aci:render_aci_json(JsonACI).\n{ok,<<\"contract Answers =\\n record state = {a : Answers.answers}\\n type answers = map(string, int)\\n function init \"...>>}\n5> file:write_file(\"aci_test.include\", InterfaceStub).\nok\n6> jsx:prettify(jsx:encode(JsonACI)).\n<<\"[\\n {\\n \\\"contract\\\": {\\n \\\"functions\\\": [\\n {\\n \\\"arguments\\\": [],\\n \\\"name\\\": \\\"init\\\",\\n \"...>>\n
The final call to jsx:prettify(jsx:encode(JsonACI))
returns the encoding in a more easily readable form. This is what is shown in the description above.
"},{"location":"aeso_compiler/","title":"aeso_compiler","text":""},{"location":"aeso_compiler/#module","title":"Module","text":""},{"location":"aeso_compiler/#aeso_compiler_1","title":"aeso_compiler","text":"The Sophia compiler
"},{"location":"aeso_compiler/#description","title":"Description","text":"This module provides the interface to the standard Sophia compiler. It returns the compiled module in a map which can then be loaded.
"},{"location":"aeso_compiler/#types","title":"Types","text":"contract_string() = string() | binary()\ncontract_map() = #{bytecode => binary(),\ncompiler_version => binary(),\ncontract_souce => string(),\ntype_info => type_info()}\ntype_info()\nerrorstring() = binary()\n
"},{"location":"aeso_compiler/#exports","title":"Exports","text":""},{"location":"aeso_compiler/#filefile","title":"file(File)","text":""},{"location":"aeso_compiler/#filefile-options-compret","title":"file(File, Options) -> CompRet","text":""},{"location":"aeso_compiler/#from_stringcontractstring-options-compret","title":"from_string(ContractString, Options) -> CompRet","text":"Types
ContractString = contract_string()\nOptions = [Option]\nCompRet = {ok,ContractMap} | {error,ErrorString}\nContractMap = contract_map()\nErrorString = errorstring()\n
Compile a contract defined in a file or in a string.
The pp_ options all print to standard output the following:
pp_sophia_code
- print the input Sophia code.
pp_ast
- print the AST of the code
pp_types
- print information about the types
pp_typed_ast
- print the AST with type information at each node
pp_assembler
- print the generated assembler code
The option include_child_contract_symbols
includes the symbols of child contracts functions in the generated fate code. It is turned off by default to avoid making contracts bigger on chain.
"},{"location":"aeso_compiler/#options-to-control-which-compiler-optimizations-should-run","title":"Options to control which compiler optimizations should run:","text":"By default all optimizations are turned on, to disable an optimization, it should be explicitly set to false and passed as a compiler option.
List of optimizations:
- optimize_inliner
- optimize_inline_local_functions
- optimize_bind_subexpressions
- optimize_let_floating
- optimize_simplifier
- optimize_drop_unused_lets
- optimize_push_consume
- optimize_one_shot_var
- optimize_write_to_dead_var
- optimize_inline_switch_target
- optimize_swap_push
- optimize_swap_pop
- optimize_swap_write
- optimize_constant_propagation
- optimize_prune_impossible_branches
- optimize_single_successful_branch
- optimize_inline_store
- optimize_float_switch_bod
"},{"location":"aeso_compiler/#check_callcontractstring-options-checkret","title":"check_call(ContractString, Options) -> CheckRet","text":"Types
ContractString = string() | binary()\nCheckRet = {ok,string(),{Types,Type | any()},Terms} | {error,Term}\nTypes = [Type]\nType = term()\n
Check a call in contract through the __call
function."},{"location":"aeso_compiler/#version-ok-version-error-term","title":"version() -> {ok, Version} | {error, term()}","text":"Types
Version = binary()\n
Get the current version of the Sophia compiler.
"},{"location":"sophia/","title":"Sophia","text":"This file has been moved here
"},{"location":"sophia_examples/","title":"Contract examples","text":""},{"location":"sophia_examples/#crowdfunding","title":"Crowdfunding","text":"/*\n * A simple crowd-funding example\n */\ncontract FundMe =\n\nrecord spend_args = { recipient : address,\namount : int }\n\nrecord state = { contributions : map(address, int),\ntotal : int,\nbeneficiary : address,\ndeadline : int,\ngoal : int }\n\nstateful function spend(args : spend_args) =\nChain.spend(args.recipient, args.amount)\n\nentrypoint init(beneficiary, deadline, goal) : state =\n{ contributions = {},\nbeneficiary = beneficiary,\ndeadline = deadline,\ntotal = 0,\ngoal = goal }\n\nfunction is_contributor(addr) =\nMap.member(addr, state.contributions)\n\nstateful entrypoint contribute() =\nif(Chain.block_height >= state.deadline)\nspend({ recipient = Call.caller, amount = Call.value }) // Refund money\nfalse\nelse\nlet amount =\nswitch(Map.lookup(Call.caller, state.contributions))\nNone => Call.value\nSome(n) => n + Call.value\nput(state{ contributions[Call.caller] = amount,\ntotal @ tot = tot + Call.value })\ntrue\n\nstateful entrypoint withdraw() =\nif(Chain.block_height < state.deadline)\nabort(\"Cannot withdraw before deadline\")\nif(Call.caller == state.beneficiary)\nwithdraw_beneficiary()\nelif(is_contributor(Call.caller))\nwithdraw_contributor()\nelse\nabort(\"Not a contributor or beneficiary\")\n\nstateful function withdraw_beneficiary() =\nrequire(state.total >= state.goal, \"Project was not funded\")\nspend({recipient = state.beneficiary,\namount = Contract.balance })\n\nstateful function withdraw_contributor() =\nif(state.total >= state.goal)\nabort(\"Project was funded\")\nlet to = Call.caller\nspend({recipient = to,\namount = state.contributions[to]})\nput(state{ contributions @ c = Map.delete(to, c) })\n
"},{"location":"sophia_examples/#repositories","title":"Repositories","text":"This is a list with repositories that include smart contracts written in Sophia:
- 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.
"},{"location":"sophia_features/","title":"Features","text":""},{"location":"sophia_features/#contracts","title":"Contracts","text":"The main unit of code in Sophia is the contract.
- A contract implementation, or simply a contract, is the code for a smart contract and consists of a list of types, entrypoints and local functions. Only the entrypoints can be called from outside the contract.
- A contract instance is an entity living on the block chain (or in a state channel). Each instance has an address that can be used to call its entrypoints, either from another contract or in a call transaction.
- A contract may define a type
state
encapsulating its local state. When creating a new contract the init
entrypoint is executed and the state is initialized to its return value.
The language offers some primitive functions to interact with the blockchain and contracts. Please refer to the Chain, Contract and the Call namespaces in the documentation.
"},{"location":"sophia_features/#calling-other-contracts","title":"Calling other contracts","text":"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,
// A contract type\ncontract interface VotingType =\nentrypoint vote : string => unit\n
Now given contract address of type VotingType
you can call the vote
entrypoint of that contract:
contract VoteTwice =\nentrypoint voteTwice(v : VotingType, alt : string) =\nv.vote(alt)\nv.vote(alt)\n
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:
entrypoint voteTwice(v : VotingType, fee : int, alt : string) =\nv.vote(value = fee, alt)\nv.vote(value = fee, alt)\n
Named arguments can be given in any order.
Note that reentrant calls are not permitted. In other words, when calling another contract it cannot call you back (directly or indirectly).
To construct a value of a contract type you can give a contract address literal (for instance ct_2gPXZnZdKU716QBUFKaT4VdBZituK93KLvHJB3n4EnbrHHw4Ay
), or convert an account address to a contract address using Address.to_contract
. Note that if the contract does not exist, or it doesn't have the entrypoint, or the type of the entrypoint does not match the stated contract type, the call fails.
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
entrypoint pay(v : VotingType, amount : int) =\nChain.spend(v.address, amount)\n
"},{"location":"sophia_features/#protected-contract-calls","title":"Protected contract calls","text":"If a contract call fails for any reason (for instance, the remote contract crashes or runs out of gas, or the entrypoint doesn't exist or has the wrong type) the parent call also fails. To make it possible to recover from failures, contract calls takes a named argument protected : bool
(default false
).
The protected argument must be a literal boolean, and when set to true
changes the type of the contract call, wrapping the result in an option
type. If the call fails the result is None
, otherwise it's Some(r)
where r
is the return value of the call.
contract interface VotingType =\nentrypoint : vote : string => unit\n\ncontract Voter =\nentrypoint tryVote(v : VotingType, alt : string) =\nswitch(v.vote(alt, protected = true) : option(unit))\nNone => \"Voting failed\"\nSome(_) => \"Voting successful\"\n
Any gas that was consumed by the contract call before the failure stays consumed, which means that in order to protect against the remote contract running out of gas it is necessary to set a gas limit using the gas
argument. However, note that errors that would normally consume all the gas in the transaction still only uses up the gas spent running the contract.
Any side effects (state change, token transfers, etc.) made by a failing protected call is rolled back, just like they would be in the unprotected case.
"},{"location":"sophia_features/#contract-factories-and-child-contracts","title":"Contract factories and child contracts","text":"Since the version 6.0.0 Sophia supports deploying contracts by other contracts. This can be done in two ways:
- Contract cloning via
Chain.clone
- Direct deploy via
Chain.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 \u2013 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 =\ntype state = int\nentrypoint init(x) = x\nentrypoint get() = state\n\nmain contract IntHolderFactory =\nstateful entrypoint new(x : int) : IntHolder =\nlet ih = Chain.create(x) : IntHolder\nih\n
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.
"},{"location":"sophia_features/#contract-interfaces-and-polymorphism","title":"Contract interfaces and polymorphism","text":"Contracts can implement one or multiple interfaces, the contract has to define every entrypoint from the implemented interface and the entrypoints in both the contract and implemented interface should have compatible types.
contract interface Animal =\n entrypoint sound : () => string\n\n contract Cat : Animal =\n entrypoint sound() = \"Cat sound\"\n
Contract interfaces can extend other interfaces. An extended interface has to declare all entrypoints from every parent interface. All the declarations in the extended interface must have types compatible with the declarations from the parent interface.
contract interface II =\n entrypoint f : () => unit\n\ncontract interface I : II =\n entrypoint f : () => unit\n entrypoint g : () => unit\n\ncontract C : I =\n entrypoint f() = ()\n entrypoint g() = ()\n
It is only possible to implement (or extend) an interface that has been already defined earlier in the file (or in an included file). Therefore recursive interface implementation is not allowed in Sophia.
// The following code would show an error\n\ncontract interface X : Z =\n entrypoint x : () => int\n\n contract interface Y : X =\n entrypoint x : () => int\n entrypoint y : () => int\n\n contract interface Z : Y =\n entrypoint x : () => int\n entrypoint y : () => int\n entrypoint z : () => int\n\n contract C : Z =\n entrypoint x() = 1\n entrypoint y() = 1\n entrypoint z() = 1\n
"},{"location":"sophia_features/#adding-or-removing-modifiers","title":"Adding or removing modifiers","text":"When a contract
or a contract interface
implements another contract interface
, the payable
and stateful
modifiers can be kept or changed, both in the contract and in the entrypoints, according to the following rules:
- A
payable
contract or interface can implement a payable
interface or a non-payable
interface. - A non-
payable
contract or interface can only implement a non-payable
interface, and cannot implement a payable
interface. - A
payable
entrypoint can implement a payable
entrypoint or a non-payable
entrypoint. - A non-
payable
entrypoint can only implement a non-payable
entrypoint, and cannot implement a payable
entrypoint. - A non-
stateful
entrypoint can implement a stateful
entrypoint or a non-stateful
entrypoint. - A
stateful
entrypoint can only implement a stateful
entrypoint, and cannot implement a non-stateful
entrypoint.
"},{"location":"sophia_features/#subtyping-and-variance","title":"Subtyping and variance","text":"Subtyping in Sophia follows common rules that take type variance into account. As described by Wikipedia,
Variance refers to how subtyping between more complex types relates to subtyping between their components.
This concept plays an important role in complex types such as tuples, datatype
s and functions. Depending on the context, it can apply to positions in the structure of a type, or type parameters of generic types. There are four kinds of variances:
- covariant
- contravariant
- invariant
- bivariant
A type is said to be on a \"covariant\" position when it describes output or a result of some computation. Analogously, position is \"contravariant\" when it is an input, or a parameter. Intuitively, when a part of the type is produced by values of it, it is covariant. When it is consumed, it is contravariant. When a type appears to be simultaneously input and output, it is described as invariant. If a type is neither of those (that is, it's unused) it's bivariant. Furthermore, whenever a complex type appears on a contravariant position, all its covariant components become contravariant and vice versa.
Variance influences how subtyping is applied. Types on covariant positions are subtyped normally, while contravariant the opposite way. Invariant types have to be exactly the same in order for subtyping to work. Bivariant types are always compatible.
A good example of where it matters can be pictured by subtyping of function types. Let us assume there is a contract interface Animal
and two contracts that implement it: Dog
and Cat
.
contract interface Animal =\nentrypoint age : () => int\n\ncontract Dog : Animal =\nentrypoint age() = // ...\nentrypoint woof() = \"woof\"\n\ncontract Cat : Animal =\nentrypoint age() = // ...\nentrypoint meow() = \"meow\"\n
The assumption of this exercise is that cats do not bark (because Cat
does not define the woof
entrypoint). If subtyping rules were applied naively, that is if we let Dog => Dog
be a subtype of Animal => Animal
, the following code would break:
let f : (Dog) => string = d => d.woof()\nlet g : (Animal) => string = f\nlet c : Cat = Chain.create()\ng(c) // Cat barking!\n
That is because arguments of functions are contravariant, as opposed to return the type which is covariant. Because of that, the assignment of f
to g
is invalid - while Dog
is a subtype of Animal
, Dog => string
is not a subtype of Animal => string
. However, Animal => string
is a subtype of Dog => string
. More than that, (Dog => Animal) => Dog
is a subtype of (Animal => Dog) => Animal
.
This has consequences on how user-defined generic types work. A type variable gains its variance from its role in the type definition as shown in the example:
datatype co('a) = Co('a) // co is covariant on 'a\ndatatype ct('a) = Ct('a => unit) // ct is contravariant on 'a\ndatatype in('a) = In('a => 'a) // in is invariant on 'a\ndatatype bi('a) = Bi // bi is bivariant on 'a\n
The following facts apply here:
co('a)
is a subtype of co('b)
when 'a
is a subtype of 'b
ct('a)
is a subtype of ct('b)
when 'b
is a subtype of 'a
in('a)
is a subtype of in('b)
when 'a
is equal to 'b
bi('a)
is a subtype of bi('b)
always
That altogether induce the following rules of subtyping in Sophia:
-
A function type (Args1) => Ret1
is a subtype of (Args2) => Ret2
when Ret1
is a subtype of Ret2
and each argument type from Args2
is a subtype of its counterpart in Args1
.
-
A list type list(A)
is a subtype of list(B)
if A
is a subtype of B
.
-
An option type option(A)
is a subtype of option(B)
if A
is a subtype of B
.
-
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 subtype of t(B)
when A
is a subtype of B
.
-
When a user-defined type t('a)
is contravariant in 'a
, then t(A)
is a subtype of t(B)
when B
is a subtype of A
.
-
When a user-defined type t('a)
is binvariant in 'a
, then t(A)
is a subtype of t(B)
when either A
is a subtype of B
or when B
is a subtype of A
.
-
When a user-defined type t('a)
is invariant in 'a
, then t(A)
can never be a subtype of t(B)
.
"},{"location":"sophia_features/#mutable-state","title":"Mutable state","text":"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
. - The initial state of a contract is computed by the contract's
init
function. The init
function is pure and returns the initial state as its return value. If the type state
is unit
, the init
function defaults to returning the value ()
. At contract creation time, the init
function is executed and its result is stored as the contract state. - The value of the state is accessible from inside the contract through an implicitly bound variable
state
. - State updates are performed by calling a function
put : state => unit
. - Aside from the
put
function (and similar functions for transactions and events), the language is purely functional. - Functions modifying the state need to be annotated with the
stateful
keyword (see below).
To make it convenient to update parts of a deeply nested state Sophia provides special syntax for map/record updates.
"},{"location":"sophia_features/#stateful-functions","title":"Stateful functions","text":"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,
stateful entrypoint set_state(s : state) =\nput(s)\n
Without the stateful
annotation the compiler does not allow the call to put
. A stateful
annotation is required 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
AENS.revoke
AENS.update
- Call a
stateful
function in the current contract - Call another contract with a non-zero
value
argument.
A stateful
annotation is not required to
- Read the contract state.
- Issue an event using the
event
function. - Call another contract with
value = 0
, even if the called function is stateful.
"},{"location":"sophia_features/#payable","title":"Payable","text":""},{"location":"sophia_features/#payable-contracts","title":"Payable contracts","text":"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
:
// A payable contract\npayable contract ExampleContract =\nstateful entrypoint do_stuff() = ...\n
If in doubt, it is possible to check if an address is payable using Address.is_payable(addr)
.
"},{"location":"sophia_features/#payable-entrypoints","title":"Payable entrypoints","text":"A contract entrypoint is by default not payable. Any call to such a function (either a Remote call or a contract call transaction) that has a non-zero value
will fail. Contract entrypoints that should be called with a non-zero value should be declared payable
.
payable stateful entrypoint buy(to : address) =\nif(Call.value > 42)\ntransfer_item(to)\nelse\nabort(\"Value too low\")\n
"},{"location":"sophia_features/#namespaces","title":"Namespaces","text":"Code can be split into libraries using the namespace
construct. Namespaces can appear at the top-level and can contain type and function definitions, but not entrypoints. Outside the namespace you can refer to the (non-private) names by qualifying them with the namespace (Namespace.name
). For example,
namespace Library =\ntype number = int\nfunction inc(x : number) : number = x + 1\n\ncontract MyContract =\nentrypoint plus2(x) : Library.number =\nLibrary.inc(Library.inc(x))\n
Functions in namespaces have access to the same environment (including the Chain
, Call
, and Contract
, builtin namespaces) as function in a contract, with the exception of state
, put
and Chain.event
since these are dependent on the specific state and event types of the contract.
To avoid mentioning the namespace every time it is used, Sophia allows including the namespace in the current scope with the using
keyword:
include \"Pair.aes\"\nusing Pair\ncontract C =\n type state = int\n entrypoint init() =\n let p = (1, 2)\n fst(p) // this is the same as Pair.fst(p)\n
It is also possible to make an alias for the namespace with the as
keyword:
include \"Pair.aes\"\ncontract C =\n using Pair as P\n type state = int\n entrypoint init() =\n let p = (1, 2)\n P.fst(p) // this is the same as Pair.fst(p)\n
Having the same alias for multiple namespaces is possible and it allows referening functions that are defined in different namespaces and have different names with the same alias:
namespace Xa = function f() = 1\nnamespace Xb = function g() = 2\ncontract Cntr =\n using Xa as A\n using Xb as A\n type state = int\n entrypoint init() = A.f() + A.g()\n
Note that using functions with the same name would result in an ambiguous name error:
namespace Xa = function f() = 1\nnamespace Xb = function f() = 2\ncontract Cntr =\n using Xa as A\n using Xb as A\n type state = int\n\n // the next line has an error because f is defined in both Xa and Xb\n entrypoint init() = A.f()\n
Importing specific parts of a namespace or hiding these parts can also be done like this:
using Pair for [fst, snd] // this will only import fst and snd\nusing Triple hiding [fst, snd] // this will import everything except for fst and snd\n
Note that it is possible to use a namespace in the top level of the file, in the contract level, namespace level, or in the function level.
"},{"location":"sophia_features/#splitting-code-over-multiple-files","title":"Splitting code over multiple files","text":"Code from another file can be included in a contract using an include
statement. These must appear at the top-level (outside the main contract). The included file can contain one or more namespaces and abstract contracts. For example, if the file library.aes
contains
namespace Library =\nfunction inc(x) = x + 1\n
you can use it from another file using an include
:
include \"library.aes\"\ncontract MyContract =\nentrypoint plus2(x) = Library.inc(Library.inc(x))\n
This behaves as if the contents of library.aes
was textually inserted into the file, except that error messages will refer to the original source locations. The language will try to include each file at most one time automatically, so even cyclic includes should be working without any special tinkering.
"},{"location":"sophia_features/#standard-library","title":"Standard library","text":"Sophia offers standard library which exposes some primitive operations and some higher level utilities. The builtin namespaces like Chain
, Contract
, Map
are included by default and are supported internally by the compiler. Others like List
, Frac
, Option
need to be manually included using the include
directive. For example
include \"List.aes\"\ninclude \"Pair.aes\"\n-- Map is already there!\n\nnamespace C =\nentrypoint keys(m : map('a, 'b)) : list('a) =\nList.map(Pair.fst, (Map.to_list(m)))\n
"},{"location":"sophia_features/#types","title":"Types","text":"Sophia has the following types:
Type Description Example int A 2-complement integer -1
address \u00e6ternity address, 32 bytes Call.origin
bool A Boolean true
bits A bit field Bits.none
bytes(n) A byte array with n
bytes #fedcba9876543210
string An array of bytes \"Foo\"
list A homogeneous immutable singly linked list. [1, 2, 3]
('a, 'b) => 'c A function. Parentheses can be skipped if there is only one argument (x : int, y : int) => x + y
tuple An ordered heterogeneous array (42, \"Foo\", true)
record An immutable key value store with fixed key names and typed values record balance = { owner: address, value: int }
map An immutable key value store with dynamic mapping of keys of one type to values of one type type accounts = map(string, address)
option('a) An optional value either None or Some('a) Some(42)
state A user defined type holding the contract state record state = { owner: address, magic_key: bytes(4) }
event An append only list of blockchain events (or log entries) datatype event = EventX(indexed int, string)
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()
"},{"location":"sophia_features/#literals","title":"Literals","text":"Type Constant/Literal example(s) int -1
, 2425
, 4598275923475723498573485768
address ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
bool true
, false
bits Bits.none
, Bits.all
bytes(8) #fedcba9876543210
string \"This is a string\"
list [1, 2, 3]
, [(true, 24), (false, 19), (false, -42)]
tuple (42, \"Foo\", true)
record { owner = Call.origin, value = 100000000 }
map {[\"foo\"] = 19, [\"bar\"] = 42}
, {}
option(int) Some(42)
, None
state state{ owner = Call.origin, magic_key = #a298105f }
event EventX(0, \"Hello\")
hash #000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
signature #000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
Chain.ttl FixedTTL(1050)
, RelativeTTL(50)
oracle('a, 'b) ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
oracle_query('a, 'b) oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
contract ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
"},{"location":"sophia_features/#hole-expression","title":"Hole expression","text":"Hole expressions, written as ???
, are expressions that are used as a placeholder. During compilation, the compiler will generate a type error indication the type of the hole expression.
include \"List.aes\"\ncontract C =\n entrypoint f() =\n List.sum(List.map(???, [1,2,3]))\n
A hole expression found in the example above will generate the error Found a hole of type `(int) => int`
. This says that the compiler expects a function from int
to int
in place of the ???
placeholder.
"},{"location":"sophia_features/#constants","title":"Constants","text":"Constants in Sophia are contract-level bindings that can be used in either contracts or namespaces. The value of a constant can be a literal, another constant, or arithmetic operations applied to other constants. Lists, tuples, maps, and records can also be used to define a constant as long as their elements are also constants.
The following visibility rules apply to constants: * Constants defined inside a contract are private in that contract. Thus, cannot be accessed through instances of their defining contract. * Constants defined inside a namespace are public. Thus, can be used in other contracts or namespaces. * Constants cannot be defined inside a contract interface.
When a constant is shadowed, it can be accessed using its qualified name:
contract C =\n let c = 1\n entrypoint f() =\n let c = 2\n c + C.c // the result is 3\n
The name of the constant must be an id; therefore, no pattern matching is allowed when defining a constant:
contract C\n let x::y::_ = [1,2,3] // this will result in an error\n
"},{"location":"sophia_features/#arithmetic","title":"Arithmetic","text":"Sophia integers (int
) are represented by arbitrary-sized signed words and support the following arithmetic operations: - addition (x + y
) - subtraction (x - y
) - multiplication (x * y
) - division (x / y
), truncated towards zero - remainder (x mod y
), satisfying y * (x / y) + x mod y == x
for non-zero y
- exponentiation (x ^ y
)
All operations are safe with respect to overflow and underflow. The division and modulo operations throw an arithmetic error if the right-hand operand is zero.
"},{"location":"sophia_features/#bit-fields","title":"Bit fields","text":"Sophia integers do not support bit arithmetic. Instead there is a separate type bits
. See the standard library documentation.
A bit field can be of arbitrary size (but it is still represented by the corresponding integer, so setting very high bits can be expensive).
"},{"location":"sophia_features/#type-aliases","title":"Type aliases","text":"Type aliases can be introduced with the type
keyword and can be parameterized. For instance
type number = int\ntype string_map('a) = map(string, 'a)\n
A type alias and its definition can be used interchangeably. Sophia does not support higher-kinded types, meaning that following type alias is invalid: type wrap('f, 'a) = 'f('a)
"},{"location":"sophia_features/#algebraic-data-types","title":"Algebraic data types","text":"Sophia supports algebraic data types (variant types) and pattern matching. Data types are declared by giving a list of constructors with their respective arguments. For instance,
datatype one_or_both('a, 'b) = Left('a) | Right('b) | Both('a, 'b)\n
Elements of data types can be pattern matched against, using the switch
construct:
function get_left(x : one_or_both('a, 'b)) : option('a) =\nswitch(x)\nLeft(x) => Some(x)\nRight(_) => None\nBoth(x, _) => Some(x)\n
or directly in the left-hand side:
function\nget_left : one_or_both('a, 'b) => option('a)\nget_left(Left(x)) = Some(x)\nget_left(Right(_)) = None\nget_left(Both(x, _)) = Some(x)\n
NOTE: Data types cannot currently be recursive.
Sophia also supports the assignment of patterns to variables:
function f(x) = switch(x)\nh1::(t = h2::_) => (h1 + h2)::t // same as `h1::h2::k => (h1 + h2)::h2::k`\n_ => x\n\nfunction g(p : int * option(int)) : int =\nlet (a, (o = Some(b))) = p // o is equal to Pair.snd(p)\nb\n
Guards are boolean expressions that can be used on patterns in both switch statements and functions definitions. If a guard expression evaluates to true
, then the corresponding body will be used. Otherwise, the next pattern will be checked:
function get_left_if_positive(x : one_or_both(int, 'b)) : option(int) =\nswitch(x)\nLeft(x) | x > 0 => Some(x)\nBoth(x, _) | x > 0 => Some(x)\n_ => None\n
function\nget_left_if_positive : one_or_both(int, 'b) => option(int)\nget_left_if_positive(Left(x)) | x > 0 = Some(x)\nget_left_if_positive(Both(x, _)) | x > 0 = Some(x)\nget_left_if_positive(_) = None\n
Guards cannot be stateful even when used inside a stateful function.
"},{"location":"sophia_features/#lists","title":"Lists","text":"A Sophia list is a dynamically sized, homogenous, immutable, singly linked list. A list is constructed with the syntax [1, 2, 3]
. The 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:
[1, 33, 2, 666] : list(int)\n[(1, \"aaa\"), (10, \"jjj\"), (666, \"the beast\")] : list(int * string)\n[{[1] = \"aaa\", [10] = \"jjj\"}, {[5] = \"eee\", [666] = \"the beast\"}] : list(map(int, string))\n
New elements can be prepended to the front of a list with the ::
operator. So 42 :: [1, 2, 3]
returns the list [42, 1, 2, 3]
. The concatenation operator ++
appends its second argument to its first and returns the resulting list. So concatenating two lists [1, 22, 33] ++ [10, 18, 55]
returns the list [1, 22, 33, 10, 18, 55]
.
Sophia supports list comprehensions known from languages like Python, Haskell or Erlang. Example syntax:
[x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]]\n// yields [12,13,14,20,21,22,30,31,32]\n
Lists can be constructed using the range syntax using special ..
operator:
[1..4] == [1,2,3,4]\n
The ranges are always ascending and have step equal to 1. Please refer to the standard library for the predefined functionalities.
"},{"location":"sophia_features/#maps-and-records","title":"Maps and records","text":"A Sophia record type is given by a fixed set of fields with associated, possibly different, types. For instance
record account = { name : string,\nbalance : int,\nhistory : list(transaction) }\n
Maps, on the other hand, can contain an arbitrary number of key-value bindings, but of a fixed type. The type of maps with keys of type 'k
and values of type 'v
is written map('k, 'v)
. The key type can be any type that does not contain a map or a function type.
Please refer to the standard library for the predefined functionalities.
"},{"location":"sophia_features/#constructing-maps-and-records","title":"Constructing maps and records","text":"A value of record type is constructed by giving a value for each of the fields. For the example above,
function new_account(name) =\n{name = name, balance = 0, history = []}\n
Maps are constructed similarly, with keys enclosed in square brackets function example_map() : map(string, int) =\n{[\"key1\"] = 1, [\"key2\"] = 2}\n
The empty map is written {}
."},{"location":"sophia_features/#accessing-values","title":"Accessing values","text":"Record fields access is written r.f
and map lookup m[k]
. For instance,
function get_balance(a : address, accounts : map(address, account)) =\naccounts[a].balance\n
Looking up a non-existing key in a map results in contract execution failing. A default value to return for non-existing keys can be provided using the syntax m[k = default]
. See also Map.member
and Map.lookup
below."},{"location":"sophia_features/#updating-a-value","title":"Updating a value","text":"Record field updates are written r{f = v}
. This creates a new record value which is the same as r
, but with the value of the field f
replaced by v
. Similarly, m{[k] = v}
constructs a map with the same values as m
except that k
maps to v
. It makes no difference if m
has a mapping for k
or not.
It is possible to give a name to the old value of a field or mapping in an update: instead of acc{ balance = acc.balance + 100 }
it is possible to write acc{ balance @ b = b + 100 }
, binding b
to acc.balance
. When giving a name to a map value (m{ [k] @ x = v }
), the corresponding key must be present in the map or execution fails, but a default value can be provided: m{ [k = default] @ x = v }
. In this case x
is bound to default
if k
is not in the map.
Updates can be nested:
function clear_history(a : address, accounts : map(address, account)) : map(address, account) =\naccounts{ [a].history = [] }\n
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
): accounts{ [a = empty_account()].history = [] }\n
"},{"location":"sophia_features/#map-implementation","title":"Map implementation","text":"Internally in the VM maps are implemented as hash maps and support fast lookup and update. Large maps can be stored in the contract state and the size of the map does not contribute to the gas costs of a contract call reading or updating it.
"},{"location":"sophia_features/#strings","title":"Strings","text":"There is a builtin type string
, which can be seen as an array of bytes. Strings can be compared for equality (==
, !=
), used as keys in maps and records, and used in builtin functions String.length
, String.concat
and the hash functions described below.
Please refer to the String
library documentation.
"},{"location":"sophia_features/#chars","title":"Chars","text":"There is a builtin type char
(the underlying representation being an integer), mainly used to manipulate strings via String.to_list
/String.from_list
.
Characters can also be introduced as character literals (`'x', '+', ...).
Please refer to the Char
library documentation.
"},{"location":"sophia_features/#byte-arrays","title":"Byte arrays","text":"Byte arrays are fixed size arrays of 8-bit integers. They are described in hexadecimal system, for example the literal #cafe
creates a two-element array of bytes ca
(202) and fe
(254) and thus is a value of type bytes(2)
.
Please refer to the Bytes
library documentation.
"},{"location":"sophia_features/#cryptographic-builtins","title":"Cryptographic builtins","text":"Libraries Crypto and String provide functions to hash objects, verify signatures etc. The hash
is a type alias for bytes(32)
.
"},{"location":"sophia_features/#authorization-interface","title":"Authorization interface","text":"When a Generalized account is authorized, the authorization function needs access to the transaction and the transaction hash for the wrapped transaction. (A GAMetaTx
wrapping a transaction.) The transaction and the transaction hash is available in the primitive Auth.tx
and Auth.tx_hash
respectively, they are only available during authentication if invoked by a normal contract call they return None
.
"},{"location":"sophia_features/#oracle-interface","title":"Oracle interface","text":"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. For a functionality documentation refer to the standard library.
"},{"location":"sophia_features/#example","title":"Example","text":"Example for an oracle answering questions of type string
with answers of type int
:
contract Oracles =\n\nstateful entrypoint registerOracle(acct : address,\nsign : signature, // Signed network id + oracle address + contract address\nqfee : int,\nttl : Chain.ttl) : oracle(string, int) =\nOracle.register(acct, signature = sign, qfee, ttl)\n\nentrypoint queryFee(o : oracle(string, int)) : int =\nOracle.query_fee(o)\n\npayable stateful entrypoint createQuery(o : oracle_query(string, int),\nq : string,\nqfee : int,\nqttl : Chain.ttl,\nrttl : int) : oracle_query(string, int) =\nrequire(qfee =< Call.value, \"insufficient value for qfee\")\nOracle.query(o, q, qfee, qttl, RelativeTTL(rttl))\n\nstateful entrypoint extendOracle(o : oracle(string, int),\nttl : Chain.ttl) : unit =\nOracle.extend(o, ttl)\n\nstateful entrypoint signExtendOracle(o : oracle(string, int),\nsign : signature, // Signed network id + oracle address + contract address\nttl : Chain.ttl) : unit =\nOracle.extend(o, signature = sign, ttl)\n\nstateful entrypoint respond(o : oracle(string, int),\nq : oracle_query(string, int),\nsign : signature, // Signed network id + oracle query id + contract address\nr : int) =\nOracle.respond(o, q, signature = sign, r)\n\nentrypoint getQuestion(o : oracle(string, int),\nq : oracle_query(string, int)) : string =\nOracle.get_question(o, q)\n\nentrypoint hasAnswer(o : oracle(string, int),\nq : oracle_query(string, int)) =\nswitch(Oracle.get_answer(o, q))\nNone => false\nSome(_) => true\n\nentrypoint getAnswer(o : oracle(string, int),\nq : oracle_query(string, int)) : option(int) =\nOracle.get_answer(o, q)\n
"},{"location":"sophia_features/#sanity-checks","title":"Sanity checks","text":"When an Oracle literal is passed to a contract, no deep checks are performed. For extra safety Oracle.check and Oracle.check_query functions are provided.
"},{"location":"sophia_features/#aens-interface","title":"AENS interface","text":"Contracts can interact with the \u00e6ternity naming system. For this purpose the AENS library was exposed.
"},{"location":"sophia_features/#example_1","title":"Example","text":"In this example we assume that the name name
already exists, and is owned by an account with address addr
. In order to allow a contract ct
to handle name
the account holder needs to create a signature sig
of addr | name.hash | ct.address
.
Armed with this information we can for example write a function that extends the name if it expires within 1000 blocks:
stateful entrypoint extend_if_necessary(addr : address, name : string, sig : signature) =\nswitch(AENS.lookup(name))\nNone => ()\nSome(AENS.Name(_, FixedTTL(expiry), _)) =>\nif(Chain.block_height + 1000 > expiry)\nAENS.update(addr, name, Some(RelativeTTL(50000)), None, None, signature = sig)\n
And we can write functions that adds and removes keys from the pointers of the name:
stateful entrypoint add_key(addr : address, name : string, key : string,\npt : AENS.pointee, sig : signature) =\nswitch(AENS.lookup(name))\nNone => ()\nSome(AENS.Name(_, _, ptrs)) =>\nAENS.update(addr, name, None, None, Some(ptrs{[key] = pt}), signature = sig)\n\nstateful entrypoint delete_key(addr : address, name : string,\nkey : string, sig : signature) =\nswitch(AENS.lookup(name))\nNone => ()\nSome(AENS.Name(_, _, ptrs)) =>\nlet ptrs = Map.delete(key, ptrs)\nAENS.update(addr, name, None, None, Some(ptrs), signature = sig)\n
Note: From the Iris hardfork more strict rules apply for AENS pointers, when a Sophia contract lookup or update (bad) legacy pointers, the bad keys are automatically removed so they will not appear in the pointers map.
"},{"location":"sophia_features/#events","title":"Events","text":"Sophia contracts log structured messages to an event log in the resulting blockchain transaction. The event log is quite similar to Events in Solidity. Events are further discussed in the protocol.
To use events a contract must declare a datatype event
, and events are then logged using the Chain.event
function:
datatype event\n= Event1(int, int, string)\n| Event2(string, address)\n\nChain.event(e : event) : unit\n
The event can have 0-3 indexed fields, and an optional payload field. A field is indexed if it fits in a 32-byte word, i.e. - bool
- int
- bits
- address
- oracle(_, _)
- oracle_query(_, _)
- contract types - bytes(n)
for n
\u2264 32, in particular hash
The payload field must be either a string or a byte array of more than 32 bytes. The fields can appear in any order.
NOTE: Indexing is not part of the core \u00e6ternity node.
Events are emitted by using the Chain.event
function. The following function will emit one Event of each kind in the example.
entrypoint emit_events() : () =\nChain.event(Event1(42, 34, \"foo\"))\nChain.event(Event2(\"This is not indexed\", Contract.address))\n
"},{"location":"sophia_features/#argument-order","title":"Argument order","text":"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.
AnotherEvent(string, indexed address)\n\n...\n\nChain.event(AnotherEvent(\"This is not indexed\", Contract.address))\n
would yield exactly the same result in the example above!"},{"location":"sophia_features/#compiler-pragmas","title":"Compiler pragmas","text":"To enforce that a contract is only compiled with specific versions of the Sophia compiler, you can give one or more @compiler
pragmas at the top-level (typically at the beginning) of a file. For instance, to enforce that a contract is compiled with version 4.3 of the compiler you write
@compiler >= 4.3\n@compiler < 4.4\n
Valid operators in compiler pragmas are <
, =<
, ==
, >=
, and >
. Version numbers are given as a sequence of non-negative integers separated by dots. Trailing zeros are ignored, so 4.0.0 == 4
. If a constraint is violated an error is reported and compilation fails.
"},{"location":"sophia_features/#exceptions","title":"Exceptions","text":"Contracts can fail with an (uncatchable) exception using the built-in function
abort(reason : string) : 'a\n
Calling abort causes the top-level call transaction to return an error result containing the reason
string. Only the gas used up to and including the abort call is charged. This is different from termination due to a crash which consumes all available gas.
For convenience the following function is also built-in:
function require(b : bool, err : string) =\nif(!b) abort(err)\n
Aside from that, there is an almost equivalent function exit
exit(reason : string) : 'a\n
Just like abort
, it breaks the execution with the given reason. The difference however is in the gas consumption \u2014 while abort
returns unused gas, a call to exit
burns it all.
"},{"location":"sophia_features/#delegation-signature","title":"Delegation signature","text":"Some chain operations (Oracle.<operation>
and AENS.<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. 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 \u00e6ternity mainnet, etc.).
"},{"location":"sophia_stdlib/","title":"Standard library","text":""},{"location":"sophia_stdlib/#standard-library","title":"Standard library","text":"Sophia language offers standard library that consists of several namespaces. Some of them are already in the scope and do not need any actions to be used, while the others require some files to be included.
The out-of-the-box namespaces are:
- Address
- AENS
- Auth
- Bits
- Bytes
- Call
- Chain
- Char
- Contract
- Crypto
- Int
- Map
- Oracle
The following ones need to be included as regular files with .aes
suffix, for example
include \"List.aes\"\n
- Bitwise
- BLS12_381
- Func
- Frac
- List
- Option
- Pair
- Set
- String
- Triple
"},{"location":"sophia_stdlib/#builtin-namespaces","title":"Builtin namespaces","text":"They are available without any explicit includes.
"},{"location":"sophia_stdlib/#address","title":"Address","text":""},{"location":"sophia_stdlib/#to_str","title":"to_str","text":"Address.to_str(a : address) : string\n
Base58 encoded string
"},{"location":"sophia_stdlib/#is_contract","title":"is_contract","text":"Address.is_contract(a : address) : bool\n
Is the address a contract
"},{"location":"sophia_stdlib/#is_oracle","title":"is_oracle","text":"Address.is_oracle(a : address) : bool\n
Is the address a registered oracle
"},{"location":"sophia_stdlib/#is_payable","title":"is_payable","text":"Address.is_payable(a : address) : bool\n
Can the address be spent to
"},{"location":"sophia_stdlib/#to_contract","title":"to_contract","text":"Address.to_contract(a : address) : C\n
Cast address to contract type C (where C
is a contract)
"},{"location":"sophia_stdlib/#aens","title":"AENS","text":"The following functionality is available for interacting with the \u00e6ternity 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, i.e. the signature material should be prefixed by the network id.
"},{"location":"sophia_stdlib/#types","title":"Types","text":""},{"location":"sophia_stdlib/#name","title":"name","text":"datatype name = Name(address, Chain.ttl, map(string, AENS.pointee))\n
"},{"location":"sophia_stdlib/#pointee","title":"pointee","text":"datatype pointee = AccountPt(address) | OraclePt(address)\n | ContractPt(address) | ChannelPt(address)\n
"},{"location":"sophia_stdlib/#functions","title":"Functions","text":""},{"location":"sophia_stdlib/#resolve","title":"resolve","text":"AENS.resolve(name : string, key : string) : option('a)\n
Name resolution. Here name
should be a registered name and key
one of the attributes associated with this name (for instance \"account_pubkey\"
). The return type ('a
) must be resolved at compile time to an atomic type and the value is type checked against this type at run time.
"},{"location":"sophia_stdlib/#lookup","title":"lookup","text":"AENS.lookup(name : string) : option(AENS.name)\n
If name
is an active name AENS.lookup
returns a name object. The three arguments to Name
are owner
, expiry
and a map of the pointees
for the name. Note: the expiry of the name is always a fixed TTL. For example:
let Some(Name(owner, FixedTTL(expiry), ptrs)) = AENS.lookup(\"example.chain\")\n
"},{"location":"sophia_stdlib/#preclaim","title":"preclaim","text":"AENS.preclaim(owner : address, commitment_hash : hash, <signature : signature>) : unit\n
The signature should be over network id
+ owner address
+ Contract.address
(concatenated as byte arrays).
"},{"location":"sophia_stdlib/#claim","title":"claim","text":"AENS.claim(owner : address, name : string, salt : int, name_fee : int, <signature : signature>) : unit\n
The signature should be over network id
+ owner address
+ name_hash
+ Contract.address
(concatenated as byte arrays) using the private key of the owner
account for signing.
"},{"location":"sophia_stdlib/#transfer","title":"transfer","text":"AENS.transfer(owner : address, new_owner : address, name : string, <signature : signature>) : unit\n
Transfers name to the new owner.
The signature should be over network id
+ owner address
+ name_hash
+ Contract.address
(concatenated as byte arrays) using the private key of the owner
account for signing.
"},{"location":"sophia_stdlib/#revoke","title":"revoke","text":"AENS.revoke(owner : address, name : string, <signature : signature>) : unit\n
Revokes the name to extend the ownership time.
The signature should be over network id
+ owner address
+ name_hash
+ Contract.address
(concatenated as byte arrays) using the private key of the owner
account for signing.
"},{"location":"sophia_stdlib/#update","title":"update","text":"AENS.update(owner : address, name : string, expiry : option(Chain.ttl), client_ttl : option(int),\n new_ptrs : option(map(string, AENS.pointee)), <signature : signature>) : unit\n
Updates the name. If the optional parameters are set to None
that parameter will not be updated, for example if None
is passed as expiry
the expiry block of the name is not changed.
"},{"location":"sophia_stdlib/#auth","title":"Auth","text":""},{"location":"sophia_stdlib/#tx","title":"tx","text":"Auth.tx : option(Chain.tx)\n
Where Chain.tx
is (built-in) defined like:
namespace Chain =\n record tx = { paying_for : option(Chain.paying_for_tx)\n , ga_metas : list(Chain.ga_meta_tx)\n , actor : address\n , fee : int\n , ttl : int\n , tx : Chain.base_tx }\n\n datatype ga_meta_tx = GAMetaTx(address, int)\n datatype paying_for_tx = PayingForTx(address, int)\n datatype base_tx = SpendTx(address, int, string)\n | OracleRegisterTx | OracleQueryTx | OracleResponseTx | OracleExtendTx\n | NamePreclaimTx | NameClaimTx(hash) | NameUpdateTx(string)\n | NameRevokeTx(hash) | NameTransferTx(address, string)\n | ChannelCreateTx(address) | ChannelDepositTx(address, int) | ChannelWithdrawTx(address, int) |\n | ChannelForceProgressTx(address) | ChannelCloseMutualTx(address) | ChannelCloseSoloTx(address)\n | ChannelSlashTx(address) | ChannelSettleTx(address) | ChannelSnapshotSoloTx(address)\n | ContractCreateTx(int) | ContractCallTx(address, int)\n | GAAttachTx\n
"},{"location":"sophia_stdlib/#tx_hash","title":"tx_hash","text":"Auth.tx_hash : option(hash)\n
Gets the transaction hash during authentication.
"},{"location":"sophia_stdlib/#bits","title":"Bits","text":""},{"location":"sophia_stdlib/#none","title":"none","text":"Bits.none : bits\n
A bit field with all bits cleared
"},{"location":"sophia_stdlib/#all","title":"all","text":"Bits.all : bits\n
A bit field with all bits set
"},{"location":"sophia_stdlib/#set","title":"set","text":"Bits.set(b : bits, i : int) : bits\n
Set bit i
"},{"location":"sophia_stdlib/#clear","title":"clear","text":"Bits.clear(b : bits, i : int) : bits\n
Clear bit i
"},{"location":"sophia_stdlib/#test","title":"test","text":"Bits.test(b : bits, i : int) : bool\n
Check if bit i is set
"},{"location":"sophia_stdlib/#sum","title":"sum","text":"Bits.sum(b : bits) : int\n
Count the number of set bits
"},{"location":"sophia_stdlib/#union","title":"union","text":"Bits.union(a : bits, b : bits) : bits\n
Bitwise disjunction
"},{"location":"sophia_stdlib/#intersection","title":"intersection","text":"Bits.intersection(a : bits, b : bits) : bits\n
Bitwise conjunction
"},{"location":"sophia_stdlib/#difference","title":"difference","text":"Bits.difference(a : bits, b : bits) : bits\n
Each bit is true if and only if it was 1 in a
and 0 in b
"},{"location":"sophia_stdlib/#bytes","title":"Bytes","text":""},{"location":"sophia_stdlib/#to_int","title":"to_int","text":"Bytes.to_int(b : bytes(n)) : int\n
Interprets the byte array as a big endian integer
"},{"location":"sophia_stdlib/#to_str_1","title":"to_str","text":"Bytes.to_str(b : bytes(n)) : string\n
Returns the hexadecimal representation of the byte array
"},{"location":"sophia_stdlib/#concat","title":"concat","text":"Bytes.concat : (a : bytes(m), b : bytes(n)) => bytes(m + n)\n
Concatenates two byte arrays
"},{"location":"sophia_stdlib/#split","title":"split","text":"Bytes.split(a : bytes(m + n)) : bytes(m) * bytes(n)\n
Splits a byte array at given index
"},{"location":"sophia_stdlib/#call","title":"Call","text":"Values related to the call to the current contract
"},{"location":"sophia_stdlib/#origin","title":"origin","text":"Call.origin : address\n
The address of the account that signed the call transaction that led to this call.
"},{"location":"sophia_stdlib/#caller","title":"caller","text":"Call.caller : address\n
The address of the entity (possibly another contract) calling the contract.
"},{"location":"sophia_stdlib/#value","title":"value","text":"Call.value : int\n
The amount of coins transferred to the contract in the call.
"},{"location":"sophia_stdlib/#gas_price","title":"gas_price","text":"Call.gas_price : int\n
The gas price of the current call.
"},{"location":"sophia_stdlib/#fee","title":"fee","text":"Call.fee : int\n
The fee of the current call.
"},{"location":"sophia_stdlib/#gas_left","title":"gas_left","text":"Call.gas_left() : int\n
The amount of gas left for the current call.
"},{"location":"sophia_stdlib/#chain","title":"Chain","text":"Values and functions related to the chain itself and other entities that live on it.
"},{"location":"sophia_stdlib/#types_1","title":"Types","text":""},{"location":"sophia_stdlib/#tx_1","title":"tx","text":"record tx = { paying_for : option(Chain.paying_for_tx)\n , ga_metas : list(Chain.ga_meta_tx)\n , actor : address\n , fee : int\n , ttl : int\n , tx : Chain.base_tx }\n
"},{"location":"sophia_stdlib/#ga_meta_tx","title":"ga_meta_tx","text":"datatype ga_meta_tx = GAMetaTx(address, int)\n
"},{"location":"sophia_stdlib/#paying_for_tx","title":"paying_for_tx","text":"datatype paying_for_tx = PayingForTx(address, int)\n
"},{"location":"sophia_stdlib/#base_tx","title":"base_tx","text":"datatype base_tx = SpendTx(address, int, string)\n | OracleRegisterTx | OracleQueryTx | OracleResponseTx | OracleExtendTx\n | NamePreclaimTx | NameClaimTx(hash) | NameUpdateTx(string)\n | NameRevokeTx(hash) | NameTransferTx(address, string)\n | ChannelCreateTx(address) | ChannelDepositTx(address, int) | ChannelWithdrawTx(address, int) |\n | ChannelForceProgressTx(address) | ChannelCloseMutualTx(address) | ChannelCloseSoloTx(address)\n | ChannelSlashTx(address) | ChannelSettleTx(address) | ChannelSnapshotSoloTx(address)\n | ContractCreateTx(int) | ContractCallTx(address, int)\n | GAAttachTx\n
"},{"location":"sophia_stdlib/#functions_1","title":"Functions","text":""},{"location":"sophia_stdlib/#balance","title":"balance","text":"Chain.balance(a : address) : int\n
The balance of account a
.
"},{"location":"sophia_stdlib/#block_hash","title":"block_hash","text":"Chain.block_hash(h : int) : option(bytes(32))\n
The hash of the block at height h
. h
has to be within 256 blocks from the current height of the chain or else the function will return None
.
NOTE: In FATE VM version 1 Chain.block_height
was not considered an allowed height. From FATE VM version 2 (IRIS) it will return the block hash of the current generation.
"},{"location":"sophia_stdlib/#block_height","title":"block_height","text":"Chain.block_height : int\"\n
The height of the current block (i.e. the block in which the current call will be included).
"},{"location":"sophia_stdlib/#bytecode_hash","title":"bytecode_hash","text":"Chain.bytecode_hash : 'c => option(hash)\n
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.
"},{"location":"sophia_stdlib/#create","title":"create","text":"Chain.create(value : int, ...) => 'c\n
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 \u2013 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 =\n record state = {supply: int, name: string}\n entrypoint init(supply, name) = {supply: supply, name: name}\n stateful payable entrypoint buy(amount) =\n require(Call.value == amount, \"amount_value_mismatch\")\n ...\n stateful entrypoint sell(amount) =\n require(amount >= 0, \"negative_amount\")\n ...\n\nmain contract Market =\n type state = list(Auction)\n entrypoint init() = []\n stateful entrypoint new(name : string) =\n let new_auction = Chain.create(0, name) : Auction\n put(new_auction::state)\n
The typechecker must be certain about the created contract's type, so it is worth writing it explicitly as shown in the example.
"},{"location":"sophia_stdlib/#clone","title":"clone","text":"Chain.clone : ( ref : 'c, gas : int, value : int, protected : bool, ...\n ) => if(protected) option('c) else 'c\n
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 \u2013 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 =\n entrypoint init : (int, string) => void\n stateful payable entrypoint buy : (int) => ()\n stateful entrypoint sell : (int) => ()\n\nmain contract Market =\n type state = list(Auction)\n entrypoint init() = []\n stateful entrypoint new_of(template : Auction, name : string) =\n switch(Chain.clone(ref=template, protected=true, 0, name))\n None => abort(\"Bad auction!\")\n Some(new_auction) =>\n put(new_auction::state)\n
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
.
"},{"location":"sophia_stdlib/#coinbase","title":"coinbase","text":"Chain.coinbase : address\n
The address of the account that mined the current block.
"},{"location":"sophia_stdlib/#difficulty","title":"difficulty","text":"Chain.difficulty : int\n
The difficulty of the current block.
"},{"location":"sophia_stdlib/#event","title":"event","text":"Chain.event(e : event) : unit\n
Emits the event. To use this function one needs to define the event
type as a datatype
in the contract.
"},{"location":"sophia_stdlib/#gas_limit","title":"gas_limit","text":"Chain.gas_limit : int\n
The gas limit of the current block.
"},{"location":"sophia_stdlib/#spend","title":"spend","text":"Chain.spend(to : address, amount : int) : unit\n
Spend amount
tokens to to
. Will fail (and abort the contract) if contract doesn't have amount
tokens to transfer, or, if to
is not payable
.
"},{"location":"sophia_stdlib/#timestamp","title":"timestamp","text":"Chain.timestamp : int\n
The timestamp of the current block (unix time, milliseconds).
"},{"location":"sophia_stdlib/#char","title":"Char","text":""},{"location":"sophia_stdlib/#to_int_1","title":"to_int","text":"Char.to_int(c : char) : int Returns the UTF-8 codepoint of a character\n\n\n#### from_int\n
Char.from_int(i : int) : option(char)
Opposite of to_int. Returns None
if the integer doesn't correspond to a single (normalized) codepoint.
"},{"location":"sophia_stdlib/#contract","title":"Contract","text":"Values related to the current contract
"},{"location":"sophia_stdlib/#creator","title":"creator","text":"Contract.creator : address\n
Address of the entity that signed the contract creation transaction
"},{"location":"sophia_stdlib/#address_1","title":"address","text":"Contract.address : address\n
Address of the contract account
"},{"location":"sophia_stdlib/#balance_1","title":"balance","text":"Contract.balance : int\n
Amount of coins in the contract account
"},{"location":"sophia_stdlib/#crypto","title":"Crypto","text":""},{"location":"sophia_stdlib/#sha3","title":"sha3","text":"Crypto.sha3(x : 'a) : hash\n
Hash any object to SHA3
"},{"location":"sophia_stdlib/#sha256","title":"sha256","text":"Crypto.sha256(x : 'a) : hash\n
Hash any object to SHA256
"},{"location":"sophia_stdlib/#blake2b","title":"blake2b","text":"Crypto.blake2b(x : 'a) : hash\n
Hash any object to blake2b
"},{"location":"sophia_stdlib/#verify_sig","title":"verify_sig","text":"Crypto.verify_sig(msg : hash, pubkey : address, sig : signature) : bool\n
Checks if the signature of msg
was made using private key corresponding to the pubkey
"},{"location":"sophia_stdlib/#ecverify_secp256k1","title":"ecverify_secp256k1","text":"Crypto.ecverify_secp256k1(msg : hash, addr : bytes(20), sig : bytes(65)) : bool\n
Verifies a signature for a msg against an Ethereum style address. Note that the signature should be 65 bytes and include the recovery identifier byte V
. The expected organization of the signature is (V || R || S
).
"},{"location":"sophia_stdlib/#ecrecover_secp256k1","title":"ecrecover_secp256k1","text":"Crypto.ecrecover_secp256k1(msg : hash, sig : bytes(65)) : option(bytes(20))\n
Recovers the Ethereum style address from a msg hash and respective ECDSA-signature. Note that the signature should be 65 bytes and include the recovery identifier byte V
. The expected organization of the signature is (V || R || S
).
"},{"location":"sophia_stdlib/#verify_sig_secp256k1","title":"verify_sig_secp256k1","text":"Crypto.verify_sig_secp256k1(msg : hash, pubkey : bytes(64), sig : bytes(64)) : bool\n
Verifies a standard 64-byte ECDSA signature (R || S
).
"},{"location":"sophia_stdlib/#int","title":"Int","text":""},{"location":"sophia_stdlib/#to_str_2","title":"to_str","text":"Int.to_str : int => string\n
Casts integer to string using decimal representation
"},{"location":"sophia_stdlib/#map","title":"Map","text":""},{"location":"sophia_stdlib/#lookup_1","title":"lookup","text":"Map.lookup(k : 'k, m : map('k, 'v)) : option('v)
Returns the value under a key in given map as Some
or None
if the key is not present
"},{"location":"sophia_stdlib/#lookup_default","title":"lookup_default","text":"Map.lookup_default(k : 'k, m : map('k, 'v), v : 'v) : 'v
Returns the value under a key in given map or the default value v
if the key is not present
"},{"location":"sophia_stdlib/#member","title":"member","text":"Map.member(k : 'k, m : map('k, 'v)) : bool
Checks if the key is present in the map
"},{"location":"sophia_stdlib/#delete","title":"delete","text":"Map.delete(k : 'k, m : map('k, 'v)) : map('k, 'v)
Removes the key from the map
"},{"location":"sophia_stdlib/#size","title":"size","text":"Map.size(m : map('k, 'v)) : int
Returns the number of elements in the map
"},{"location":"sophia_stdlib/#to_list","title":"to_list","text":"Map.to_list(m : map('k, 'v)) : list('k * 'v)
Returns a list containing pairs of keys and their respective elements.
"},{"location":"sophia_stdlib/#from_list","title":"from_list","text":"Map.from_list(m : list('k * 'v)) : map('k, 'v)
Turns a list of pairs of form (key, value)
into a map
"},{"location":"sophia_stdlib/#oracle","title":"Oracle","text":""},{"location":"sophia_stdlib/#register","title":"register","text":"Oracle.register(<signature : bytes(64)>, acct : address, qfee : int, ttl : Chain.ttl) : oracle('a, 'b)\n
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). signature
is a signature proving that the contract is allowed to register the account - the network id
+ account address
+ contract address
(concatenated as byte arrays) is signed with the private key of the account, 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))\n Oracle.register(addr1, 25, RelativeTTL(500), signature = sign1)\n
"},{"location":"sophia_stdlib/#get_question","title":"get_question","text":"Oracle.get_question(o : oracle('a, 'b), q : oracle_query('a, 'b)) : 'a\n
Checks what was the question of query q
on oracle o
"},{"location":"sophia_stdlib/#respond","title":"respond","text":"Oracle.respond(<signature : bytes(64)>, o : oracle('a, 'b), q : oracle_query('a, 'b), 'b) : unit\n
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 the network id
+ oracle query id
+ contract address
"},{"location":"sophia_stdlib/#extend","title":"extend","text":"Oracle.extend(<signature : bytes(64)>, o : oracle('a, 'b), ttl : Chain.ttl) : unit\n
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.
"},{"location":"sophia_stdlib/#query_fee","title":"query_fee","text":"Oracle.query_fee(o : oracle('a, 'b)) : int\n
Returns the query fee of the oracle
"},{"location":"sophia_stdlib/#query","title":"query","text":"Oracle.query(o : oracle('a, 'b), q : 'a, qfee : int, qttl : Chain.ttl, rttl : Chain.ttl) : oracle_query('a, 'b)\n
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.
"},{"location":"sophia_stdlib/#get_answer","title":"get_answer","text":"Oracle.get_answer(o : oracle('a, 'b), q : oracle_query('a, 'b)) : option('b)\n
Checks what is the optional query answer
"},{"location":"sophia_stdlib/#expiry","title":"expiry","text":"Oracle.expiry(o : oracle('a, 'b)) : int\n
Ask the oracle when it expires. The result is the block height at which it will happen.
"},{"location":"sophia_stdlib/#check","title":"check","text":"Oracle.check(o : oracle('a, 'b)) : bool\n
Returns true
iff the oracle o
exists and has correct type
"},{"location":"sophia_stdlib/#check_query","title":"check_query","text":"Oracle.check_query(o : oracle('a, 'b), q : oracle_query('a, 'b)) : bool\n
It returns true
iff the oracle query exist and has the expected type.
"},{"location":"sophia_stdlib/#includable-namespaces","title":"Includable namespaces","text":"These need to be explicitly included (with .aes
suffix)
"},{"location":"sophia_stdlib/#bitwise","title":"Bitwise","text":"Bitwise operations on arbitrary precision integers.
"},{"location":"sophia_stdlib/#bsr","title":"bsr","text":"Bitwise.bsr(n : int, x : int) : int\n
Logical bit shift x
right n
positions.
"},{"location":"sophia_stdlib/#bsl","title":"bsl","text":"Bitwise.bsl(n : int, x : int) : int\n
Logical bit shift x
left n
positions.
"},{"location":"sophia_stdlib/#bsli","title":"bsli","text":"Bitwise.bsli(n : int, x : int, lim : int) : int\n
Logical bit shift x
left n
positions, limit to lim
bits.
"},{"location":"sophia_stdlib/#band","title":"band","text":"Bitwise.band(x : int, y : int) : int\n
Bitwise and
of x
and y
.
"},{"location":"sophia_stdlib/#bor","title":"bor","text":"Bitwise.bor(x : int, y : int) : int\n
Bitwise or
of x
and y
.
"},{"location":"sophia_stdlib/#bxor","title":"bxor","text":"Bitwise.bxor(x : int, y : int) : int\n
Bitwise xor
of x
and y
.
"},{"location":"sophia_stdlib/#bnot","title":"bnot","text":"Bitwise.bnot(x : int) : int\n
Bitwise not
of x
. Defined and implemented as bnot(x) = bxor(x, -1)
.
"},{"location":"sophia_stdlib/#uband","title":"uband","text":"Bitwise.uband(x : int, y : int) : int\n
Bitwise and
of non-negative numbers x
and y
.
"},{"location":"sophia_stdlib/#ubor","title":"ubor","text":"Bitwise.ubor(x : int, y : int) : int\n
Bitwise or
of non-negative x
and y
.
"},{"location":"sophia_stdlib/#ubxor","title":"ubxor","text":"Bitwise.ubxor(x : int, y : int) : int\n
Bitwise xor
of non-negative x
and y
.
"},{"location":"sophia_stdlib/#bls12_381","title":"BLS12_381","text":""},{"location":"sophia_stdlib/#types_2","title":"Types","text":""},{"location":"sophia_stdlib/#fr","title":"fr","text":"Built-in (Montgomery) integer representation 32 bytes
"},{"location":"sophia_stdlib/#fp","title":"fp","text":"Built-in (Montgomery) integer representation 48 bytes
"},{"location":"sophia_stdlib/#fp2","title":"fp2","text":"record fp2 = { x1 : fp, x2 : fp }`\n
"},{"location":"sophia_stdlib/#g1","title":"g1","text":"record g1 = { x : fp, y : fp, z : fp }\n
"},{"location":"sophia_stdlib/#g2","title":"g2","text":"record g2 = { x : fp2, y : fp2, z : fp2 }\n
"},{"location":"sophia_stdlib/#gt","title":"gt","text":"record gt = { x1 : fp, x2 : fp, x3 : fp, x4 : fp, x5 : fp, x6 : fp, x7 : fp, x8 : fp, x9 : fp, x10 : fp, x11 : fp, x12 : fp }\n
"},{"location":"sophia_stdlib/#functions_2","title":"Functions","text":""},{"location":"sophia_stdlib/#pairing_check","title":"pairing_check","text":"BLS12_381.pairing_check(xs : list(g1), ys : list(g2)) : bool\n
Pairing check of a list of points, xs
and ys
should be of equal length.
"},{"location":"sophia_stdlib/#int_to_fr","title":"int_to_fr","text":"BLS12_381.int_to_fr(x : int) : fr\n
Convert an integer to an fr
- a 32 bytes internal (Montgomery) integer representation.
"},{"location":"sophia_stdlib/#int_to_fp","title":"int_to_fp","text":"BLS12_381.int_to_fp(x : int) : fp\n
Convert an integer to an fp
- a 48 bytes internal (Montgomery) integer representation.
"},{"location":"sophia_stdlib/#fr_to_int","title":"fr_to_int","text":"BLS12_381.fr_to_int(x : fr) : int\n
Convert a fr
value into an integer.
"},{"location":"sophia_stdlib/#fp_to_int","title":"fp_to_int","text":"BLS12_381.fp_to_int(x : fp) : int\n
Convert a fp
value into an integer.
"},{"location":"sophia_stdlib/#mk_g1","title":"mk_g1","text":"BLS12_381.mk_g1(x : int, y : int, z : int) : g1\n
Construct a g1
point from three integers.
"},{"location":"sophia_stdlib/#mk_g2","title":"mk_g2","text":"BLS12_381.mk_g2(x1 : int, x2 : int, y1 : int, y2 : int, z1 : int, z2 : int) : g2\n
Construct a g2
point from six integers.
"},{"location":"sophia_stdlib/#g1_neg","title":"g1_neg","text":"BLS12_381.g1_neg(p : g1) : g1\n
Negate a g1
value.
"},{"location":"sophia_stdlib/#g1_norm","title":"g1_norm","text":"BLS12_381.g1_norm(p : g1) : g1\n
Normalize a g1
value.
"},{"location":"sophia_stdlib/#g1_valid","title":"g1_valid","text":"BLS12_381.g1_valid(p : g1) : bool\n
Check that a g1
value is a group member.
"},{"location":"sophia_stdlib/#g1_is_zero","title":"g1_is_zero","text":"BLS12_381.g1_is_zero(p : g1) : bool\n
Check if a g1
value corresponds to the zero value of the group.
"},{"location":"sophia_stdlib/#g1_add","title":"g1_add","text":"BLS12_381.g1_add(p : g1, q : g1) : g1\n
Add two g1
values.
"},{"location":"sophia_stdlib/#g1_mul","title":"g1_mul","text":"BLS12_381.g1_mul(k : fr, p : g1) : g1\n
Scalar multiplication for g1
.
"},{"location":"sophia_stdlib/#g2_neg","title":"g2_neg","text":"BLS12_381.g2_neg(p : g2) : g2\n
Negate a g2
value.
"},{"location":"sophia_stdlib/#g2_norm","title":"g2_norm","text":"BLS12_381.g2_norm(p : g2) : g2\n
Normalize a g2
value.
"},{"location":"sophia_stdlib/#g2_valid","title":"g2_valid","text":"BLS12_381.g2_valid(p : g2) : bool\n
Check that a g2
value is a group member.
"},{"location":"sophia_stdlib/#g2_is_zero","title":"g2_is_zero","text":"BLS12_381.g2_is_zero(p : g2) : bool\n
Check if a g2
value corresponds to the zero value of the group.
"},{"location":"sophia_stdlib/#g2_add","title":"g2_add","text":"BLS12_381.g2_add(p : g2, q : g2) : g2\n
Add two g2
values.
"},{"location":"sophia_stdlib/#g2_mul","title":"g2_mul","text":"BLS12_381.g2_mul(k : fr, p : g2) : g2\n
Scalar multiplication for g2
.
"},{"location":"sophia_stdlib/#gt_inv","title":"gt_inv","text":"BLS12_381.gt_inv(p : gt) : gt\n
Invert a gt
value.
"},{"location":"sophia_stdlib/#gt_add","title":"gt_add","text":"BLS12_381.gt_add(p : gt, q : gt) : gt\n
Add two gt
values.
"},{"location":"sophia_stdlib/#gt_mul","title":"gt_mul","text":"BLS12_381.gt_mul(p : gt, q : gt) : gt\n
Multiply two gt
values.
"},{"location":"sophia_stdlib/#gt_pow","title":"gt_pow","text":"BLS12_381.gt_pow(p : gt, k : fr) : gt\n
Calculate exponentiation p ^ k
.
"},{"location":"sophia_stdlib/#gt_is_one","title":"gt_is_one","text":"BLS12_381.gt_is_one(p : gt) : bool\n
Compare a gt
value to the unit value of the Gt group.
"},{"location":"sophia_stdlib/#pairing","title":"pairing","text":"BLS12_381.pairing(p : g1, q : g2) : gt\n
Compute the pairing of a g1
value and a g2
value.
"},{"location":"sophia_stdlib/#miller_loop","title":"miller_loop","text":"BLS12_381.miller_loop(p : g1, q : g2) : gt\n
Do the Miller loop stage of pairing for g1
and g2
.
"},{"location":"sophia_stdlib/#final_exp","title":"final_exp","text":"BLS12_381.final_exp(p : gt) : gt\n
Perform the final exponentiation step of pairing for a gt
value.
"},{"location":"sophia_stdlib/#func","title":"Func","text":"Functional combinators.
"},{"location":"sophia_stdlib/#id","title":"id","text":"Func.id(x : 'a) : 'a\n
Identity function. Returns its argument.
"},{"location":"sophia_stdlib/#const","title":"const","text":"Func.const(x : 'a) : 'b => 'a = (y) => x\n
Constant function constructor. Given x
returns a function that returns x
regardless of its argument.
"},{"location":"sophia_stdlib/#flip","title":"flip","text":"Func.flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c\n
Switches order of arguments of arity 2 function.
"},{"location":"sophia_stdlib/#comp","title":"comp","text":"Func.comp(f : 'b => 'c, g : 'a => 'b) : 'a => 'c\n
Function composition. comp(f, g)(x) == f(g(x))
.
"},{"location":"sophia_stdlib/#pipe","title":"pipe","text":"Func.pipe(f : 'a => 'b, g : 'b => 'c) : 'a => 'c\n
Flipped function composition. pipe(f, g)(x) == g(f(x))
.
"},{"location":"sophia_stdlib/#rapply","title":"rapply","text":"Func.rapply(x : 'a, f : 'a => 'b) : 'b\n
Reverse application. rapply(x, f) == f(x)
.
"},{"location":"sophia_stdlib/#recur","title":"recur","text":"Func.recur(f : ('arg => 'res, 'arg) => 'res) : 'arg => 'res\n
The Z combinator. Allows performing local recursion and having anonymous recursive lambdas. To make function A => B
recursive the user needs to transform it to take two arguments instead \u2013 one of type A => B
which is going to work as a self-reference, and the other one of type A
which is the original argument. Therefore, transformed function should have (A => B, A) => B
signature.
Example usage:
let factorial = recur((fac, n) => if(n < 2) 1 else n * fac(n - 1))\n
If the function is going to take more than one argument it will need to be either tuplified or have curried out latter arguments.
Example (factorial with custom step):
// tuplified version\nlet factorial_t(n, step) =\n let fac(rec, args) =\n let (n, step) = args\n if(n < 2) 1 else n * rec((n - step, step))\n recur(fac)((n, step))\n\n// curried version\nlet factorial_c(n, step) =\n let fac(rec, n) = (step) =>\n if(n < 2) 1 else n * rec(n - 1)(step)\n recur(fac)(n)(step)\n
"},{"location":"sophia_stdlib/#iter","title":"iter","text":"Func.iter(n : int, f : 'a => 'a) : 'a => 'a\n
n
th composition of f with itself, for instance iter(3, f)
is equivalent to (x) => f(f(f(x)))
.
"},{"location":"sophia_stdlib/#curry","title":"curry","text":"Func.curry2(f : ('a, 'b) => 'c) : 'a => ('b => 'c)\nFunc.curry3(f : ('a, 'b, 'c) => 'd) : 'a => ('b => ('c => 'd))\n
Turns a function that takes n arguments into a curried function that takes one argument and returns a function that waits for the rest in the same manner. For instance curry2((a, b) => a + b)(1)(2) == 3
.
"},{"location":"sophia_stdlib/#uncurry","title":"uncurry","text":"Func.uncurry2(f : 'a => ('b => 'c)) : ('a, 'b) => 'c\nFunc.uncurry3(f : 'a => ('b => ('c => 'd))) : ('a, 'b, 'c) => 'd\n
Opposite to curry.
"},{"location":"sophia_stdlib/#tuplify","title":"tuplify","text":"Func.tuplify2(f : ('a, 'b) => 'c) : (('a * 'b)) => 'c\nFunc.tuplify3(f : ('a, 'b, 'c) => 'd) : 'a * 'b * 'c => 'd\n
Turns a function that takes n arguments into a function that takes an n-tuple.
"},{"location":"sophia_stdlib/#untuplify","title":"untuplify","text":"Func.untuplify2(f : 'a * 'b => 'c) : ('a, 'b) => 'c\nFunc.untuplify3(f : 'a * 'b * 'c => 'd) : ('a, 'b, 'c) => 'd\n
Opposite to tuplify.
"},{"location":"sophia_stdlib/#frac","title":"Frac","text":"This namespace provides operations on rational numbers. A rational number is represented as a fraction of two integers which are stored internally in the frac
datatype.
The datatype consists of three constructors Neg/2
, Zero/0
and Pos/2
which determine the sign of the number. Both values stored in Neg
and Pos
need to be strictly positive integers. However, when creating a frac
you should never use the constructors explicitly. Instead of that, always use provided functions like make_frac
or from_int
. This helps keeping the internal representation well defined.
The described below functions take care of the normalization of the fractions \u2013 they won't grow if it is unnecessary. Please note that the size of frac
can be still very big while the value is actually very close to a natural number \u2013 the division of two extremely big prime numbers will be as big as both of them. To face this issue the optimize function is provided. It will approximate the value of the fraction to fit in the given error margin and to shrink its size as much as possible.
Important note: frac
must not be compared using standard <
-like operators. The operator comparison is not possible to overload at this moment, nor the language provides checkers to prevent unintended usage of them. Therefore the typechecker will allow that and the results of such comparison will be unspecified. You should use lt, geq, eq etc instead.
"},{"location":"sophia_stdlib/#types_3","title":"Types","text":""},{"location":"sophia_stdlib/#frac_1","title":"frac","text":"datatype frac = Pos(int, int) | Zero | Neg(int, int)\n
Internal representation of fractional numbers. First integer encodes the numerator and the second the denominator \u2013 both must be always positive, as the sign is being handled by the choice of the constructor.
"},{"location":"sophia_stdlib/#functions_3","title":"Functions","text":""},{"location":"sophia_stdlib/#make_frac","title":"make_frac","text":"Frac.make_frac(n : int, d : int) : frac
Creates a fraction out of numerator and denominator. Automatically normalizes, so make_frac(2, 4)
and make_frac(1, 2)
will yield same results.
"},{"location":"sophia_stdlib/#num","title":"num","text":"Frac.num(f : frac) : int
Returns the numerator of a fraction.
"},{"location":"sophia_stdlib/#den","title":"den","text":"Frac.den(f : frac) : int
Returns the denominator of a fraction.
"},{"location":"sophia_stdlib/#to_pair","title":"to_pair","text":"Frac.to_pair(f : frac) : int * int
Turns a fraction into a pair of numerator and denominator.
"},{"location":"sophia_stdlib/#sign","title":"sign","text":"Frac.sign(f : frac) : int
Returns the signum of a fraction, -1, 0, 1 if negative, zero, positive respectively.
"},{"location":"sophia_stdlib/#to_str_3","title":"to_str","text":"Frac.to_str(f : frac) : string
Conversion to string. Does not display division by 1 or denominator if equals zero.
"},{"location":"sophia_stdlib/#simplify","title":"simplify","text":"Frac.simplify(f : frac) : frac
Reduces fraction to normal form if for some reason it is not in it.
"},{"location":"sophia_stdlib/#eq","title":"eq","text":"Frac.eq(a : frac, b : frac) : bool
Checks if a
is equal to b
.
"},{"location":"sophia_stdlib/#neq","title":"neq","text":"Frac.neq(a : frac, b : frac) : bool
Checks if a
is not equal to b
.
"},{"location":"sophia_stdlib/#geq","title":"geq","text":"Frac.geq(a : frac, b : frac) : bool
Checks if a
is greater or equal to b
.
"},{"location":"sophia_stdlib/#leq","title":"leq","text":"Frac.leq(a : frac, b : frac) : bool
Checks if a
is lesser or equal to b
.
"},{"location":"sophia_stdlib/#gt_1","title":"gt","text":"Frac.gt(a : frac, b : frac) : bool
Checks if a
is greater than b
.
"},{"location":"sophia_stdlib/#lt","title":"lt","text":"Frac.lt(a : frac, b : frac) : bool
Checks if a
is lesser than b
.
"},{"location":"sophia_stdlib/#min","title":"min","text":"Frac.min(a : frac, b : frac) : frac
Chooses lesser of the two fractions.
"},{"location":"sophia_stdlib/#max","title":"max","text":"Frac.max(a : frac, b : frac) : frac
Chooses greater of the two fractions.
"},{"location":"sophia_stdlib/#abs","title":"abs","text":"Frac.abs(f : frac) : frac
Absolute value.
"},{"location":"sophia_stdlib/#from_int","title":"from_int","text":"Frac.from_int(n : int) : frac
From integer conversion. Effectively make_frac(n, 1)
.
"},{"location":"sophia_stdlib/#floor","title":"floor","text":"Frac.floor(f : frac) : int
Rounds a fraction to the nearest lesser or equal integer.
"},{"location":"sophia_stdlib/#ceil","title":"ceil","text":"Frac.ceil(f : frac) : int
Rounds a fraction to the nearest greater or equal integer.
"},{"location":"sophia_stdlib/#round_to_zero","title":"round_to_zero","text":"Frac.round_to_zero(f : frac) : int
Rounds a fraction towards zero. Effectively ceil
if lesser than zero and floor
if greater.
"},{"location":"sophia_stdlib/#round_from_zero","title":"round_from_zero","text":"Frac.round_from_zero(f : frac) : int
Rounds a fraction from zero. Effectively ceil
if greater than zero and floor
if lesser.
"},{"location":"sophia_stdlib/#round","title":"round","text":"Frac.round(f : frac) : int
Rounds a fraction to a nearest integer. If two integers are in the same distance it will choose the even one.
"},{"location":"sophia_stdlib/#add","title":"add","text":"Frac.add(a : frac, b : frac) : frac
Sum of the fractions.
"},{"location":"sophia_stdlib/#neg","title":"neg","text":"Frac.neg(a : frac) : frac
Negation of the fraction.
"},{"location":"sophia_stdlib/#sub","title":"sub","text":"Frac.sub(a : frac, b : frac) : frac
Subtraction of two fractions.
"},{"location":"sophia_stdlib/#inv","title":"inv","text":"Frac.inv(a : frac) : frac
Inverts a fraction. Throws error if a
is zero.
"},{"location":"sophia_stdlib/#mul","title":"mul","text":"Frac.mul(a : frac, b : frac) : frac
Multiplication of two fractions.
"},{"location":"sophia_stdlib/#div","title":"div","text":"Frac.div(a : frac, b : frac) : frac
Division of two fractions.
"},{"location":"sophia_stdlib/#int_exp","title":"int_exp","text":"Frac.int_exp(b : frac, e : int) : frac
Takes b
to the power of e
. The exponent can be a negative value.
"},{"location":"sophia_stdlib/#optimize","title":"optimize","text":"Frac.optimize(f : frac, loss : frac) : frac
Shrink the internal size of a fraction as much as possible by approximating it to the point where the error would exceed the loss
value.
"},{"location":"sophia_stdlib/#is_sane","title":"is_sane","text":"Frac.is_sane(f : frac) : bool
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
If you expect getting calls with malformed frac
s in your contract, you should use this function to verify the input.
"},{"location":"sophia_stdlib/#list","title":"List","text":"This module contains common operations on lists like constructing, querying, traversing etc.
"},{"location":"sophia_stdlib/#is_empty","title":"is_empty","text":"List.is_empty(l : list('a)) : bool\n
Returns true
iff the list is equal to []
.
"},{"location":"sophia_stdlib/#first","title":"first","text":"List.first(l : list('a)) : option('a)\n
Returns Some
of the first element of a list or None
if the list is empty.
"},{"location":"sophia_stdlib/#tail","title":"tail","text":"List.tail(l : list('a)) : option(list('a))\n
Returns Some
of a list without its first element or None
if the list is empty.
"},{"location":"sophia_stdlib/#last","title":"last","text":"List.last(l : list('a)) : option('a)\n
Returns Some
of the last element of a list or None
if the list is empty.
"},{"location":"sophia_stdlib/#contains","title":"contains","text":"List.contains(e : 'a, l : list('a)) : bool\n
Checks if list l
contains element e
. Equivalent to List.find(x => x == e, l) != None
."},{"location":"sophia_stdlib/#find","title":"find","text":"List.find(p : 'a => bool, l : list('a)) : option('a)\n
Finds first element of l
fulfilling predicate p
as Some
or None
if no such element exists.
"},{"location":"sophia_stdlib/#find_indices","title":"find_indices","text":"List.find_indices(p : 'a => bool, l : list('a)) : list(int)\n
Returns list of all indices of elements from l
that fulfill the predicate p
.
"},{"location":"sophia_stdlib/#nth","title":"nth","text":"List.nth(n : int, l : list('a)) : option('a)\n
Gets n
th element of l
as Some
or None
if l
is shorter than n + 1
or n
is negative.
"},{"location":"sophia_stdlib/#get","title":"get","text":"List.get(n : int, l : list('a)) : 'a\n
Gets n
th element of l
forcefully, throwing and error if l
is shorter than n + 1
or n
is negative.
"},{"location":"sophia_stdlib/#length","title":"length","text":"List.length(l : list('a)) : int\n
Returns length of a list.
"},{"location":"sophia_stdlib/#from_to","title":"from_to","text":"List.from_to(a : int, b : int) : list(int)\n
Creates an ascending sequence of all integer numbers between a
and b
(including a
and b
).
"},{"location":"sophia_stdlib/#from_to_step","title":"from_to_step","text":"List.from_to_step(a : int, b : int, step : int) : list(int)\n
Creates an ascending sequence of integer numbers betweeen a
and b
jumping by given step
. Includes a
and takes b
only if (b - a) mod step == 0
. step
should be bigger than 0.
"},{"location":"sophia_stdlib/#replace_at","title":"replace_at","text":"List.replace_at(n : int, e : 'a, l : list('a)) : list('a)\n
Replaces n
th element of l
with e
. Throws an error if n
is negative or would cause an overflow.
"},{"location":"sophia_stdlib/#insert_at","title":"insert_at","text":"List.insert_at(n : int, e : 'a, l : list('a)) : list('a)\n
Inserts e
into l
to be on position n
by shifting following elements further. For instance,
insert_at(2, 9, [1,2,3,4])\n
will yield [1,2,9,3,4]
."},{"location":"sophia_stdlib/#insert_by","title":"insert_by","text":"List.insert_by(cmp : (('a, 'a) => bool), x : 'a, l : list('a)) : list('a)\n
Assuming that cmp represents <
comparison, inserts x
before the first element in the list l
which is greater than it. For instance,
insert_by((a, b) => a < b, 4, [1,2,3,5,6,7])\n
will yield [1,2,3,4,5,6,7]
"},{"location":"sophia_stdlib/#foldr","title":"foldr","text":"List.foldr(cons : ('a, 'b) => 'b, nil : 'b, l : list('a)) : 'b\n
Right fold of a list. Assuming l = [x, y, z]
will return f(x, f(y, f(z, nil)))
. Not tail recursive.
"},{"location":"sophia_stdlib/#foldl","title":"foldl","text":"List.foldl(rcons : ('b, 'a) => 'b, acc : 'b, l : list('a)) : 'b\n
Left fold of a list. Assuming l = [x, y, z]
will return f(f(f(acc, x), y), z)
. Tail recursive.
"},{"location":"sophia_stdlib/#foreach","title":"foreach","text":"List.foreach(l : list('a), f : 'a => unit) : unit\n
Evaluates f
on each element of a list.
"},{"location":"sophia_stdlib/#reverse","title":"reverse","text":"List.reverse(l : list('a)) : list('a)\n
Returns a copy of l
with reversed order of elements.
"},{"location":"sophia_stdlib/#map_1","title":"map","text":"List.map(f : 'a => 'b, l : list('a)) : list('b)\n
Maps function f
over a list. For instance
map((x) => x == 0, [1, 2, 0, 3, 0])\n
will yield [false, false, true, false, true]
"},{"location":"sophia_stdlib/#flat_map","title":"flat_map","text":"List.flat_map(f : 'a => list('b), l : list('a)) : list('b)\n
Maps f
over a list and then flattens it. For instance
flat_map((x) => [x, x * 10], [1, 2, 3])\n
will yield [1, 10, 2, 20, 3, 30]
"},{"location":"sophia_stdlib/#filter","title":"filter","text":"List.filter(p : 'a => bool, l : list('a)) : list('a)\n
Filters out elements of l
that fulfill predicate p
. For instance
filter((x) => x > 0, [-1, 1, -2, 0, 1, 2, -3])\n
will yield [1, 1, 2]
"},{"location":"sophia_stdlib/#take","title":"take","text":"List.take(n : int, l : list('a)) : list('a)\n
Takes n
first elements of l
. Fails if n
is negative. If n
is greater than length of a list it will return whole list.
"},{"location":"sophia_stdlib/#drop","title":"drop","text":"List.drop(n : int, l : list('a)) : list('a)\n
Removes n
first elements of l
. Fails if n
is negative. If n
is greater than length of a list it will return []
.
"},{"location":"sophia_stdlib/#take_while","title":"take_while","text":"List.take_while(p : 'a => bool, l : list('a)) : list('a)\n
Returns longest prefix of l
in which all elements fulfill p
.
"},{"location":"sophia_stdlib/#drop_while","title":"drop_while","text":"List.drop_while(p : 'a => bool, l : list('a)) : list('a)\n
Removes longest prefix from l
in which all elements fulfill p
.
"},{"location":"sophia_stdlib/#partition","title":"partition","text":"List.partition(p : 'a => bool, l : list('a)) : (list('a) * list('a))\n
Separates elements of l
that fulfill p
and these that do not. Elements fulfilling predicate will be in the right list. For instance
partition((x) => x > 0, [-1, 1, -2, 0, 1, 2, -3])\n
will yield ([1, 1, 2], [-1, -2, 0, -3])
"},{"location":"sophia_stdlib/#flatten","title":"flatten","text":"List.flatten(ll : list(list('a))) : list('a)\n
Flattens a list of lists into a one list.
"},{"location":"sophia_stdlib/#all_1","title":"all","text":"List.all(p : 'a => bool, l : list('a)) : bool\n
Checks if all elements of a list fulfill predicate p
.
"},{"location":"sophia_stdlib/#any","title":"any","text":"List.any(p : 'a => bool, l : list('a)) : bool\n
Checks if any element of a list fulfills predicate p
.
"},{"location":"sophia_stdlib/#sum_1","title":"sum","text":"List.sum(l : list(int)) : int\n
Sums elements of a list. Returns 0 if the list is empty.
"},{"location":"sophia_stdlib/#product","title":"product","text":"List.product(l : list(int)) : int\n
Multiplies elements of a list. Returns 1 if the list is empty.
"},{"location":"sophia_stdlib/#zip_with","title":"zip_with","text":"List.zip_with(f : ('a, 'b) => 'c, l1 : list('a), l2 : list('b)) : list('c)\n
\"zips\" two lists with a function. n-th element of resulting list will be equal to f(x1, x2)
where x1
and x2
are n-th elements of l1
and l2
respectively. Will cut off the tail of the longer list. For instance
zip_with((a, b) => a + b, [1,2], [1,2,3])\n
will yield [2,4]
"},{"location":"sophia_stdlib/#zip","title":"zip","text":"List.zip(l1 : list('a), l2 : list('b)) : list('a * 'b)\n
Special case of zip_with where the zipping function is (a, b) => (a, b)
.
"},{"location":"sophia_stdlib/#unzip","title":"unzip","text":"List.unzip(l : list('a * 'b)) : list('a) * list('b)\n
Opposite to the zip
operation. Takes a list of pairs and returns pair of lists with respective elements on same indices.
"},{"location":"sophia_stdlib/#merge","title":"merge","text":"List.merge(lesser_cmp : ('a, 'a) => bool, l1 : list('a), l2 : list('a)) : list('a)\n
Merges two sorted lists into a single sorted list. O(length(l1) + length(l2))
"},{"location":"sophia_stdlib/#sort","title":"sort","text":"List.sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a)\n
Sorts a list using given comparator. lesser_cmp(x, y)
should return true
iff x < y
. If lesser_cmp
is not transitive or there exists an element x
such that lesser_cmp(x, x)
or there exists a pair of elements x
and y
such that lesser_cmp(x, y) && lesser_cmp(y, x)
then the result is undefined. O(length(l) * log_2(length(l))).
"},{"location":"sophia_stdlib/#intersperse","title":"intersperse","text":"List.intersperse(delim : 'a, l : list('a)) : list('a)\n
Intersperses elements of l
with delim
. Does nothing on empty lists and singletons. For instance
intersperse(0, [1, 2, 3, 4])\n
will yield [1, 0, 2, 0, 3, 0, 4]
"},{"location":"sophia_stdlib/#enumerate","title":"enumerate","text":"List.enumerate(l : list('a)) : list(int * 'a)\n
Equivalent to zip with [0..length(l)]
, but slightly faster.
"},{"location":"sophia_stdlib/#option","title":"Option","text":"Common operations on option
types and lists of option
s.
"},{"location":"sophia_stdlib/#is_none","title":"is_none","text":"Option.is_none(o : option('a)) : bool\n
Returns true iff o == None
"},{"location":"sophia_stdlib/#is_some","title":"is_some","text":"Option.is_some(o : option('a)) : bool\n
Returns true iff o
is not None
.
"},{"location":"sophia_stdlib/#match","title":"match","text":"Option.match(n : 'b, s : 'a => 'b, o : option('a)) : 'b\n
Behaves like pattern matching on option
using two case functions.
"},{"location":"sophia_stdlib/#default","title":"default","text":"Option.default(def : 'a, o : option('a)) : 'a\n
Escapes option
wrapping by providing default value for None
.
"},{"location":"sophia_stdlib/#force","title":"force","text":"Option.force(o : option('a)) : 'a\n
Forcefully escapes the option
wrapping assuming it is Some
. Aborts on None
.
"},{"location":"sophia_stdlib/#force_msg","title":"force_msg","text":"Option.force_msg(o : option('a), err : string) : 'a\n
Forcefully escapes the option
wrapping assuming it is Some
. Aborts with err
error message on None
.
"},{"location":"sophia_stdlib/#contains_1","title":"contains","text":"Option.contains(e : 'a, o : option('a)) : bool\n
Returns true
if and only if o
contains element equal to e
. Equivalent to Option.match(false, x => x == e, o)
."},{"location":"sophia_stdlib/#on_elem","title":"on_elem","text":"Option.on_elem(o : option('a), f : 'a => unit) : unit\n
Evaluates f
on element under Some
. Does nothing on None
.
"},{"location":"sophia_stdlib/#map_2","title":"map","text":"Option.map(f : 'a => 'b, o : option('a)) : option('b)\n
Maps element under Some
. Leaves None
unchanged.
"},{"location":"sophia_stdlib/#map2","title":"map2","text":"Option.map2(f : ('a, 'b) => 'c, o1 : option('a), o2 : option('b)) : option('c)\n
Applies arity 2 function over two option
s' elements. Returns Some
iff both of o1
and o2
were Some
, or None
otherwise. For instance
map2((a, b) => a + b, Some(1), Some(2))\n
will yield Some(3)
and map2((a, b) => a + b, Some(1), None)\n
will yield None
."},{"location":"sophia_stdlib/#map3","title":"map3","text":"Option.map3(f : ('a, 'b, 'c) => 'd, o1 : option('a), o2 : option('b), o3 : option('c)) : option('d)\n
Same as map2 but with arity 3 function.
"},{"location":"sophia_stdlib/#app_over","title":"app_over","text":"Option.app_over(f : option ('a => 'b), o : option('a)) : option('b)\n
Applies function under option
over argument under option
. If either of them is None
the result will be None
as well. For instance
app_over(Some((x) => x + 1), Some(1))\n
will yield Some(2)
and app_over(Some((x) => x + 1), None)\n
will yield None
."},{"location":"sophia_stdlib/#flat_map_1","title":"flat_map","text":"Option.flat_map(f : 'a => option('b), o : option('a)) : option('b)\n
Performs monadic bind on an option
. Extracts element from o
(if present) and forms new option
from it. For instance
flat_map((x) => Some(x + 1), Some(1))\n
will yield Some(2)
and flat_map((x) => Some(x + 1), None)\n
will yield None
."},{"location":"sophia_stdlib/#to_list_1","title":"to_list","text":"Option.to_list(o : option('a)) : list('a)\n
Turns o
into an empty (if None
) or singleton (if Some
) list.
"},{"location":"sophia_stdlib/#filter_options","title":"filter_options","text":"Option.filter_options(l : list(option('a))) : list('a)\n
Removes None
s from list and unpacks all remaining Some
s. For instance
filter_options([Some(1), None, Some(2)])\n
will yield [1, 2]
."},{"location":"sophia_stdlib/#seq_options","title":"seq_options","text":"Option.seq_options(l : list (option('a))) : option (list('a))\n
Tries to unpack all elements of a list from Some
s. Returns None
if at least element of l
is None
. For instance
seq_options([Some(1), Some(2)])\n
will yield Some([1, 2])
, but seq_options([Some(1), Some(2), None])\n
will yield None
."},{"location":"sophia_stdlib/#choose","title":"choose","text":"Option.choose(o1 : option('a), o2 : option('a)) : option('a)\n
Out of two option
s choose the one that is Some
, or None
if both are None
s.
"},{"location":"sophia_stdlib/#choose_first","title":"choose_first","text":"Option.choose_first(l : list(option('a))) : option('a)\n
Same as choose, but chooses from a list insted of two arguments.
"},{"location":"sophia_stdlib/#pair","title":"Pair","text":"Common operations on 2-tuples.
"},{"location":"sophia_stdlib/#fst","title":"fst","text":"Pair.fst(t : ('a * 'b)) : 'a\n
First element projection.
"},{"location":"sophia_stdlib/#snd","title":"snd","text":"Pair.snd(t : ('a * 'b)) : 'b\n
Second element projection.
"},{"location":"sophia_stdlib/#map1","title":"map1","text":"Pair.map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b)\n
Applies function over first element.
"},{"location":"sophia_stdlib/#map2_1","title":"map2","text":"Pair.map2(f : 'b => 'c, t : ('a * 'b)) : ('a * 'c)\n
Applies function over second element.
"},{"location":"sophia_stdlib/#bimap","title":"bimap","text":"Pair.bimap(f : 'a => 'c, g : 'b => 'd, t : ('a * 'b)) : ('c * 'd)\n
Applies functions over respective elements.
"},{"location":"sophia_stdlib/#swap","title":"swap","text":"Pair.swap(t : ('a * 'b)) : ('b * 'a)\n
Swaps elements.
"},{"location":"sophia_stdlib/#set_1","title":"Set","text":""},{"location":"sophia_stdlib/#types_4","title":"Types","text":"record set('a) = { to_map : map('a, unit) }\n
"},{"location":"sophia_stdlib/#functions_4","title":"Functions","text":""},{"location":"sophia_stdlib/#new","title":"new","text":"Set.new() : set('a)\n
Returns an empty set
"},{"location":"sophia_stdlib/#member_1","title":"member","text":"member(e : 'a, s : set('a)) : bool\n
Checks if the element e
is present in the set s
"},{"location":"sophia_stdlib/#insert","title":"insert","text":"insert(e : 'a, s : set('a)) : set('a)\n
Inserts the element e
in the set s
"},{"location":"sophia_stdlib/#delete_1","title":"delete","text":"Set.delete(e : 'a, s : set('a)) : set('a)\n
Removes the element e
from the set s
"},{"location":"sophia_stdlib/#size_1","title":"size","text":"size(s : set('a)) : int\n
Returns the number of elements in the set s
"},{"location":"sophia_stdlib/#to_list_2","title":"to_list","text":"Set.to_list(s : set('a)) : list('a)\n
Returns a list containing the elements of the set s
"},{"location":"sophia_stdlib/#from_list_1","title":"from_list","text":"Set.from_list(l : list('a)) : set('a)\n
Turns the list l
into a set
"},{"location":"sophia_stdlib/#filter_1","title":"filter","text":"Set.filter(p : 'a => bool, s : set('a)) : set('a)\n
Filters out elements of s
that fulfill predicate p
"},{"location":"sophia_stdlib/#fold","title":"fold","text":"Set.fold(f : ('a, 'b) => 'b, acc : 'b, s : set('a)) : 'b\n
Folds the function f
over every element in the set s
and returns the final value of the accumulator acc
.
"},{"location":"sophia_stdlib/#subtract","title":"subtract","text":"Set.subtract(s1 : set('a), s2 : set('a)) : set('a)\n
Returns the elements of s1
that are not members of s2
"},{"location":"sophia_stdlib/#intersection_1","title":"intersection","text":"Set.intersection(s1 : set('a), s2 : set('a)) : set('a)\n
Returns the intersection of the two sets s1
and s2
"},{"location":"sophia_stdlib/#intersection_list","title":"intersection_list","text":"Set.intersection_list(sets : list(set('a))) : set('a)\n
Returns the intersection of all the sets in the given list
"},{"location":"sophia_stdlib/#union_1","title":"union","text":"Set.union(s1 : set('a), s2 : set('a)) : set('a)\n
Returns the union of the two sets s1
and s2
"},{"location":"sophia_stdlib/#union_list","title":"union_list","text":"Set.union_list(sets : list(set('a))) : set('a)\n
Returns the union of all the sets in the given list
"},{"location":"sophia_stdlib/#string","title":"String","text":"Operations on the string
type. A string
is a UTF-8 encoded byte array.
"},{"location":"sophia_stdlib/#length_1","title":"length","text":"length(s : string) : int
The length of a string.
Note: not equivalent to byte size of the string, rather List.length(String.to_list(s))
"},{"location":"sophia_stdlib/#concat_1","title":"concat","text":"concat(s1 : string, s2 : string) : string\n
Concatenates s1
and s2
.
"},{"location":"sophia_stdlib/#concats","title":"concats","text":"concats(ss : list(string)) : string\n
Concatenates a list of strings.
"},{"location":"sophia_stdlib/#to_list_3","title":"to_list","text":"to_list(s : string) : list(char)\n
Converts a string
to a list of char
- the code points are normalized, but composite characters are possibly converted to multiple char
s. For example the string \"\ud83d\ude1ci\u0307\" is converted to [128540,105,775]
- where the smiley is the first code point and the strangely dotted i
becomes [105, 775]
.
"},{"location":"sophia_stdlib/#from_list_2","title":"from_list","text":"from_list(cs : list(char)) : string\n
Converts a list of characters into a normalized UTF-8 string.
"},{"location":"sophia_stdlib/#to_lower","title":"to_lower","text":"to_lower(s : string) : string\n
Converts a string to lowercase.
"},{"location":"sophia_stdlib/#to_upper","title":"to_upper","text":"to_upper(s : string) : string\n
Converts a string to uppercase.
"},{"location":"sophia_stdlib/#at","title":"at","text":"at(ix : int, s : string) : option(char)\n
Returns the character/codepoint at (zero-based) index ix
. Basically the equivalent to List.nth(ix, String.to_list(s))
.
"},{"location":"sophia_stdlib/#split_1","title":"split","text":"split(ix : int, s:string) : string * string\n
Splits a string at (zero-based) index ix
.
"},{"location":"sophia_stdlib/#contains_2","title":"contains","text":"contains(str : string, pat : string) : option(int)\n
Searches for pat
in str
, returning Some(ix)
if pat
is a substring of str
starting at position ix
, otherwise returns None
.
"},{"location":"sophia_stdlib/#tokens","title":"tokens","text":"tokens(str : string, pat : string) : list(string)\n
Splits str
into tokens, pat
is the divider of tokens.
"},{"location":"sophia_stdlib/#to_int_2","title":"to_int","text":"to_int(s : string) : option(int)\n
Converts a decimal (\"123\", \"-253\") or a hexadecimal (\"0xa2f\", \"-0xBBB\") string into an integer. If the string doesn't contain a valid number None
is returned.
"},{"location":"sophia_stdlib/#sha3_1","title":"sha3","text":"sha3(s : string) : hash\n
Computes the SHA3/Keccak hash of the string.
"},{"location":"sophia_stdlib/#sha256_1","title":"sha256","text":"sha256(s : string) : hash\n
Computes the SHA256 hash of the string.
"},{"location":"sophia_stdlib/#blake2b_1","title":"blake2b","text":"blake2b(s : string) : hash\n
Computes the Blake2B hash of the string.
"},{"location":"sophia_stdlib/#triple","title":"Triple","text":""},{"location":"sophia_stdlib/#fst_1","title":"fst","text":"Triple.fst(t : ('a * 'b * 'c)) : 'a\n
First element projection.
"},{"location":"sophia_stdlib/#snd_1","title":"snd","text":"Triple.snd(t : ('a * 'b * 'c)) : 'b\n
Second element projection.
"},{"location":"sophia_stdlib/#thd","title":"thd","text":"Triple.thd(t : ('a * 'b * 'c)) : 'c\n
Third element projection.
"},{"location":"sophia_stdlib/#map1_1","title":"map1","text":"Triple.map1(f : 'a => 'm, t : ('a * 'b * 'c)) : ('m * 'b * 'c)\n
Applies function over first element.
"},{"location":"sophia_stdlib/#map2_2","title":"map2","text":"Triple.map2(f : 'b => 'm, t : ('a * 'b * 'c)) : ('a * 'm * 'c)\n
Applies function over second element.
"},{"location":"sophia_stdlib/#map3_1","title":"map3","text":"Triple.map3(f : 'c => 'm, t : ('a * 'b * 'c)) : ('a * 'b * 'm)\n
Applies function over third element.
"},{"location":"sophia_stdlib/#trimap","title":"trimap","text":"Triple.trimap(f : 'a => 'x, g : 'b => 'y, h : 'c => 'z, t : ('a * 'b * 'c)) : ('x * 'y * 'z)\n
Applies functions over respective elements.
"},{"location":"sophia_stdlib/#swap_1","title":"swap","text":"Triple.swap(t : ('a * 'b * 'c)) : ('c * 'b * 'a)\n
Swaps first and third element.
"},{"location":"sophia_stdlib/#rotr","title":"rotr","text":"Triple.rotr(t : ('a * 'b * 'c)) : ('c * 'a * 'b)\n
Cyclic rotation of the elements to the right.
"},{"location":"sophia_stdlib/#rotl","title":"rotl","text":"Triple.rotl(t : ('a * 'b * 'c)) : ('b * 'c * 'a)\n
Cyclic rotation of the elements to the left.
"},{"location":"sophia_syntax/","title":"Syntax","text":""},{"location":"sophia_syntax/#lexical-syntax","title":"Lexical syntax","text":""},{"location":"sophia_syntax/#comments","title":"Comments","text":"Single line comments start with //
and block comments are enclosed in /*
and */
and can be nested.
"},{"location":"sophia_syntax/#keywords","title":"Keywords","text":"contract include let switch type record datatype if elif else function\nstateful payable true false mod public entrypoint private indexed namespace\ninterface main using as for hiding\n
"},{"location":"sophia_syntax/#tokens","title":"Tokens","text":" Id = [a-z_][A-Za-z0-9_']*
identifiers start with a lower case letter. Con = [A-Z][A-Za-z0-9_']*
constructors start with an upper case letter. QId = (Con\\.)+Id
qualified identifiers (e.g. Map.member
) QCon = (Con\\.)+Con
qualified constructor TVar = 'Id
type variable (e.g 'a
, 'b
) Int = [0-9]+(_[0-9]+)*|0x[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*
integer literal with optional _
separators Bytes = #[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*
byte array literal with optional _
separators String
string literal enclosed in \"
with escape character \\
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
Valid string escape codes are
Escape ASCII \\b
8 \\t
9 \\n
10 \\v
11 \\f
12 \\r
13 \\e
27 \\xHexDigits
HexDigits See the identifier encoding scheme for the details on the base58 literals.
"},{"location":"sophia_syntax/#layout-blocks","title":"Layout blocks","text":"Sophia uses Python-style layout rules to group declarations and statements. A layout block with more than one element must start on a separate line and be indented more than the currently enclosing layout block. Blocks with a single element can be written on the same line as the previous token.
Each element of the block must share the same indentation and no part of an element may be indented less than the indentation of the block. For instance
contract Layout =\nfunction foo() = 0 // no layout\nfunction bar() = // layout block starts on next line\nlet x = foo() // indented more than 2 spaces\nx\n+ 1 // the '+' is indented more than the 'x'\n
"},{"location":"sophia_syntax/#notation","title":"Notation","text":"In describing the syntax below, we use the following conventions:
- Upper-case identifiers denote non-terminals (like
Expr
) or terminals with some associated value (like Id
). - Keywords and symbols are enclosed in single quotes:
'let'
or '='
. - Choices are separated by vertical bars:
|
. - Optional elements are enclosed in
[
square brackets ]
. (
Parentheses )
are used for grouping. - Zero or more repetitions are denoted by a postfix
*
, and one or more repetitions by a +
. Block(X)
denotes a layout block of X
s. Sep(X, S)
is short for [X (S X)*]
, i.e. a possibly empty sequence of X
s separated by S
s. Sep1(X, S)
is short for X (S X)*
, i.e. same as Sep
, but must not be empty.
"},{"location":"sophia_syntax/#declarations","title":"Declarations","text":"A Sophia file consists of a sequence of declarations in a layout block.
File ::= Block(TopDecl)\n\nTopDecl ::= ['payable'] ['main'] 'contract' Con [Implement] '=' Block(Decl)\n| 'contract' 'interface' Con [Implement] '=' Block(Decl)\n| 'namespace' Con '=' Block(Decl)\n| '@compiler' PragmaOp Version\n| 'include' String\n| Using\n\nImplement ::= ':' Sep1(Con, ',')\n\nDecl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias\n| 'record' Id ['(' TVar* ')'] '=' RecordType\n| 'datatype' Id ['(' TVar* ')'] '=' DataType\n| 'let' Id [':' Type] '=' Expr\n| (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)\n| Using\n\nFunDecl ::= Id ':' Type // Type signature\n| Id Args [':' Type] '=' Block(Stmt) // Definition\n| Id Args [':' Type] Block(GuardedDef) // Guarded definitions\n\nGuardedDef ::= '|' Sep1(Expr, ',') '=' Block(Stmt)\n\nUsing ::= 'using' Con ['as' Con] [UsingParts]\nUsingParts ::= 'for' '[' Sep1(Id, ',') ']'\n| 'hiding' '[' Sep1(Id, ',') ']'\n\nPragmaOp ::= '<' | '=<' | '==' | '>=' | '>'\nVersion ::= Sep1(Int, '.')\n\nEModifier ::= 'payable' | 'stateful'\nFModifier ::= 'stateful' | 'private'\n\nArgs ::= '(' Sep(Pattern, ',') ')'\n
Contract declarations must appear at the top-level.
For example,
contract Test =\ntype t = int\nentrypoint add (x : t, y : t) = x + y\n
There are three forms of type declarations: type aliases (declared with the type
keyword), record type definitions (record
) and data type definitions (datatype
):
TypeAlias ::= Type\nRecordType ::= '{' Sep(FieldType, ',') '}'\nDataType ::= Sep1(ConDecl, '|')\n\nFieldType ::= Id ':' Type\nConDecl ::= Con ['(' Sep1(Type, ',') ')']\n
For example,
record point('a) = {x : 'a, y : 'a}\ndatatype shape('a) = Circle(point('a), 'a) | Rect(point('a), point('a))\ntype int_shape = shape(int)\n
"},{"location":"sophia_syntax/#types","title":"Types","text":"Type ::= Domain '=>' Type // Function type\n| Type '(' Sep(Type, ',') ')' // Type application\n| '(' Type ')' // Parens\n| 'unit' | Sep(Type, '*') // Tuples\n| Id | QId | TVar\n\nDomain ::= Type // Single argument\n| '(' Sep(Type, ',') ')' // Multiple arguments\n
The function type arrow associates to the right.
Example,
'a => list('a) => (int * list('a))\n
"},{"location":"sophia_syntax/#statements","title":"Statements","text":"Function bodies are blocks of statements, where a statement is one of the following
Stmt ::= 'switch' '(' Expr ')' Block(Case)\n| 'if' '(' Expr ')' Block(Stmt)\n| 'elif' '(' Expr ')' Block(Stmt)\n| 'else' Block(Stmt)\n| 'let' LetDef\n| Using\n| Expr\n\nLetDef ::= Id Args [':' Type] '=' Block(Stmt) // Function definition\n| Pattern '=' Block(Stmt) // Value definition\n\nCase ::= Pattern '=>' Block(Stmt)\n| Pattern Block(GuardedCase)\n\nGuardedCase ::= '|' Sep1(Expr, ',') '=>' Block(Stmt)\n\nPattern ::= Expr\n
if
statements can be followed by zero or more elif
statements and an optional final else
statement. For example,
let x : int = 4\nswitch(f(x))\nNone => 0\nSome(y) =>\nif(y > 10)\n\"too big\"\nelif(y < 3)\n\"too small\"\nelse\n\"just right\"\n
"},{"location":"sophia_syntax/#expressions","title":"Expressions","text":"Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + 1\n| '(' BinOp ')' // Operator lambda (+)\n| 'if' '(' Expr ')' Expr 'else' Expr // If expression if(x < y) y else x\n| Expr ':' Type // Type annotation 5 : int\n| Expr BinOp Expr // Binary operator x + y\n| UnOp Expr // Unary operator ! b\n| Expr '(' Sep(Expr, ',') ')' // Application f(x, y)\n| Expr '.' Id // Projection state.x\n| Expr '[' Expr ']' // Map lookup map[key]\n| Expr '{' Sep(FieldUpdate, ',') '}' // Record or map update r{ fld[key].x = y }\n| '[' Sep(Expr, ',') ']' // List [1, 2, 3]\n| '[' Expr '|' Sep(Generator, ',') ']'\n// List comprehension [k | x <- [1], if (f(x)), let k = x+1]\n| '[' Expr '..' Expr ']' // List range [1..n]\n| '{' Sep(FieldUpdate, ',') '}' // Record or map value {x = 0, y = 1}, {[key] = val}\n| '(' Expr ')' // Parens (1 + 2) * 3\n| '(' Expr '=' Expr ')' // Assign pattern (y = x::_)\n| Id | Con | QId | QCon // Identifiers x, None, Map.member, AELib.Token\n| Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, \"foo\", '%'\n| AccountAddress | ContractAddress // Chain identifiers\n| OracleAddress | OracleQueryId // Chain identifiers\n| '???' // Hole expression 1 + ???\n\nGenerator ::= Pattern '<-' Expr // Generator\n| 'if' '(' Expr ')' // Guard\n| LetDef // Definition\n\nLamArgs ::= '(' Sep(LamArg, ',') ')'\nLamArg ::= Id [':' Type]\n\nFieldUpdate ::= Path '=' Expr\nPath ::= Id // Record field\n| '[' Expr ']' // Map key\n| Path '.' Id // Nested record field\n| Path '[' Expr ']' // Nested map key\n\nBinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!='\n| '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^'\n| '|>'\nUnOp ::= '-' | '!'\n
"},{"location":"sophia_syntax/#operators-types","title":"Operators types","text":"Operators Type -
+
*
/
mod
^
arithmetic operators !
&&
||
logical operators ==
!=
<
>
=<
>=
comparison operators ::
++
list operators |>
functional operators"},{"location":"sophia_syntax/#operator-precedence","title":"Operator precedence","text":"In order of highest to lowest precedence.
Operators Associativity !
right ^
left *
/
mod
left -
(unary) right +
-
left ::
++
right <
>
=<
>=
==
!=
none &&
right ||
right |>
left"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Introduction","text":"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 \u00e6ternity blockchain specific primitives, constructions and types have been added.
Note
- For rapid prototyping of smart contracts check out AEstudio!
- For playing around and diving deeper into the language itself check out the REPL!
"},{"location":"CHANGELOG/","title":"Changelog","text":"All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
"},{"location":"CHANGELOG/#unreleased","title":"Unreleased","text":""},{"location":"CHANGELOG/#added","title":"Added","text":""},{"location":"CHANGELOG/#changed","title":"Changed","text":""},{"location":"CHANGELOG/#removed","title":"Removed","text":""},{"location":"CHANGELOG/#fixed","title":"Fixed","text":""},{"location":"CHANGELOG/#730","title":"7.3.0","text":""},{"location":"CHANGELOG/#fixed_1","title":"Fixed","text":" - Fixed a bug with polymorphism that allowed functions with the same name but different type to be considered as implementations for their corresponding interface function.
- Fixed a bug in the byte code optimization that incorrectly reordered dependent instructions.
"},{"location":"CHANGELOG/#721","title":"7.2.1","text":""},{"location":"CHANGELOG/#fixed_2","title":"Fixed","text":" - Fixed bugs with the newly added debugging symbols
"},{"location":"CHANGELOG/#720","title":"7.2.0","text":""},{"location":"CHANGELOG/#added_1","title":"Added","text":""},{"location":"CHANGELOG/#removed_1","title":"Removed","text":" - Remove the mapping from variables to FATE registers from the compilation output.
"},{"location":"CHANGELOG/#fixed_3","title":"Fixed","text":" - Warning about unused include when there is no include.
"},{"location":"CHANGELOG/#710","title":"7.1.0","text":""},{"location":"CHANGELOG/#added_2","title":"Added","text":""},{"location":"CHANGELOG/#changed_1","title":"Changed","text":" - Type definitions serialised to ACI as
typedefs
field instead of type_defs
to increase compatibility. - Check contracts and entrypoints modifiers when implementing interfaces.
- Contracts can no longer be used as namespaces.
- Do not show unused stateful warning for functions that call other contracts with a non-zero value argument.
"},{"location":"CHANGELOG/#fixed_4","title":"Fixed","text":" - Typechecker crashes if Chain.create or Chain.clone are used without arguments.
"},{"location":"CHANGELOG/#701","title":"7.0.1","text":""},{"location":"CHANGELOG/#added_3","title":"Added","text":" - Add CONTRIBUTING.md file.
"},{"location":"CHANGELOG/#changed_2","title":"Changed","text":" - Update Sophia syntax docs to include missing information about existing syntax.
"},{"location":"CHANGELOG/#fixed_5","title":"Fixed","text":" - 404 Contract polymorphism crashes on non-obvious child contract typing.
"},{"location":"CHANGELOG/#700","title":"7.0.0","text":""},{"location":"CHANGELOG/#added_4","title":"Added","text":" - Added support for
EXIT
opcode via exit : (string) => 'a
function (behaves same as ABORT
, but consumes all gas). - Compiler warnings for the following: shadowing, negative spends, division by zero, unused functions, unused includes, unused stateful annotations, unused variables, unused parameters, unused user-defined type, dead return value.
- The pipe operator |>
[1, 2, 3] |> List.first |> Option.is_some // Option.is_some(List.first([1, 2, 3]))\n
- Allow binary operators to be used as lambdas
function sum(l : list(int)) : int = foldl((+), 0, l)\nfunction logical_and(x, y) = (&&)(x, y)\n
- Contract interfaces polymorphism
"},{"location":"CHANGELOG/#changed_3","title":"Changed","text":" - Error messages have been restructured (less newlines) to provide more unified errors. Also
pp_oneline/1
has been added. - Ban empty record definitions (e.g.
record r = {}
would give an error).
"},{"location":"CHANGELOG/#removed_2","title":"Removed","text":" - Support for AEVM has been entirely wiped
"},{"location":"CHANGELOG/#610-2021-10-20","title":"6.1.0 - 2021-10-20","text":""},{"location":"CHANGELOG/#added_5","title":"Added","text":""},{"location":"CHANGELOG/#changed_4","title":"Changed","text":" - Fixed the ACI renderer, it shouldn't drop the
stateful
modifier
"},{"location":"CHANGELOG/#602-2021-07-05","title":"6.0.2 2021-07-05","text":""},{"location":"CHANGELOG/#changed_5","title":"Changed","text":" List.from_to_step
now forbids non-positive step (this change does not alter the behavior of the previously deployed contracts) - Fixed leaking state between contracts
"},{"location":"CHANGELOG/#601-2021-06-24","title":"6.0.1 2021-06-24","text":""},{"location":"CHANGELOG/#changed_6","title":"Changed","text":" - Fixed a bug in calldata encoding for contracts containing multiple contracts
- Fixed a missing
include
in the Frac
standard library
"},{"location":"CHANGELOG/#600-2021-05-26","title":"6.0.0 2021-05-26","text":""},{"location":"CHANGELOG/#added_6","title":"Added","text":" - Child contracts
Chain.clone
Chain.create
Chain.bytecode_hash
- Minor support for variadic functions
void
type that represents an empty type Call.fee
builtin
"},{"location":"CHANGELOG/#changed_7","title":"Changed","text":" - 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
"},{"location":"CHANGELOG/#removed_3","title":"Removed","text":""},{"location":"CHANGELOG/#500-2021-04-30","title":"5.0.0 2021-04-30","text":""},{"location":"CHANGELOG/#added_7","title":"Added","text":" There are also convenience functions split
, concat
, to_upper
, to_lower
, etc.
All String functions in FATEv2 operate on unicode code points. - Operations for pairing-based cryptography has been added the operations are in the standard library BLS12_381. With these operations it is possible to do Zero Knowledge-proofs, etc. The operations are for the BLS12-381 curve (as the name suggests). - Calls to functions in other contracts (i.e. remote calls) can now be protected
. If a contract call fails for any reason (for instance, the remote contract crashes or runs out of gas, or the entrypoint doesn't exist or has the wrong type) the parent call also fails. To make it possible to recover from failures, contract calls takes a named argument protected : bool
(default false
).
If protected = true
the result of the contract call is wrapped in an option
, and Some(value)
indicates a succesful execution and None
indicates that the contract call failed. Note: any gas consumed until the failure is still charged, but all side effects in the remote contract are rolled back on failure. - A new chain operation AENS.update
is supported. - New chain exploring operations AENS.lookup
and Oracle.expiry
to look up an AENS record and the expiry of an Oracle respectively, are added. - Transaction introspection (Auth.tx
) has been added. When a Generalized account is authorized, the authorization function needs access to the transaction (and the transaction hash) for the wrapped transaction. The transaction and the transaction hash is available Auth.tx
, it is only available during authentication if invoked by a normal contract call it returns None
. Example:
switch(Auth.tx)\n None => abort(\"Not in Auth context\")\n Some(tx0) =>\n switch(tx0.tx)\n Chain.SpendTx(_, amount, _) => amount > 400\n Chain.ContractCallTx(_, _) => true\n _ => false\n
- A debug mode is a added to the compiler. Right now its only use is to turn off hermetization."},{"location":"CHANGELOG/#changed_8","title":"Changed","text":" - The function
Chain.block_hash(height)
is now (in FATEv2) defined for the current height - this used to be an error. - Standard library: Sort is optimized to do
mergesort
and a contains
function is added. - Improved type errors and explicit errors for some syntax errors (empty code blocks, etc.).
- Compiler optimization: The ACI is generated alongside bytecode. This means that multiple compiler passes can be avoided.
- Compiler optimization: Improved parsing (less stack used when transpiled).
- A bug where constraints were handled out of order fixed.
- Fixed calldata decoding for singleton records.
- Improved the documentation w.r.t. signatures, especially stressing the fact that the network ID is a part of what is signed.
"},{"location":"CHANGELOG/#removed_4","title":"Removed","text":""},{"location":"CHANGELOG/#430","title":"4.3.0","text":""},{"location":"CHANGELOG/#added_8","title":"Added","text":" - Added documentation (moved from
protocol
) Frac.aes
\u2013 library for rational numbers - Added some more meaningful error messages
- Exported several parsing functionalities
- With option
keep_included
it is possible to see which files were included during the parse - There is a function
run_parser
that be used to evaluate any parsing rule - Exported parsers:
body
, type
and decl
"},{"location":"CHANGELOG/#changed_9","title":"Changed","text":" - Performance improvements in the standard library
- Fixed ACI encoder to handle
-
unary operator - Fixed including by absolute path
- Fixed variant type printing in the ACI error messages
- Fixed pretty printing of combined function clauses
"},{"location":"CHANGELOG/#removed_5","title":"Removed","text":" let
definitions are no longer supported in the toplevel of the contract - type declarations are no longer supported
"},{"location":"CHANGELOG/#420-2020-01-15","title":"4.2.0 - 2020-01-15","text":""},{"location":"CHANGELOG/#added_9","title":"Added","text":" - Allow separate entrypoint/function type signature and definition, and pattern matching in left-hand sides:
function\n length : list('a) => int\n length([]) = 0\n length(x :: xs) = 1 + length(xs)\n
- Allow pattern matching in list comprehension generators (filtering out match failures):
function somes(xs : list(option('a))) : list('a) =\n [ x | Some(x) <- xs ]\n
- Allow pattern matching in let-bindings (aborting on match failures):
function test(m : map(int, int)) =\n let Some(x) = Map.lookup(m, 0)\n x\n
"},{"location":"CHANGELOG/#changed_10","title":"Changed","text":" - FATE code generator improvements.
- Bug fix: Handle qualified constructors in patterns.
- Bug fix: Allow switching also on negative numbers.
"},{"location":"CHANGELOG/#removed_6","title":"Removed","text":""},{"location":"CHANGELOG/#410-2019-11-26","title":"4.1.0 - 2019-11-26","text":""},{"location":"CHANGELOG/#added_10","title":"Added","text":" - Support encoding and decoding bit fields in call arguments and results.
"},{"location":"CHANGELOG/#changed_11","title":"Changed","text":" - Various improvements to FATE code generator.
"},{"location":"CHANGELOG/#removed_7","title":"Removed","text":""},{"location":"CHANGELOG/#400-2019-10-11","title":"4.0.0 - 2019-10-11","text":""},{"location":"CHANGELOG/#added_11","title":"Added","text":" Address.to_contract
- casts an address to a (any) contract type. - Pragma to check compiler version, e.g.
@compiler >= 4.0
. - Handle numeric escapes, i.e.
\"\\x19Ethereum Signed Message:\\n\"
, and similar strings. Bytes.concat
and Bytes.split
are added to be able to (de-)construct byte arrays. [a..b]
language construct, returning the list of numbers between a
and b
(inclusive). Returns the empty list if a
> b
. - Standard libraries
- Checks that
init
is not called from other functions. - FATE backend - the compiler is able to produce VM code for both
AEVM
and FATE
. Many of the APIs now take {backend, aevm | fate}
to decide wich backend to produce artifacts for. - New builtin functions
Crypto.ecrecover_secp256k1: (hash, bytes(65)) => option(bytes(20))
and Crypto.ecverify_secp256k1 : (hash, bytes(20), bytes(65)) => bool
for recovering and verifying an Ethereum address for a message hash and a signature. - Sophia supports list comprehensions known from languages like Python, Haskell or Erlang. Example syntax:
[x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]]\n// yields [12,13,14,20,21,22,30,31,32]\n
- A new contract, and endpoint, modifier
payable
is introduced. Contracts, and enpoints, that shall be able to receive funds should be marked as payable. Address.is_payable(a)
can be used to check if an (contract) address is payable or not.
"},{"location":"CHANGELOG/#changed_12","title":"Changed","text":" - Nice type error if contract function is called as from a namespace.
- Fail on function definitions in contracts other than the main contract.
- Bug fix in variable optimization - don't discard writes to the store/state.
- Bug fixes in error reporting.
- Bug fix in variable liveness analysis for FATE.
- Error messages are changed into a uniform format, and more helpful messages have been added.
Crypto.<hash_fun>
and String.<hash_fun>
for byte arrays now only hash the actual byte array - not the internal ABI format. - More strict checks for polymorphic oracles and higher order oracles and entrypoints.
AENS.claim
is updated with a NameFee
field - to be able to do name auctions within contracts. - Fixed a bug in
Bytes.to_str
for AEVM. - New syntax for tuple types. Now 0-tuple type is encoded as
unit
instead of ()
and regular tuples are encoded by interspersing inner types with *
, for instance int * string
. Parens are not necessary. Note it only affects the types, values remain as their were before, so (1, \"a\") : int * string
- The
AENS.transfer
and AENS.revoke
functions have been updated to take a name string
instead of a name hash
. - Fixed a bug where the
AEVM
backend complained about a missing init
function when trying to generate calldata from an ACI-generated interface. - Compiler now returns the ABI-version in the compiler result map.
- Renamed
Crypto.ecverify
and Crypto.ecverify_secp256k1
into Crypto.verify_sig
and Crypto.verify_sig_secp256k1
respectively.
"},{"location":"CHANGELOG/#removed_8","title":"Removed","text":""},{"location":"CHANGELOG/#320-2019-06-28","title":"3.2.0 - 2019-06-28","text":""},{"location":"CHANGELOG/#added_12","title":"Added","text":" - New builtin function
require : (bool, string) => ()
. Defined as function require(b, err) = if(!b) abort(err)\n
- New builtin functions
Bytes.to_str : bytes(_) => string\nBytes.to_int : bytes(_) => int\n
for converting a byte array to a hex string and interpreting it as a big-endian encoded integer respectively.
"},{"location":"CHANGELOG/#changed_13","title":"Changed","text":""},{"location":"CHANGELOG/#removed_9","title":"Removed","text":""},{"location":"CHANGELOG/#310-2019-06-03","title":"3.1.0 - 2019-06-03","text":""},{"location":"CHANGELOG/#added_13","title":"Added","text":""},{"location":"CHANGELOG/#changed_14","title":"Changed","text":" - Keyword
indexed
is now optional for word typed (bool
, int
, address
, ...) event arguments. - State variable pretty printing now produce
'a, 'b, ...
instead of '1, '2, ...
. - ACI is restructured and improved:
state
and event
types (if present) now appear at the top level. - Namespaces and remote interfaces are no longer ignored.
- All type definitions are included in the interface rendering.
- API functions are renamed, new functions are
contract_interface
and render_aci_json
.
- Fixed a bug in
create_calldata
/to_sophia_value
- it can now handle negative literals.
"},{"location":"CHANGELOG/#removed_10","title":"Removed","text":""},{"location":"CHANGELOG/#300-2019-05-21","title":"3.0.0 - 2019-05-21","text":""},{"location":"CHANGELOG/#added_14","title":"Added","text":" stateful
annotations are now properly enforced. Functions must be marked stateful in order to update the state or spend tokens. - Primitives
Contract.creator
, Address.is_contract
, Address.is_oracle
, Oracle.check
and Oracle.check_query
has been added to Sophia. - A byte array type
bytes(N)
has been added to generalize hash (== bytes(32))
and signature (== bytes(64))
and allow for byte arrays of arbitrary fixed length. Crypto.ecverify_secp256k1
has been added.
"},{"location":"CHANGELOG/#changed_15","title":"Changed","text":" - Address literals (+ Oracle, Oracle query and remote contracts) have been changed from
#<hex>
to address as ak_<base58check>
, oracle ok_<base58check>
, oracle query oq_<base58check>
and remote contract ct_<base58check>
. - The compilation and typechecking of
letfun
(e.g. let m(f, xs) = map(f, xs)
) was not working properly and has been fixed.
"},{"location":"CHANGELOG/#removed_11","title":"Removed","text":" let rec
has been removed from the language, it has never worked. - The standalone CLI compiler is served in the repo
aeternity/aesophia_cli
and has been completely removed from aesophia
.
"},{"location":"CHANGELOG/#210-2019-04-11","title":"2.1.0 - 2019-04-11","text":""},{"location":"CHANGELOG/#added_15","title":"Added","text":" - Stubs (not yet wired up) for compilation to FATE
- Add functions specific for Calldata decoding
- Support for
Auth.tx_hash
, not available in AEVM until Fortuna release
"},{"location":"CHANGELOG/#changed_16","title":"Changed","text":" - Improvements to the ACI generator
"},{"location":"CHANGELOG/#200-2019-03-11","title":"2.0.0 - 2019-03-11","text":""},{"location":"CHANGELOG/#added_16","title":"Added","text":" - Add
Crypto.ecverify
to the compiler. - Add
Crypto.sha3
, Crypto.blake2
, Crypto.sha256
, String.blake2
and String.sha256
to the compiler. - Add the
bits
type for working with bit fields in Sophia. - Add Namespaces to Sophia in order to simplify using library contracts, etc.
- Add a missig type check on the
init
function - detects programmer errors earlier. - Add the ACI (Aeternity Contract Interface) generator.
"},{"location":"CHANGELOG/#changed_17","title":"Changed","text":" - Use native bit shift operations in builtin functions, reducing gas cost.
- Improve type checking of
record
fields - generates more understandable error messages. - Improved, more coherent, error messages.
- Simplify calldata creation - instead of passing a compiled contract, simply pass a (stubbed) contract string.
"},{"location":"aeso_aci/","title":"aeso_aci","text":""},{"location":"aeso_aci/#module","title":"Module","text":""},{"location":"aeso_aci/#aeso_aci_1","title":"aeso_aci","text":"The ACI interface encoder and decoder.
"},{"location":"aeso_aci/#description","title":"Description","text":"This module provides an interface to generate and convert between Sophia contracts and a suitable JSON encoding of contract interface. As yet the interface is very basic.
Encoding this contract:
contract Answers =\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful function init() = { a = {} }\n private function the_answer() = 42\n function new_answer(q : string, a : int) : answers() = { [q] = a }\n
generates the following JSON structure representing the contract interface:
{\n\"contract\": {\n\"functions\": [\n{\n\"arguments\": [],\n\"name\": \"init\",\n\"returns\": \"Answers.state\",\n\"stateful\": true\n},\n{\n\"arguments\": [\n{\n\"name\": \"q\",\n\"type\": \"string\"\n},\n{\n\"name\": \"a\",\n\"type\": \"int\"\n}\n],\n\"name\": \"new_answer\",\n\"returns\": {\n\"map\": [\n\"string\",\n\"int\"\n]\n},\n\"stateful\": false\n}\n],\n\"name\": \"Answers\",\n\"state\": {\n\"record\": [\n{\n\"name\": \"a\",\n\"type\": \"Answers.answers\"\n}\n]\n},\n\"typedefs\": [\n{\n\"name\": \"answers\",\n\"typedef\": {\n\"map\": [\n\"string\",\n\"int\"\n]\n},\n\"vars\": []\n}\n]\n}\n}\n
When that encoding is decoded the following include definition is generated:
contract Answers =\n record state = {a : Answers.answers}\n type answers = map(string, int)\n function init : () => Answers.state\n function new_answer : (string, int) => map(string, int)\n
"},{"location":"aeso_aci/#types","title":"Types","text":"-type aci_type() :: json | string.\n-type json() :: jsx:json_term().\n-type json_text() :: binary().\n
"},{"location":"aeso_aci/#exports","title":"Exports","text":""},{"location":"aeso_aci/#contract_interfaceaci_type-string-ok-json-string-error-term","title":"contract_interface(aci_type(), string()) -> {ok, json() | string()} | {error, term()}","text":"Generate the JSON encoding of the interface to a contract. The type definitions and non-private functions are included in the JSON string.
"},{"location":"aeso_aci/#render_aci_jsonjson-json_text-string","title":"render_aci_json(json() | json_text()) -> string().","text":"Take a JSON encoding of a contract interface and generate a contract interface that can be included in another contract.
"},{"location":"aeso_aci/#example-run","title":"Example run","text":"This is an example of using the ACI generator from an Erlang shell. The file called aci_test.aes
contains the contract in the description from which we want to generate files aci_test.json
which is the JSON encoding of the contract interface and aci_test.include
which is the contract definition to be included inside another contract.
1> {ok,Contract} = file:read_file(\"aci_test.aes\").\n{ok,<<\"contract Answers =\\n record state = { a : answers }\\n type answers() = map(string, int)\\n\\n stateful function\"...>>}\n2> {ok,JsonACI} = aeso_aci:contract_interface(json, Contract).\n{ok,[#{contract =>\n#{functions =>\n[#{arguments => [],name => <<\"init\">>,\nreturns => <<\"Answers.state\">>,stateful => true},\n#{arguments =>\n[#{name => <<\"q\">>,type => <<\"string\">>},\n#{name => <<\"a\">>,type => <<\"int\">>}],\nname => <<\"new_answer\">>,\nreturns => #{<<\"map\">> => [<<\"string\">>,<<\"int\">>]},\nstateful => false}],\nname => <<\"Answers\">>,\nstate =>\n#{record =>\n[#{name => <<\"a\">>,type => <<\"Answers.answers\">>}]},\ntypedefs =>\n[#{name => <<\"answers\">>,\ntypedef => #{<<\"map\">> => [<<\"string\">>,<<\"int\">>]},\nvars => []}]}}]}\n3> file:write_file(\"aci_test.aci\", jsx:encode(JsonACI)).\nok\n4> {ok,InterfaceStub} = aeso_aci:render_aci_json(JsonACI).\n{ok,<<\"contract Answers =\\n record state = {a : Answers.answers}\\n type answers = map(string, int)\\n function init \"...>>}\n5> file:write_file(\"aci_test.include\", InterfaceStub).\nok\n6> jsx:prettify(jsx:encode(JsonACI)).\n<<\"[\\n {\\n \\\"contract\\\": {\\n \\\"functions\\\": [\\n {\\n \\\"arguments\\\": [],\\n \\\"name\\\": \\\"init\\\",\\n \"...>>\n
The final call to jsx:prettify(jsx:encode(JsonACI))
returns the encoding in a more easily readable form. This is what is shown in the description above.
"},{"location":"aeso_compiler/","title":"aeso_compiler","text":""},{"location":"aeso_compiler/#module","title":"Module","text":""},{"location":"aeso_compiler/#aeso_compiler_1","title":"aeso_compiler","text":"The Sophia compiler
"},{"location":"aeso_compiler/#description","title":"Description","text":"This module provides the interface to the standard Sophia compiler. It returns the compiled module in a map which can then be loaded.
"},{"location":"aeso_compiler/#types","title":"Types","text":"contract_string() = string() | binary()\ncontract_map() = #{bytecode => binary(),\ncompiler_version => binary(),\ncontract_souce => string(),\ntype_info => type_info()}\ntype_info()\nerrorstring() = binary()\n
"},{"location":"aeso_compiler/#exports","title":"Exports","text":""},{"location":"aeso_compiler/#filefile","title":"file(File)","text":""},{"location":"aeso_compiler/#filefile-options-compret","title":"file(File, Options) -> CompRet","text":""},{"location":"aeso_compiler/#from_stringcontractstring-options-compret","title":"from_string(ContractString, Options) -> CompRet","text":"Types
ContractString = contract_string()\nOptions = [Option]\nCompRet = {ok,ContractMap} | {error,ErrorString}\nContractMap = contract_map()\nErrorString = errorstring()\n
Compile a contract defined in a file or in a string.
The pp_ options all print to standard output the following:
pp_sophia_code
- print the input Sophia code.
pp_ast
- print the AST of the code
pp_types
- print information about the types
pp_typed_ast
- print the AST with type information at each node
pp_assembler
- print the generated assembler code
The option include_child_contract_symbols
includes the symbols of child contracts functions in the generated fate code. It is turned off by default to avoid making contracts bigger on chain.
"},{"location":"aeso_compiler/#options-to-control-which-compiler-optimizations-should-run","title":"Options to control which compiler optimizations should run:","text":"By default all optimizations are turned on, to disable an optimization, it should be explicitly set to false and passed as a compiler option.
List of optimizations:
- optimize_inliner
- optimize_inline_local_functions
- optimize_bind_subexpressions
- optimize_let_floating
- optimize_simplifier
- optimize_drop_unused_lets
- optimize_push_consume
- optimize_one_shot_var
- optimize_write_to_dead_var
- optimize_inline_switch_target
- optimize_swap_push
- optimize_swap_pop
- optimize_swap_write
- optimize_constant_propagation
- optimize_prune_impossible_branches
- optimize_single_successful_branch
- optimize_inline_store
- optimize_float_switch_bod
"},{"location":"aeso_compiler/#check_callcontractstring-options-checkret","title":"check_call(ContractString, Options) -> CheckRet","text":"Types
ContractString = string() | binary()\nCheckRet = {ok,string(),{Types,Type | any()},Terms} | {error,Term}\nTypes = [Type]\nType = term()\n
Check a call in contract through the __call
function."},{"location":"aeso_compiler/#version-ok-version-error-term","title":"version() -> {ok, Version} | {error, term()}","text":"Types
Version = binary()\n
Get the current version of the Sophia compiler.
"},{"location":"sophia/","title":"Sophia","text":"This file has been moved here
"},{"location":"sophia_examples/","title":"Contract examples","text":""},{"location":"sophia_examples/#crowdfunding","title":"Crowdfunding","text":"/*\n * A simple crowd-funding example\n */\ncontract FundMe =\n\nrecord spend_args = { recipient : address,\namount : int }\n\nrecord state = { contributions : map(address, int),\ntotal : int,\nbeneficiary : address,\ndeadline : int,\ngoal : int }\n\nstateful function spend(args : spend_args) =\nChain.spend(args.recipient, args.amount)\n\nentrypoint init(beneficiary, deadline, goal) : state =\n{ contributions = {},\nbeneficiary = beneficiary,\ndeadline = deadline,\ntotal = 0,\ngoal = goal }\n\nfunction is_contributor(addr) =\nMap.member(addr, state.contributions)\n\nstateful entrypoint contribute() =\nif(Chain.block_height >= state.deadline)\nspend({ recipient = Call.caller, amount = Call.value }) // Refund money\nfalse\nelse\nlet amount =\nswitch(Map.lookup(Call.caller, state.contributions))\nNone => Call.value\nSome(n) => n + Call.value\nput(state{ contributions[Call.caller] = amount,\ntotal @ tot = tot + Call.value })\ntrue\n\nstateful entrypoint withdraw() =\nif(Chain.block_height < state.deadline)\nabort(\"Cannot withdraw before deadline\")\nif(Call.caller == state.beneficiary)\nwithdraw_beneficiary()\nelif(is_contributor(Call.caller))\nwithdraw_contributor()\nelse\nabort(\"Not a contributor or beneficiary\")\n\nstateful function withdraw_beneficiary() =\nrequire(state.total >= state.goal, \"Project was not funded\")\nspend({recipient = state.beneficiary,\namount = Contract.balance })\n\nstateful function withdraw_contributor() =\nif(state.total >= state.goal)\nabort(\"Project was funded\")\nlet to = Call.caller\nspend({recipient = to,\namount = state.contributions[to]})\nput(state{ contributions @ c = Map.delete(to, c) })\n
"},{"location":"sophia_examples/#repositories","title":"Repositories","text":"This is a list with repositories that include smart contracts written in Sophia:
- 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.
"},{"location":"sophia_features/","title":"Features","text":""},{"location":"sophia_features/#contracts","title":"Contracts","text":"The main unit of code in Sophia is the contract.
- A contract implementation, or simply a contract, is the code for a smart contract and consists of a list of types, entrypoints and local functions. Only the entrypoints can be called from outside the contract.
- A contract instance is an entity living on the block chain (or in a state channel). Each instance has an address that can be used to call its entrypoints, either from another contract or in a call transaction.
- A contract may define a type
state
encapsulating its local state. When creating a new contract the init
entrypoint is executed and the state is initialized to its return value.
The language offers some primitive functions to interact with the blockchain and contracts. Please refer to the Chain, Contract and the Call namespaces in the documentation.
"},{"location":"sophia_features/#calling-other-contracts","title":"Calling other contracts","text":"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,
// A contract type\ncontract interface VotingType =\nentrypoint vote : string => unit\n
Now given contract address of type VotingType
you can call the vote
entrypoint of that contract:
contract VoteTwice =\nentrypoint voteTwice(v : VotingType, alt : string) =\nv.vote(alt)\nv.vote(alt)\n
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:
entrypoint voteTwice(v : VotingType, fee : int, alt : string) =\nv.vote(value = fee, alt)\nv.vote(value = fee, alt)\n
Named arguments can be given in any order.
Note that reentrant calls are not permitted. In other words, when calling another contract it cannot call you back (directly or indirectly).
To construct a value of a contract type you can give a contract address literal (for instance ct_2gPXZnZdKU716QBUFKaT4VdBZituK93KLvHJB3n4EnbrHHw4Ay
), or convert an account address to a contract address using Address.to_contract
. Note that if the contract does not exist, or it doesn't have the entrypoint, or the type of the entrypoint does not match the stated contract type, the call fails.
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
entrypoint pay(v : VotingType, amount : int) =\nChain.spend(v.address, amount)\n
"},{"location":"sophia_features/#protected-contract-calls","title":"Protected contract calls","text":"If a contract call fails for any reason (for instance, the remote contract crashes or runs out of gas, or the entrypoint doesn't exist or has the wrong type) the parent call also fails. To make it possible to recover from failures, contract calls takes a named argument protected : bool
(default false
).
The protected argument must be a literal boolean, and when set to true
changes the type of the contract call, wrapping the result in an option
type. If the call fails the result is None
, otherwise it's Some(r)
where r
is the return value of the call.
contract interface VotingType =\nentrypoint : vote : string => unit\n\ncontract Voter =\nentrypoint tryVote(v : VotingType, alt : string) =\nswitch(v.vote(alt, protected = true) : option(unit))\nNone => \"Voting failed\"\nSome(_) => \"Voting successful\"\n
Any gas that was consumed by the contract call before the failure stays consumed, which means that in order to protect against the remote contract running out of gas it is necessary to set a gas limit using the gas
argument. However, note that errors that would normally consume all the gas in the transaction still only uses up the gas spent running the contract.
Any side effects (state change, token transfers, etc.) made by a failing protected call is rolled back, just like they would be in the unprotected case.
"},{"location":"sophia_features/#contract-factories-and-child-contracts","title":"Contract factories and child contracts","text":"Since the version 6.0.0 Sophia supports deploying contracts by other contracts. This can be done in two ways:
- Contract cloning via
Chain.clone
- Direct deploy via
Chain.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 \u2013 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 =\ntype state = int\nentrypoint init(x) = x\nentrypoint get() = state\n\nmain contract IntHolderFactory =\nstateful entrypoint new(x : int) : IntHolder =\nlet ih = Chain.create(x) : IntHolder\nih\n
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.
"},{"location":"sophia_features/#contract-interfaces-and-polymorphism","title":"Contract interfaces and polymorphism","text":"Contracts can implement one or multiple interfaces, the contract has to define every entrypoint from the implemented interface and the entrypoints in both the contract and implemented interface should have compatible types.
contract interface Animal =\n entrypoint sound : () => string\n\n contract Cat : Animal =\n entrypoint sound() = \"Cat sound\"\n
Contract interfaces can extend other interfaces. An extended interface has to declare all entrypoints from every parent interface. All the declarations in the extended interface must have types compatible with the declarations from the parent interface.
contract interface II =\n entrypoint f : () => unit\n\ncontract interface I : II =\n entrypoint f : () => unit\n entrypoint g : () => unit\n\ncontract C : I =\n entrypoint f() = ()\n entrypoint g() = ()\n
It is only possible to implement (or extend) an interface that has been already defined earlier in the file (or in an included file). Therefore recursive interface implementation is not allowed in Sophia.
// The following code would show an error\n\ncontract interface X : Z =\n entrypoint x : () => int\n\n contract interface Y : X =\n entrypoint x : () => int\n entrypoint y : () => int\n\n contract interface Z : Y =\n entrypoint x : () => int\n entrypoint y : () => int\n entrypoint z : () => int\n\n contract C : Z =\n entrypoint x() = 1\n entrypoint y() = 1\n entrypoint z() = 1\n
"},{"location":"sophia_features/#adding-or-removing-modifiers","title":"Adding or removing modifiers","text":"When a contract
or a contract interface
implements another contract interface
, the payable
and stateful
modifiers can be kept or changed, both in the contract and in the entrypoints, according to the following rules:
- A
payable
contract or interface can implement a payable
interface or a non-payable
interface. - A non-
payable
contract or interface can only implement a non-payable
interface, and cannot implement a payable
interface. - A
payable
entrypoint can implement a payable
entrypoint or a non-payable
entrypoint. - A non-
payable
entrypoint can only implement a non-payable
entrypoint, and cannot implement a payable
entrypoint. - A non-
stateful
entrypoint can implement a stateful
entrypoint or a non-stateful
entrypoint. - A
stateful
entrypoint can only implement a stateful
entrypoint, and cannot implement a non-stateful
entrypoint.
"},{"location":"sophia_features/#subtyping-and-variance","title":"Subtyping and variance","text":"Subtyping in Sophia follows common rules that take type variance into account. As described by Wikipedia,
Variance refers to how subtyping between more complex types relates to subtyping between their components.
This concept plays an important role in complex types such as tuples, datatype
s and functions. Depending on the context, it can apply to positions in the structure of a type, or type parameters of generic types. There are four kinds of variances:
- covariant
- contravariant
- invariant
- bivariant
A type is said to be on a \"covariant\" position when it describes output or a result of some computation. Analogously, position is \"contravariant\" when it is an input, or a parameter. Intuitively, when a part of the type is produced by values of it, it is covariant. When it is consumed, it is contravariant. When a type appears to be simultaneously input and output, it is described as invariant. If a type is neither of those (that is, it's unused) it's bivariant. Furthermore, whenever a complex type appears on a contravariant position, all its covariant components become contravariant and vice versa.
Variance influences how subtyping is applied. Types on covariant positions are subtyped normally, while contravariant the opposite way. Invariant types have to be exactly the same in order for subtyping to work. Bivariant types are always compatible.
A good example of where it matters can be pictured by subtyping of function types. Let us assume there is a contract interface Animal
and two contracts that implement it: Dog
and Cat
.
contract interface Animal =\nentrypoint age : () => int\n\ncontract Dog : Animal =\nentrypoint age() = // ...\nentrypoint woof() = \"woof\"\n\ncontract Cat : Animal =\nentrypoint age() = // ...\nentrypoint meow() = \"meow\"\n
The assumption of this exercise is that cats do not bark (because Cat
does not define the woof
entrypoint). If subtyping rules were applied naively, that is if we let Dog => Dog
be a subtype of Animal => Animal
, the following code would break:
let f : (Dog) => string = d => d.woof()\nlet g : (Animal) => string = f\nlet c : Cat = Chain.create()\ng(c) // Cat barking!\n
That is because arguments of functions are contravariant, as opposed to return the type which is covariant. Because of that, the assignment of f
to g
is invalid - while Dog
is a subtype of Animal
, Dog => string
is not a subtype of Animal => string
. However, Animal => string
is a subtype of Dog => string
. More than that, (Dog => Animal) => Dog
is a subtype of (Animal => Dog) => Animal
.
This has consequences on how user-defined generic types work. A type variable gains its variance from its role in the type definition as shown in the example:
datatype co('a) = Co('a) // co is covariant on 'a\ndatatype ct('a) = Ct('a => unit) // ct is contravariant on 'a\ndatatype in('a) = In('a => 'a) // in is invariant on 'a\ndatatype bi('a) = Bi // bi is bivariant on 'a\n
The following facts apply here:
co('a)
is a subtype of co('b)
when 'a
is a subtype of 'b
ct('a)
is a subtype of ct('b)
when 'b
is a subtype of 'a
in('a)
is a subtype of in('b)
when 'a
is equal to 'b
bi('a)
is a subtype of bi('b)
always
That altogether induce the following rules of subtyping in Sophia:
-
A function type (Args1) => Ret1
is a subtype of (Args2) => Ret2
when Ret1
is a subtype of Ret2
and each argument type from Args2
is a subtype of its counterpart in Args1
.
-
A list type list(A)
is a subtype of list(B)
if A
is a subtype of B
.
-
An option type option(A)
is a subtype of option(B)
if A
is a subtype of B
.
-
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 subtype of t(B)
when A
is a subtype of B
.
-
When a user-defined type t('a)
is contravariant in 'a
, then t(A)
is a subtype of t(B)
when B
is a subtype of A
.
-
When a user-defined type t('a)
is binvariant in 'a
, then t(A)
is a subtype of t(B)
when either A
is a subtype of B
or when B
is a subtype of A
.
-
When a user-defined type t('a)
is invariant in 'a
, then t(A)
can never be a subtype of t(B)
.
"},{"location":"sophia_features/#mutable-state","title":"Mutable state","text":"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
. - The initial state of a contract is computed by the contract's
init
function. The init
function is pure and returns the initial state as its return value. If the type state
is unit
, the init
function defaults to returning the value ()
. At contract creation time, the init
function is executed and its result is stored as the contract state. - The value of the state is accessible from inside the contract through an implicitly bound variable
state
. - State updates are performed by calling a function
put : state => unit
. - Aside from the
put
function (and similar functions for transactions and events), the language is purely functional. - Functions modifying the state need to be annotated with the
stateful
keyword (see below).
To make it convenient to update parts of a deeply nested state Sophia provides special syntax for map/record updates.
"},{"location":"sophia_features/#stateful-functions","title":"Stateful functions","text":"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,
stateful entrypoint set_state(s : state) =\nput(s)\n
Without the stateful
annotation the compiler does not allow the call to put
. A stateful
annotation is required 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
AENS.revoke
AENS.update
- Call a
stateful
function in the current contract - Call another contract with a non-zero
value
argument.
A stateful
annotation is not required to
- Read the contract state.
- Issue an event using the
event
function. - Call another contract with
value = 0
, even if the called function is stateful.
"},{"location":"sophia_features/#payable","title":"Payable","text":""},{"location":"sophia_features/#payable-contracts","title":"Payable contracts","text":"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
:
// A payable contract\npayable contract ExampleContract =\nstateful entrypoint do_stuff() = ...\n
If in doubt, it is possible to check if an address is payable using Address.is_payable(addr)
.
"},{"location":"sophia_features/#payable-entrypoints","title":"Payable entrypoints","text":"A contract entrypoint is by default not payable. Any call to such a function (either a Remote call or a contract call transaction) that has a non-zero value
will fail. Contract entrypoints that should be called with a non-zero value should be declared payable
.
payable stateful entrypoint buy(to : address) =\nif(Call.value > 42)\ntransfer_item(to)\nelse\nabort(\"Value too low\")\n
"},{"location":"sophia_features/#namespaces","title":"Namespaces","text":"Code can be split into libraries using the namespace
construct. Namespaces can appear at the top-level and can contain type and function definitions, but not entrypoints. Outside the namespace you can refer to the (non-private) names by qualifying them with the namespace (Namespace.name
). For example,
namespace Library =\ntype number = int\nfunction inc(x : number) : number = x + 1\n\ncontract MyContract =\nentrypoint plus2(x) : Library.number =\nLibrary.inc(Library.inc(x))\n
Functions in namespaces have access to the same environment (including the Chain
, Call
, and Contract
, builtin namespaces) as function in a contract, with the exception of state
, put
and Chain.event
since these are dependent on the specific state and event types of the contract.
To avoid mentioning the namespace every time it is used, Sophia allows including the namespace in the current scope with the using
keyword:
include \"Pair.aes\"\nusing Pair\ncontract C =\n type state = int\n entrypoint init() =\n let p = (1, 2)\n fst(p) // this is the same as Pair.fst(p)\n
It is also possible to make an alias for the namespace with the as
keyword:
include \"Pair.aes\"\ncontract C =\n using Pair as P\n type state = int\n entrypoint init() =\n let p = (1, 2)\n P.fst(p) // this is the same as Pair.fst(p)\n
Having the same alias for multiple namespaces is possible and it allows referening functions that are defined in different namespaces and have different names with the same alias:
namespace Xa = function f() = 1\nnamespace Xb = function g() = 2\ncontract Cntr =\n using Xa as A\n using Xb as A\n type state = int\n entrypoint init() = A.f() + A.g()\n
Note that using functions with the same name would result in an ambiguous name error:
namespace Xa = function f() = 1\nnamespace Xb = function f() = 2\ncontract Cntr =\n using Xa as A\n using Xb as A\n type state = int\n\n // the next line has an error because f is defined in both Xa and Xb\n entrypoint init() = A.f()\n
Importing specific parts of a namespace or hiding these parts can also be done like this:
using Pair for [fst, snd] // this will only import fst and snd\nusing Triple hiding [fst, snd] // this will import everything except for fst and snd\n
Note that it is possible to use a namespace in the top level of the file, in the contract level, namespace level, or in the function level.
"},{"location":"sophia_features/#splitting-code-over-multiple-files","title":"Splitting code over multiple files","text":"Code from another file can be included in a contract using an include
statement. These must appear at the top-level (outside the main contract). The included file can contain one or more namespaces and abstract contracts. For example, if the file library.aes
contains
namespace Library =\nfunction inc(x) = x + 1\n
you can use it from another file using an include
:
include \"library.aes\"\ncontract MyContract =\nentrypoint plus2(x) = Library.inc(Library.inc(x))\n
This behaves as if the contents of library.aes
was textually inserted into the file, except that error messages will refer to the original source locations. The language will try to include each file at most one time automatically, so even cyclic includes should be working without any special tinkering.
"},{"location":"sophia_features/#standard-library","title":"Standard library","text":"Sophia offers standard library which exposes some primitive operations and some higher level utilities. The builtin namespaces like Chain
, Contract
, Map
are included by default and are supported internally by the compiler. Others like List
, Frac
, Option
need to be manually included using the include
directive. For example
include \"List.aes\"\ninclude \"Pair.aes\"\n-- Map is already there!\n\nnamespace C =\nentrypoint keys(m : map('a, 'b)) : list('a) =\nList.map(Pair.fst, (Map.to_list(m)))\n
"},{"location":"sophia_features/#types","title":"Types","text":"Sophia has the following types:
Type Description Example int A 2-complement integer -1
address \u00e6ternity address, 32 bytes Call.origin
bool A Boolean true
bits A bit field Bits.none
bytes(n) A byte array with n
bytes #fedcba9876543210
string An array of bytes \"Foo\"
list A homogeneous immutable singly linked list. [1, 2, 3]
('a, 'b) => 'c A function. Parentheses can be skipped if there is only one argument (x : int, y : int) => x + y
tuple An ordered heterogeneous array (42, \"Foo\", true)
record An immutable key value store with fixed key names and typed values record balance = { owner: address, value: int }
map An immutable key value store with dynamic mapping of keys of one type to values of one type type accounts = map(string, address)
option('a) An optional value either None or Some('a) Some(42)
state A user defined type holding the contract state record state = { owner: address, magic_key: bytes(4) }
event An append only list of blockchain events (or log entries) datatype event = EventX(indexed int, string)
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()
"},{"location":"sophia_features/#literals","title":"Literals","text":"Type Constant/Literal example(s) int -1
, 2425
, 4598275923475723498573485768
address ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
bool true
, false
bits Bits.none
, Bits.all
bytes(8) #fedcba9876543210
string \"This is a string\"
list [1, 2, 3]
, [(true, 24), (false, 19), (false, -42)]
tuple (42, \"Foo\", true)
record { owner = Call.origin, value = 100000000 }
map {[\"foo\"] = 19, [\"bar\"] = 42}
, {}
option(int) Some(42)
, None
state state{ owner = Call.origin, magic_key = #a298105f }
event EventX(0, \"Hello\")
hash #000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
signature #000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
Chain.ttl FixedTTL(1050)
, RelativeTTL(50)
oracle('a, 'b) ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
oracle_query('a, 'b) oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
contract ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
"},{"location":"sophia_features/#hole-expression","title":"Hole expression","text":"Hole expressions, written as ???
, are expressions that are used as a placeholder. During compilation, the compiler will generate a type error indication the type of the hole expression.
include \"List.aes\"\ncontract C =\n entrypoint f() =\n List.sum(List.map(???, [1,2,3]))\n
A hole expression found in the example above will generate the error Found a hole of type `(int) => int`
. This says that the compiler expects a function from int
to int
in place of the ???
placeholder.
"},{"location":"sophia_features/#constants","title":"Constants","text":"Constants in Sophia are contract-level bindings that can be used in either contracts or namespaces. The value of a constant can be a literal, another constant, or arithmetic operations applied to other constants. Lists, tuples, maps, and records can also be used to define a constant as long as their elements are also constants.
The following visibility rules apply to constants: * Constants defined inside a contract are private in that contract. Thus, cannot be accessed through instances of their defining contract. * Constants defined inside a namespace are public. Thus, can be used in other contracts or namespaces. * Constants cannot be defined inside a contract interface.
When a constant is shadowed, it can be accessed using its qualified name:
contract C =\n let c = 1\n entrypoint f() =\n let c = 2\n c + C.c // the result is 3\n
The name of the constant must be an id; therefore, no pattern matching is allowed when defining a constant:
contract C\n let x::y::_ = [1,2,3] // this will result in an error\n
"},{"location":"sophia_features/#arithmetic","title":"Arithmetic","text":"Sophia integers (int
) are represented by arbitrary-sized signed words and support the following arithmetic operations: - addition (x + y
) - subtraction (x - y
) - multiplication (x * y
) - division (x / y
), truncated towards zero - remainder (x mod y
), satisfying y * (x / y) + x mod y == x
for non-zero y
- exponentiation (x ^ y
)
All operations are safe with respect to overflow and underflow. The division and modulo operations throw an arithmetic error if the right-hand operand is zero.
"},{"location":"sophia_features/#bit-fields","title":"Bit fields","text":"Sophia integers do not support bit arithmetic. Instead there is a separate type bits
. See the standard library documentation.
A bit field can be of arbitrary size (but it is still represented by the corresponding integer, so setting very high bits can be expensive).
"},{"location":"sophia_features/#type-aliases","title":"Type aliases","text":"Type aliases can be introduced with the type
keyword and can be parameterized. For instance
type number = int\ntype string_map('a) = map(string, 'a)\n
A type alias and its definition can be used interchangeably. Sophia does not support higher-kinded types, meaning that following type alias is invalid: type wrap('f, 'a) = 'f('a)
"},{"location":"sophia_features/#algebraic-data-types","title":"Algebraic data types","text":"Sophia supports algebraic data types (variant types) and pattern matching. Data types are declared by giving a list of constructors with their respective arguments. For instance,
datatype one_or_both('a, 'b) = Left('a) | Right('b) | Both('a, 'b)\n
Elements of data types can be pattern matched against, using the switch
construct:
function get_left(x : one_or_both('a, 'b)) : option('a) =\nswitch(x)\nLeft(x) => Some(x)\nRight(_) => None\nBoth(x, _) => Some(x)\n
or directly in the left-hand side:
function\nget_left : one_or_both('a, 'b) => option('a)\nget_left(Left(x)) = Some(x)\nget_left(Right(_)) = None\nget_left(Both(x, _)) = Some(x)\n
NOTE: Data types cannot currently be recursive.
Sophia also supports the assignment of patterns to variables:
function f(x) = switch(x)\nh1::(t = h2::_) => (h1 + h2)::t // same as `h1::h2::k => (h1 + h2)::h2::k`\n_ => x\n\nfunction g(p : int * option(int)) : int =\nlet (a, (o = Some(b))) = p // o is equal to Pair.snd(p)\nb\n
Guards are boolean expressions that can be used on patterns in both switch statements and functions definitions. If a guard expression evaluates to true
, then the corresponding body will be used. Otherwise, the next pattern will be checked:
function get_left_if_positive(x : one_or_both(int, 'b)) : option(int) =\nswitch(x)\nLeft(x) | x > 0 => Some(x)\nBoth(x, _) | x > 0 => Some(x)\n_ => None\n
function\nget_left_if_positive : one_or_both(int, 'b) => option(int)\nget_left_if_positive(Left(x)) | x > 0 = Some(x)\nget_left_if_positive(Both(x, _)) | x > 0 = Some(x)\nget_left_if_positive(_) = None\n
Guards cannot be stateful even when used inside a stateful function.
"},{"location":"sophia_features/#lists","title":"Lists","text":"A Sophia list is a dynamically sized, homogenous, immutable, singly linked list. A list is constructed with the syntax [1, 2, 3]
. The 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:
[1, 33, 2, 666] : list(int)\n[(1, \"aaa\"), (10, \"jjj\"), (666, \"the beast\")] : list(int * string)\n[{[1] = \"aaa\", [10] = \"jjj\"}, {[5] = \"eee\", [666] = \"the beast\"}] : list(map(int, string))\n
New elements can be prepended to the front of a list with the ::
operator. So 42 :: [1, 2, 3]
returns the list [42, 1, 2, 3]
. The concatenation operator ++
appends its second argument to its first and returns the resulting list. So concatenating two lists [1, 22, 33] ++ [10, 18, 55]
returns the list [1, 22, 33, 10, 18, 55]
.
Sophia supports list comprehensions known from languages like Python, Haskell or Erlang. Example syntax:
[x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]]\n// yields [12,13,14,20,21,22,30,31,32]\n
Lists can be constructed using the range syntax using special ..
operator:
[1..4] == [1,2,3,4]\n
The ranges are always ascending and have step equal to 1. Please refer to the standard library for the predefined functionalities.
"},{"location":"sophia_features/#maps-and-records","title":"Maps and records","text":"A Sophia record type is given by a fixed set of fields with associated, possibly different, types. For instance
record account = { name : string,\nbalance : int,\nhistory : list(transaction) }\n
Maps, on the other hand, can contain an arbitrary number of key-value bindings, but of a fixed type. The type of maps with keys of type 'k
and values of type 'v
is written map('k, 'v)
. The key type can be any type that does not contain a map or a function type.
Please refer to the standard library for the predefined functionalities.
"},{"location":"sophia_features/#constructing-maps-and-records","title":"Constructing maps and records","text":"A value of record type is constructed by giving a value for each of the fields. For the example above,
function new_account(name) =\n{name = name, balance = 0, history = []}\n
Maps are constructed similarly, with keys enclosed in square brackets function example_map() : map(string, int) =\n{[\"key1\"] = 1, [\"key2\"] = 2}\n
The empty map is written {}
."},{"location":"sophia_features/#accessing-values","title":"Accessing values","text":"Record fields access is written r.f
and map lookup m[k]
. For instance,
function get_balance(a : address, accounts : map(address, account)) =\naccounts[a].balance\n
Looking up a non-existing key in a map results in contract execution failing. A default value to return for non-existing keys can be provided using the syntax m[k = default]
. See also Map.member
and Map.lookup
below."},{"location":"sophia_features/#updating-a-value","title":"Updating a value","text":"Record field updates are written r{f = v}
. This creates a new record value which is the same as r
, but with the value of the field f
replaced by v
. Similarly, m{[k] = v}
constructs a map with the same values as m
except that k
maps to v
. It makes no difference if m
has a mapping for k
or not.
It is possible to give a name to the old value of a field or mapping in an update: instead of acc{ balance = acc.balance + 100 }
it is possible to write acc{ balance @ b = b + 100 }
, binding b
to acc.balance
. When giving a name to a map value (m{ [k] @ x = v }
), the corresponding key must be present in the map or execution fails, but a default value can be provided: m{ [k = default] @ x = v }
. In this case x
is bound to default
if k
is not in the map.
Updates can be nested:
function clear_history(a : address, accounts : map(address, account)) : map(address, account) =\naccounts{ [a].history = [] }\n
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
): accounts{ [a = empty_account()].history = [] }\n
"},{"location":"sophia_features/#map-implementation","title":"Map implementation","text":"Internally in the VM maps are implemented as hash maps and support fast lookup and update. Large maps can be stored in the contract state and the size of the map does not contribute to the gas costs of a contract call reading or updating it.
"},{"location":"sophia_features/#strings","title":"Strings","text":"There is a builtin type string
, which can be seen as an array of bytes. Strings can be compared for equality (==
, !=
), used as keys in maps and records, and used in builtin functions String.length
, String.concat
and the hash functions described below.
Please refer to the String
library documentation.
"},{"location":"sophia_features/#chars","title":"Chars","text":"There is a builtin type char
(the underlying representation being an integer), mainly used to manipulate strings via String.to_list
/String.from_list
.
Characters can also be introduced as character literals (`'x', '+', ...).
Please refer to the Char
library documentation.
"},{"location":"sophia_features/#byte-arrays","title":"Byte arrays","text":"Byte arrays are fixed size arrays of 8-bit integers. They are described in hexadecimal system, for example the literal #cafe
creates a two-element array of bytes ca
(202) and fe
(254) and thus is a value of type bytes(2)
.
Please refer to the Bytes
library documentation.
"},{"location":"sophia_features/#cryptographic-builtins","title":"Cryptographic builtins","text":"Libraries Crypto and String provide functions to hash objects, verify signatures etc. The hash
is a type alias for bytes(32)
.
"},{"location":"sophia_features/#authorization-interface","title":"Authorization interface","text":"When a Generalized account is authorized, the authorization function needs access to the transaction and the transaction hash for the wrapped transaction. (A GAMetaTx
wrapping a transaction.) The transaction and the transaction hash is available in the primitive Auth.tx
and Auth.tx_hash
respectively, they are only available during authentication if invoked by a normal contract call they return None
.
"},{"location":"sophia_features/#oracle-interface","title":"Oracle interface","text":"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. For a functionality documentation refer to the standard library.
"},{"location":"sophia_features/#example","title":"Example","text":"Example for an oracle answering questions of type string
with answers of type int
:
contract Oracles =\n\nstateful entrypoint registerOracle(acct : address,\nsign : signature, // Signed network id + oracle address + contract address\nqfee : int,\nttl : Chain.ttl) : oracle(string, int) =\nOracle.register(acct, signature = sign, qfee, ttl)\n\nentrypoint queryFee(o : oracle(string, int)) : int =\nOracle.query_fee(o)\n\npayable stateful entrypoint createQuery(o : oracle_query(string, int),\nq : string,\nqfee : int,\nqttl : Chain.ttl,\nrttl : int) : oracle_query(string, int) =\nrequire(qfee =< Call.value, \"insufficient value for qfee\")\nOracle.query(o, q, qfee, qttl, RelativeTTL(rttl))\n\nstateful entrypoint extendOracle(o : oracle(string, int),\nttl : Chain.ttl) : unit =\nOracle.extend(o, ttl)\n\nstateful entrypoint signExtendOracle(o : oracle(string, int),\nsign : signature, // Signed network id + oracle address + contract address\nttl : Chain.ttl) : unit =\nOracle.extend(o, signature = sign, ttl)\n\nstateful entrypoint respond(o : oracle(string, int),\nq : oracle_query(string, int),\nsign : signature, // Signed network id + oracle query id + contract address\nr : int) =\nOracle.respond(o, q, signature = sign, r)\n\nentrypoint getQuestion(o : oracle(string, int),\nq : oracle_query(string, int)) : string =\nOracle.get_question(o, q)\n\nentrypoint hasAnswer(o : oracle(string, int),\nq : oracle_query(string, int)) =\nswitch(Oracle.get_answer(o, q))\nNone => false\nSome(_) => true\n\nentrypoint getAnswer(o : oracle(string, int),\nq : oracle_query(string, int)) : option(int) =\nOracle.get_answer(o, q)\n
"},{"location":"sophia_features/#sanity-checks","title":"Sanity checks","text":"When an Oracle literal is passed to a contract, no deep checks are performed. For extra safety Oracle.check and Oracle.check_query functions are provided.
"},{"location":"sophia_features/#aens-interface","title":"AENS interface","text":"Contracts can interact with the \u00e6ternity naming system. For this purpose the AENS library was exposed.
"},{"location":"sophia_features/#example_1","title":"Example","text":"In this example we assume that the name name
already exists, and is owned by an account with address addr
. In order to allow a contract ct
to handle name
the account holder needs to create a signature sig
of addr | name.hash | ct.address
.
Armed with this information we can for example write a function that extends the name if it expires within 1000 blocks:
stateful entrypoint extend_if_necessary(addr : address, name : string, sig : signature) =\nswitch(AENS.lookup(name))\nNone => ()\nSome(AENS.Name(_, FixedTTL(expiry), _)) =>\nif(Chain.block_height + 1000 > expiry)\nAENS.update(addr, name, Some(RelativeTTL(50000)), None, None, signature = sig)\n
And we can write functions that adds and removes keys from the pointers of the name:
stateful entrypoint add_key(addr : address, name : string, key : string,\npt : AENS.pointee, sig : signature) =\nswitch(AENS.lookup(name))\nNone => ()\nSome(AENS.Name(_, _, ptrs)) =>\nAENS.update(addr, name, None, None, Some(ptrs{[key] = pt}), signature = sig)\n\nstateful entrypoint delete_key(addr : address, name : string,\nkey : string, sig : signature) =\nswitch(AENS.lookup(name))\nNone => ()\nSome(AENS.Name(_, _, ptrs)) =>\nlet ptrs = Map.delete(key, ptrs)\nAENS.update(addr, name, None, None, Some(ptrs), signature = sig)\n
Note: From the Iris hardfork more strict rules apply for AENS pointers, when a Sophia contract lookup or update (bad) legacy pointers, the bad keys are automatically removed so they will not appear in the pointers map.
"},{"location":"sophia_features/#events","title":"Events","text":"Sophia contracts log structured messages to an event log in the resulting blockchain transaction. The event log is quite similar to Events in Solidity. Events are further discussed in the protocol.
To use events a contract must declare a datatype event
, and events are then logged using the Chain.event
function:
datatype event\n= Event1(int, int, string)\n| Event2(string, address)\n\nChain.event(e : event) : unit\n
The event can have 0-3 indexed fields, and an optional payload field. A field is indexed if it fits in a 32-byte word, i.e. - bool
- int
- bits
- address
- oracle(_, _)
- oracle_query(_, _)
- contract types - bytes(n)
for n
\u2264 32, in particular hash
The payload field must be either a string or a byte array of more than 32 bytes. The fields can appear in any order.
NOTE: Indexing is not part of the core \u00e6ternity node.
Events are emitted by using the Chain.event
function. The following function will emit one Event of each kind in the example.
entrypoint emit_events() : () =\nChain.event(Event1(42, 34, \"foo\"))\nChain.event(Event2(\"This is not indexed\", Contract.address))\n
"},{"location":"sophia_features/#argument-order","title":"Argument order","text":"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.
AnotherEvent(string, indexed address)\n\n...\n\nChain.event(AnotherEvent(\"This is not indexed\", Contract.address))\n
would yield exactly the same result in the example above!"},{"location":"sophia_features/#compiler-pragmas","title":"Compiler pragmas","text":"To enforce that a contract is only compiled with specific versions of the Sophia compiler, you can give one or more @compiler
pragmas at the top-level (typically at the beginning) of a file. For instance, to enforce that a contract is compiled with version 4.3 of the compiler you write
@compiler >= 4.3\n@compiler < 4.4\n
Valid operators in compiler pragmas are <
, =<
, ==
, >=
, and >
. Version numbers are given as a sequence of non-negative integers separated by dots. Trailing zeros are ignored, so 4.0.0 == 4
. If a constraint is violated an error is reported and compilation fails.
"},{"location":"sophia_features/#exceptions","title":"Exceptions","text":"Contracts can fail with an (uncatchable) exception using the built-in function
abort(reason : string) : 'a\n
Calling abort causes the top-level call transaction to return an error result containing the reason
string. Only the gas used up to and including the abort call is charged. This is different from termination due to a crash which consumes all available gas.
For convenience the following function is also built-in:
function require(b : bool, err : string) =\nif(!b) abort(err)\n
Aside from that, there is an almost equivalent function exit
exit(reason : string) : 'a\n
Just like abort
, it breaks the execution with the given reason. The difference however is in the gas consumption \u2014 while abort
returns unused gas, a call to exit
burns it all.
"},{"location":"sophia_features/#delegation-signature","title":"Delegation signature","text":"Some chain operations (Oracle.<operation>
and AENS.<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. 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 \u00e6ternity mainnet, etc.).
"},{"location":"sophia_stdlib/","title":"Standard library","text":""},{"location":"sophia_stdlib/#standard-library","title":"Standard library","text":"Sophia language offers standard library that consists of several namespaces. Some of them are already in the scope and do not need any actions to be used, while the others require some files to be included.
The out-of-the-box namespaces are:
- Address
- AENS
- Auth
- Bits
- Bytes
- Call
- Chain
- Char
- Contract
- Crypto
- Int
- Map
- Oracle
The following ones need to be included as regular files with .aes
suffix, for example
include \"List.aes\"\n
- Bitwise
- BLS12_381
- Func
- Frac
- List
- Option
- Pair
- Set
- String
- Triple
"},{"location":"sophia_stdlib/#builtin-namespaces","title":"Builtin namespaces","text":"They are available without any explicit includes.
"},{"location":"sophia_stdlib/#address","title":"Address","text":""},{"location":"sophia_stdlib/#to_str","title":"to_str","text":"Address.to_str(a : address) : string\n
Base58 encoded string
"},{"location":"sophia_stdlib/#is_contract","title":"is_contract","text":"Address.is_contract(a : address) : bool\n
Is the address a contract
"},{"location":"sophia_stdlib/#is_oracle","title":"is_oracle","text":"Address.is_oracle(a : address) : bool\n
Is the address a registered oracle
"},{"location":"sophia_stdlib/#is_payable","title":"is_payable","text":"Address.is_payable(a : address) : bool\n
Can the address be spent to
"},{"location":"sophia_stdlib/#to_contract","title":"to_contract","text":"Address.to_contract(a : address) : C\n
Cast address to contract type C (where C
is a contract)
"},{"location":"sophia_stdlib/#aens","title":"AENS","text":"The following functionality is available for interacting with the \u00e6ternity 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, i.e. the signature material should be prefixed by the network id.
"},{"location":"sophia_stdlib/#types","title":"Types","text":""},{"location":"sophia_stdlib/#name","title":"name","text":"datatype name = Name(address, Chain.ttl, map(string, AENS.pointee))\n
"},{"location":"sophia_stdlib/#pointee","title":"pointee","text":"datatype pointee = AccountPt(address) | OraclePt(address)\n | ContractPt(address) | ChannelPt(address)\n
"},{"location":"sophia_stdlib/#functions","title":"Functions","text":""},{"location":"sophia_stdlib/#resolve","title":"resolve","text":"AENS.resolve(name : string, key : string) : option('a)\n
Name resolution. Here name
should be a registered name and key
one of the attributes associated with this name (for instance \"account_pubkey\"
). The return type ('a
) must be resolved at compile time to an atomic type and the value is type checked against this type at run time.
"},{"location":"sophia_stdlib/#lookup","title":"lookup","text":"AENS.lookup(name : string) : option(AENS.name)\n
If name
is an active name AENS.lookup
returns a name object. The three arguments to Name
are owner
, expiry
and a map of the pointees
for the name. Note: the expiry of the name is always a fixed TTL. For example:
let Some(Name(owner, FixedTTL(expiry), ptrs)) = AENS.lookup(\"example.chain\")\n
"},{"location":"sophia_stdlib/#preclaim","title":"preclaim","text":"AENS.preclaim(owner : address, commitment_hash : hash, <signature : signature>) : unit\n
The signature should be over network id
+ owner address
+ Contract.address
(concatenated as byte arrays).
"},{"location":"sophia_stdlib/#claim","title":"claim","text":"AENS.claim(owner : address, name : string, salt : int, name_fee : int, <signature : signature>) : unit\n
The signature should be over network id
+ owner address
+ name_hash
+ Contract.address
(concatenated as byte arrays) using the private key of the owner
account for signing.
"},{"location":"sophia_stdlib/#transfer","title":"transfer","text":"AENS.transfer(owner : address, new_owner : address, name : string, <signature : signature>) : unit\n
Transfers name to the new owner.
The signature should be over network id
+ owner address
+ name_hash
+ Contract.address
(concatenated as byte arrays) using the private key of the owner
account for signing.
"},{"location":"sophia_stdlib/#revoke","title":"revoke","text":"AENS.revoke(owner : address, name : string, <signature : signature>) : unit\n
Revokes the name to extend the ownership time.
The signature should be over network id
+ owner address
+ name_hash
+ Contract.address
(concatenated as byte arrays) using the private key of the owner
account for signing.
"},{"location":"sophia_stdlib/#update","title":"update","text":"AENS.update(owner : address, name : string, expiry : option(Chain.ttl), client_ttl : option(int),\n new_ptrs : option(map(string, AENS.pointee)), <signature : signature>) : unit\n
Updates the name. If the optional parameters are set to None
that parameter will not be updated, for example if None
is passed as expiry
the expiry block of the name is not changed.
"},{"location":"sophia_stdlib/#auth","title":"Auth","text":""},{"location":"sophia_stdlib/#tx","title":"tx","text":"Auth.tx : option(Chain.tx)\n
Where Chain.tx
is (built-in) defined like:
namespace Chain =\n record tx = { paying_for : option(Chain.paying_for_tx)\n , ga_metas : list(Chain.ga_meta_tx)\n , actor : address\n , fee : int\n , ttl : int\n , tx : Chain.base_tx }\n\n datatype ga_meta_tx = GAMetaTx(address, int)\n datatype paying_for_tx = PayingForTx(address, int)\n datatype base_tx = SpendTx(address, int, string)\n | OracleRegisterTx | OracleQueryTx | OracleResponseTx | OracleExtendTx\n | NamePreclaimTx | NameClaimTx(hash) | NameUpdateTx(string)\n | NameRevokeTx(hash) | NameTransferTx(address, string)\n | ChannelCreateTx(address) | ChannelDepositTx(address, int) | ChannelWithdrawTx(address, int) |\n | ChannelForceProgressTx(address) | ChannelCloseMutualTx(address) | ChannelCloseSoloTx(address)\n | ChannelSlashTx(address) | ChannelSettleTx(address) | ChannelSnapshotSoloTx(address)\n | ContractCreateTx(int) | ContractCallTx(address, int)\n | GAAttachTx\n
"},{"location":"sophia_stdlib/#tx_hash","title":"tx_hash","text":"Auth.tx_hash : option(hash)\n
Gets the transaction hash during authentication.
"},{"location":"sophia_stdlib/#bits","title":"Bits","text":""},{"location":"sophia_stdlib/#none","title":"none","text":"Bits.none : bits\n
A bit field with all bits cleared
"},{"location":"sophia_stdlib/#all","title":"all","text":"Bits.all : bits\n
A bit field with all bits set
"},{"location":"sophia_stdlib/#set","title":"set","text":"Bits.set(b : bits, i : int) : bits\n
Set bit i
"},{"location":"sophia_stdlib/#clear","title":"clear","text":"Bits.clear(b : bits, i : int) : bits\n
Clear bit i
"},{"location":"sophia_stdlib/#test","title":"test","text":"Bits.test(b : bits, i : int) : bool\n
Check if bit i is set
"},{"location":"sophia_stdlib/#sum","title":"sum","text":"Bits.sum(b : bits) : int\n
Count the number of set bits
"},{"location":"sophia_stdlib/#union","title":"union","text":"Bits.union(a : bits, b : bits) : bits\n
Bitwise disjunction
"},{"location":"sophia_stdlib/#intersection","title":"intersection","text":"Bits.intersection(a : bits, b : bits) : bits\n
Bitwise conjunction
"},{"location":"sophia_stdlib/#difference","title":"difference","text":"Bits.difference(a : bits, b : bits) : bits\n
Each bit is true if and only if it was 1 in a
and 0 in b
"},{"location":"sophia_stdlib/#bytes","title":"Bytes","text":""},{"location":"sophia_stdlib/#to_int","title":"to_int","text":"Bytes.to_int(b : bytes(n)) : int\n
Interprets the byte array as a big endian integer
"},{"location":"sophia_stdlib/#to_str_1","title":"to_str","text":"Bytes.to_str(b : bytes(n)) : string\n
Returns the hexadecimal representation of the byte array
"},{"location":"sophia_stdlib/#concat","title":"concat","text":"Bytes.concat : (a : bytes(m), b : bytes(n)) => bytes(m + n)\n
Concatenates two byte arrays
"},{"location":"sophia_stdlib/#split","title":"split","text":"Bytes.split(a : bytes(m + n)) : bytes(m) * bytes(n)\n
Splits a byte array at given index
"},{"location":"sophia_stdlib/#call","title":"Call","text":"Values related to the call to the current contract
"},{"location":"sophia_stdlib/#origin","title":"origin","text":"Call.origin : address\n
The address of the account that signed the call transaction that led to this call.
"},{"location":"sophia_stdlib/#caller","title":"caller","text":"Call.caller : address\n
The address of the entity (possibly another contract) calling the contract.
"},{"location":"sophia_stdlib/#value","title":"value","text":"Call.value : int\n
The amount of coins transferred to the contract in the call.
"},{"location":"sophia_stdlib/#gas_price","title":"gas_price","text":"Call.gas_price : int\n
The gas price of the current call.
"},{"location":"sophia_stdlib/#fee","title":"fee","text":"Call.fee : int\n
The fee of the current call.
"},{"location":"sophia_stdlib/#gas_left","title":"gas_left","text":"Call.gas_left() : int\n
The amount of gas left for the current call.
"},{"location":"sophia_stdlib/#chain","title":"Chain","text":"Values and functions related to the chain itself and other entities that live on it.
"},{"location":"sophia_stdlib/#types_1","title":"Types","text":""},{"location":"sophia_stdlib/#tx_1","title":"tx","text":"record tx = { paying_for : option(Chain.paying_for_tx)\n , ga_metas : list(Chain.ga_meta_tx)\n , actor : address\n , fee : int\n , ttl : int\n , tx : Chain.base_tx }\n
"},{"location":"sophia_stdlib/#ga_meta_tx","title":"ga_meta_tx","text":"datatype ga_meta_tx = GAMetaTx(address, int)\n
"},{"location":"sophia_stdlib/#paying_for_tx","title":"paying_for_tx","text":"datatype paying_for_tx = PayingForTx(address, int)\n
"},{"location":"sophia_stdlib/#base_tx","title":"base_tx","text":"datatype base_tx = SpendTx(address, int, string)\n | OracleRegisterTx | OracleQueryTx | OracleResponseTx | OracleExtendTx\n | NamePreclaimTx | NameClaimTx(hash) | NameUpdateTx(string)\n | NameRevokeTx(hash) | NameTransferTx(address, string)\n | ChannelCreateTx(address) | ChannelDepositTx(address, int) | ChannelWithdrawTx(address, int) |\n | ChannelForceProgressTx(address) | ChannelCloseMutualTx(address) | ChannelCloseSoloTx(address)\n | ChannelSlashTx(address) | ChannelSettleTx(address) | ChannelSnapshotSoloTx(address)\n | ContractCreateTx(int) | ContractCallTx(address, int)\n | GAAttachTx\n
"},{"location":"sophia_stdlib/#functions_1","title":"Functions","text":""},{"location":"sophia_stdlib/#balance","title":"balance","text":"Chain.balance(a : address) : int\n
The balance of account a
.
"},{"location":"sophia_stdlib/#block_hash","title":"block_hash","text":"Chain.block_hash(h : int) : option(bytes(32))\n
The hash of the block at height h
. h
has to be within 256 blocks from the current height of the chain or else the function will return None
.
NOTE: In FATE VM version 1 Chain.block_height
was not considered an allowed height. From FATE VM version 2 (IRIS) it will return the block hash of the current generation.
"},{"location":"sophia_stdlib/#block_height","title":"block_height","text":"Chain.block_height : int\"\n
The height of the current block (i.e. the block in which the current call will be included).
"},{"location":"sophia_stdlib/#bytecode_hash","title":"bytecode_hash","text":"Chain.bytecode_hash : 'c => option(hash)\n
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.
"},{"location":"sophia_stdlib/#create","title":"create","text":"Chain.create(value : int, ...) => 'c\n
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 \u2013 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 =\n record state = {supply: int, name: string}\n entrypoint init(supply, name) = {supply: supply, name: name}\n stateful payable entrypoint buy(amount) =\n require(Call.value == amount, \"amount_value_mismatch\")\n ...\n stateful entrypoint sell(amount) =\n require(amount >= 0, \"negative_amount\")\n ...\n\nmain contract Market =\n type state = list(Auction)\n entrypoint init() = []\n stateful entrypoint new(name : string) =\n let new_auction = Chain.create(0, name) : Auction\n put(new_auction::state)\n
The typechecker must be certain about the created contract's type, so it is worth writing it explicitly as shown in the example.
"},{"location":"sophia_stdlib/#clone","title":"clone","text":"Chain.clone : ( ref : 'c, gas : int, value : int, protected : bool, ...\n ) => if(protected) option('c) else 'c\n
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 \u2013 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 =\n entrypoint init : (int, string) => void\n stateful payable entrypoint buy : (int) => ()\n stateful entrypoint sell : (int) => ()\n\nmain contract Market =\n type state = list(Auction)\n entrypoint init() = []\n stateful entrypoint new_of(template : Auction, name : string) =\n switch(Chain.clone(ref=template, protected=true, 0, name))\n None => abort(\"Bad auction!\")\n Some(new_auction) =>\n put(new_auction::state)\n
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
.
"},{"location":"sophia_stdlib/#coinbase","title":"coinbase","text":"Chain.coinbase : address\n
The address of the account that mined the current block.
"},{"location":"sophia_stdlib/#difficulty","title":"difficulty","text":"Chain.difficulty : int\n
The difficulty of the current block.
"},{"location":"sophia_stdlib/#event","title":"event","text":"Chain.event(e : event) : unit\n
Emits the event. To use this function one needs to define the event
type as a datatype
in the contract.
"},{"location":"sophia_stdlib/#gas_limit","title":"gas_limit","text":"Chain.gas_limit : int\n
The gas limit of the current block.
"},{"location":"sophia_stdlib/#spend","title":"spend","text":"Chain.spend(to : address, amount : int) : unit\n
Spend amount
tokens to to
. Will fail (and abort the contract) if contract doesn't have amount
tokens to transfer, or, if to
is not payable
.
"},{"location":"sophia_stdlib/#timestamp","title":"timestamp","text":"Chain.timestamp : int\n
The timestamp of the current block (unix time, milliseconds).
"},{"location":"sophia_stdlib/#char","title":"Char","text":""},{"location":"sophia_stdlib/#to_int_1","title":"to_int","text":"Char.to_int(c : char) : int Returns the UTF-8 codepoint of a character\n\n\n#### from_int\n
Char.from_int(i : int) : option(char)
Opposite of to_int. Returns None
if the integer doesn't correspond to a single (normalized) codepoint.
"},{"location":"sophia_stdlib/#contract","title":"Contract","text":"Values related to the current contract
"},{"location":"sophia_stdlib/#creator","title":"creator","text":"Contract.creator : address\n
Address of the entity that signed the contract creation transaction
"},{"location":"sophia_stdlib/#address_1","title":"address","text":"Contract.address : address\n
Address of the contract account
"},{"location":"sophia_stdlib/#balance_1","title":"balance","text":"Contract.balance : int\n
Amount of coins in the contract account
"},{"location":"sophia_stdlib/#crypto","title":"Crypto","text":""},{"location":"sophia_stdlib/#sha3","title":"sha3","text":"Crypto.sha3(x : 'a) : hash\n
Hash any object to SHA3
"},{"location":"sophia_stdlib/#sha256","title":"sha256","text":"Crypto.sha256(x : 'a) : hash\n
Hash any object to SHA256
"},{"location":"sophia_stdlib/#blake2b","title":"blake2b","text":"Crypto.blake2b(x : 'a) : hash\n
Hash any object to blake2b
"},{"location":"sophia_stdlib/#verify_sig","title":"verify_sig","text":"Crypto.verify_sig(msg : hash, pubkey : address, sig : signature) : bool\n
Checks if the signature of msg
was made using private key corresponding to the pubkey
"},{"location":"sophia_stdlib/#ecverify_secp256k1","title":"ecverify_secp256k1","text":"Crypto.ecverify_secp256k1(msg : hash, addr : bytes(20), sig : bytes(65)) : bool\n
Verifies a signature for a msg against an Ethereum style address. Note that the signature should be 65 bytes and include the recovery identifier byte V
. The expected organization of the signature is (V || R || S
).
"},{"location":"sophia_stdlib/#ecrecover_secp256k1","title":"ecrecover_secp256k1","text":"Crypto.ecrecover_secp256k1(msg : hash, sig : bytes(65)) : option(bytes(20))\n
Recovers the Ethereum style address from a msg hash and respective ECDSA-signature. Note that the signature should be 65 bytes and include the recovery identifier byte V
. The expected organization of the signature is (V || R || S
).
"},{"location":"sophia_stdlib/#verify_sig_secp256k1","title":"verify_sig_secp256k1","text":"Crypto.verify_sig_secp256k1(msg : hash, pubkey : bytes(64), sig : bytes(64)) : bool\n
Verifies a standard 64-byte ECDSA signature (R || S
).
"},{"location":"sophia_stdlib/#int","title":"Int","text":""},{"location":"sophia_stdlib/#to_str_2","title":"to_str","text":"Int.to_str : int => string\n
Casts integer to string using decimal representation
"},{"location":"sophia_stdlib/#map","title":"Map","text":""},{"location":"sophia_stdlib/#lookup_1","title":"lookup","text":"Map.lookup(k : 'k, m : map('k, 'v)) : option('v)
Returns the value under a key in given map as Some
or None
if the key is not present
"},{"location":"sophia_stdlib/#lookup_default","title":"lookup_default","text":"Map.lookup_default(k : 'k, m : map('k, 'v), v : 'v) : 'v
Returns the value under a key in given map or the default value v
if the key is not present
"},{"location":"sophia_stdlib/#member","title":"member","text":"Map.member(k : 'k, m : map('k, 'v)) : bool
Checks if the key is present in the map
"},{"location":"sophia_stdlib/#delete","title":"delete","text":"Map.delete(k : 'k, m : map('k, 'v)) : map('k, 'v)
Removes the key from the map
"},{"location":"sophia_stdlib/#size","title":"size","text":"Map.size(m : map('k, 'v)) : int
Returns the number of elements in the map
"},{"location":"sophia_stdlib/#to_list","title":"to_list","text":"Map.to_list(m : map('k, 'v)) : list('k * 'v)
Returns a list containing pairs of keys and their respective elements.
"},{"location":"sophia_stdlib/#from_list","title":"from_list","text":"Map.from_list(m : list('k * 'v)) : map('k, 'v)
Turns a list of pairs of form (key, value)
into a map
"},{"location":"sophia_stdlib/#oracle","title":"Oracle","text":""},{"location":"sophia_stdlib/#register","title":"register","text":"Oracle.register(<signature : bytes(64)>, acct : address, qfee : int, ttl : Chain.ttl) : oracle('a, 'b)\n
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). signature
is a signature proving that the contract is allowed to register the account - the network id
+ account address
+ contract address
(concatenated as byte arrays) is signed with the private key of the account, 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))\n Oracle.register(addr1, 25, RelativeTTL(500), signature = sign1)\n
"},{"location":"sophia_stdlib/#get_question","title":"get_question","text":"Oracle.get_question(o : oracle('a, 'b), q : oracle_query('a, 'b)) : 'a\n
Checks what was the question of query q
on oracle o
"},{"location":"sophia_stdlib/#respond","title":"respond","text":"Oracle.respond(<signature : bytes(64)>, o : oracle('a, 'b), q : oracle_query('a, 'b), 'b) : unit\n
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 the network id
+ oracle query id
+ contract address
"},{"location":"sophia_stdlib/#extend","title":"extend","text":"Oracle.extend(<signature : bytes(64)>, o : oracle('a, 'b), ttl : Chain.ttl) : unit\n
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.
"},{"location":"sophia_stdlib/#query_fee","title":"query_fee","text":"Oracle.query_fee(o : oracle('a, 'b)) : int\n
Returns the query fee of the oracle
"},{"location":"sophia_stdlib/#query","title":"query","text":"Oracle.query(o : oracle('a, 'b), q : 'a, qfee : int, qttl : Chain.ttl, rttl : Chain.ttl) : oracle_query('a, 'b)\n
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.
"},{"location":"sophia_stdlib/#get_answer","title":"get_answer","text":"Oracle.get_answer(o : oracle('a, 'b), q : oracle_query('a, 'b)) : option('b)\n
Checks what is the optional query answer
"},{"location":"sophia_stdlib/#expiry","title":"expiry","text":"Oracle.expiry(o : oracle('a, 'b)) : int\n
Ask the oracle when it expires. The result is the block height at which it will happen.
"},{"location":"sophia_stdlib/#check","title":"check","text":"Oracle.check(o : oracle('a, 'b)) : bool\n
Returns true
iff the oracle o
exists and has correct type
"},{"location":"sophia_stdlib/#check_query","title":"check_query","text":"Oracle.check_query(o : oracle('a, 'b), q : oracle_query('a, 'b)) : bool\n
It returns true
iff the oracle query exist and has the expected type.
"},{"location":"sophia_stdlib/#includable-namespaces","title":"Includable namespaces","text":"These need to be explicitly included (with .aes
suffix)
"},{"location":"sophia_stdlib/#bitwise","title":"Bitwise","text":"Bitwise operations on arbitrary precision integers.
"},{"location":"sophia_stdlib/#bsr","title":"bsr","text":"Bitwise.bsr(n : int, x : int) : int\n
Logical bit shift x
right n
positions.
"},{"location":"sophia_stdlib/#bsl","title":"bsl","text":"Bitwise.bsl(n : int, x : int) : int\n
Logical bit shift x
left n
positions.
"},{"location":"sophia_stdlib/#bsli","title":"bsli","text":"Bitwise.bsli(n : int, x : int, lim : int) : int\n
Logical bit shift x
left n
positions, limit to lim
bits.
"},{"location":"sophia_stdlib/#band","title":"band","text":"Bitwise.band(x : int, y : int) : int\n
Bitwise and
of x
and y
.
"},{"location":"sophia_stdlib/#bor","title":"bor","text":"Bitwise.bor(x : int, y : int) : int\n
Bitwise or
of x
and y
.
"},{"location":"sophia_stdlib/#bxor","title":"bxor","text":"Bitwise.bxor(x : int, y : int) : int\n
Bitwise xor
of x
and y
.
"},{"location":"sophia_stdlib/#bnot","title":"bnot","text":"Bitwise.bnot(x : int) : int\n
Bitwise not
of x
. Defined and implemented as bnot(x) = bxor(x, -1)
.
"},{"location":"sophia_stdlib/#uband","title":"uband","text":"Bitwise.uband(x : int, y : int) : int\n
Bitwise and
of non-negative numbers x
and y
.
"},{"location":"sophia_stdlib/#ubor","title":"ubor","text":"Bitwise.ubor(x : int, y : int) : int\n
Bitwise or
of non-negative x
and y
.
"},{"location":"sophia_stdlib/#ubxor","title":"ubxor","text":"Bitwise.ubxor(x : int, y : int) : int\n
Bitwise xor
of non-negative x
and y
.
"},{"location":"sophia_stdlib/#bls12_381","title":"BLS12_381","text":""},{"location":"sophia_stdlib/#types_2","title":"Types","text":""},{"location":"sophia_stdlib/#fr","title":"fr","text":"Built-in (Montgomery) integer representation 32 bytes
"},{"location":"sophia_stdlib/#fp","title":"fp","text":"Built-in (Montgomery) integer representation 48 bytes
"},{"location":"sophia_stdlib/#fp2","title":"fp2","text":"record fp2 = { x1 : fp, x2 : fp }`\n
"},{"location":"sophia_stdlib/#g1","title":"g1","text":"record g1 = { x : fp, y : fp, z : fp }\n
"},{"location":"sophia_stdlib/#g2","title":"g2","text":"record g2 = { x : fp2, y : fp2, z : fp2 }\n
"},{"location":"sophia_stdlib/#gt","title":"gt","text":"record gt = { x1 : fp, x2 : fp, x3 : fp, x4 : fp, x5 : fp, x6 : fp, x7 : fp, x8 : fp, x9 : fp, x10 : fp, x11 : fp, x12 : fp }\n
"},{"location":"sophia_stdlib/#functions_2","title":"Functions","text":""},{"location":"sophia_stdlib/#pairing_check","title":"pairing_check","text":"BLS12_381.pairing_check(xs : list(g1), ys : list(g2)) : bool\n
Pairing check of a list of points, xs
and ys
should be of equal length.
"},{"location":"sophia_stdlib/#int_to_fr","title":"int_to_fr","text":"BLS12_381.int_to_fr(x : int) : fr\n
Convert an integer to an fr
- a 32 bytes internal (Montgomery) integer representation.
"},{"location":"sophia_stdlib/#int_to_fp","title":"int_to_fp","text":"BLS12_381.int_to_fp(x : int) : fp\n
Convert an integer to an fp
- a 48 bytes internal (Montgomery) integer representation.
"},{"location":"sophia_stdlib/#fr_to_int","title":"fr_to_int","text":"BLS12_381.fr_to_int(x : fr) : int\n
Convert a fr
value into an integer.
"},{"location":"sophia_stdlib/#fp_to_int","title":"fp_to_int","text":"BLS12_381.fp_to_int(x : fp) : int\n
Convert a fp
value into an integer.
"},{"location":"sophia_stdlib/#mk_g1","title":"mk_g1","text":"BLS12_381.mk_g1(x : int, y : int, z : int) : g1\n
Construct a g1
point from three integers.
"},{"location":"sophia_stdlib/#mk_g2","title":"mk_g2","text":"BLS12_381.mk_g2(x1 : int, x2 : int, y1 : int, y2 : int, z1 : int, z2 : int) : g2\n
Construct a g2
point from six integers.
"},{"location":"sophia_stdlib/#g1_neg","title":"g1_neg","text":"BLS12_381.g1_neg(p : g1) : g1\n
Negate a g1
value.
"},{"location":"sophia_stdlib/#g1_norm","title":"g1_norm","text":"BLS12_381.g1_norm(p : g1) : g1\n
Normalize a g1
value.
"},{"location":"sophia_stdlib/#g1_valid","title":"g1_valid","text":"BLS12_381.g1_valid(p : g1) : bool\n
Check that a g1
value is a group member.
"},{"location":"sophia_stdlib/#g1_is_zero","title":"g1_is_zero","text":"BLS12_381.g1_is_zero(p : g1) : bool\n
Check if a g1
value corresponds to the zero value of the group.
"},{"location":"sophia_stdlib/#g1_add","title":"g1_add","text":"BLS12_381.g1_add(p : g1, q : g1) : g1\n
Add two g1
values.
"},{"location":"sophia_stdlib/#g1_mul","title":"g1_mul","text":"BLS12_381.g1_mul(k : fr, p : g1) : g1\n
Scalar multiplication for g1
.
"},{"location":"sophia_stdlib/#g2_neg","title":"g2_neg","text":"BLS12_381.g2_neg(p : g2) : g2\n
Negate a g2
value.
"},{"location":"sophia_stdlib/#g2_norm","title":"g2_norm","text":"BLS12_381.g2_norm(p : g2) : g2\n
Normalize a g2
value.
"},{"location":"sophia_stdlib/#g2_valid","title":"g2_valid","text":"BLS12_381.g2_valid(p : g2) : bool\n
Check that a g2
value is a group member.
"},{"location":"sophia_stdlib/#g2_is_zero","title":"g2_is_zero","text":"BLS12_381.g2_is_zero(p : g2) : bool\n
Check if a g2
value corresponds to the zero value of the group.
"},{"location":"sophia_stdlib/#g2_add","title":"g2_add","text":"BLS12_381.g2_add(p : g2, q : g2) : g2\n
Add two g2
values.
"},{"location":"sophia_stdlib/#g2_mul","title":"g2_mul","text":"BLS12_381.g2_mul(k : fr, p : g2) : g2\n
Scalar multiplication for g2
.
"},{"location":"sophia_stdlib/#gt_inv","title":"gt_inv","text":"BLS12_381.gt_inv(p : gt) : gt\n
Invert a gt
value.
"},{"location":"sophia_stdlib/#gt_add","title":"gt_add","text":"BLS12_381.gt_add(p : gt, q : gt) : gt\n
Add two gt
values.
"},{"location":"sophia_stdlib/#gt_mul","title":"gt_mul","text":"BLS12_381.gt_mul(p : gt, q : gt) : gt\n
Multiply two gt
values.
"},{"location":"sophia_stdlib/#gt_pow","title":"gt_pow","text":"BLS12_381.gt_pow(p : gt, k : fr) : gt\n
Calculate exponentiation p ^ k
.
"},{"location":"sophia_stdlib/#gt_is_one","title":"gt_is_one","text":"BLS12_381.gt_is_one(p : gt) : bool\n
Compare a gt
value to the unit value of the Gt group.
"},{"location":"sophia_stdlib/#pairing","title":"pairing","text":"BLS12_381.pairing(p : g1, q : g2) : gt\n
Compute the pairing of a g1
value and a g2
value.
"},{"location":"sophia_stdlib/#miller_loop","title":"miller_loop","text":"BLS12_381.miller_loop(p : g1, q : g2) : gt\n
Do the Miller loop stage of pairing for g1
and g2
.
"},{"location":"sophia_stdlib/#final_exp","title":"final_exp","text":"BLS12_381.final_exp(p : gt) : gt\n
Perform the final exponentiation step of pairing for a gt
value.
"},{"location":"sophia_stdlib/#func","title":"Func","text":"Functional combinators.
"},{"location":"sophia_stdlib/#id","title":"id","text":"Func.id(x : 'a) : 'a\n
Identity function. Returns its argument.
"},{"location":"sophia_stdlib/#const","title":"const","text":"Func.const(x : 'a) : 'b => 'a = (y) => x\n
Constant function constructor. Given x
returns a function that returns x
regardless of its argument.
"},{"location":"sophia_stdlib/#flip","title":"flip","text":"Func.flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c\n
Switches order of arguments of arity 2 function.
"},{"location":"sophia_stdlib/#comp","title":"comp","text":"Func.comp(f : 'b => 'c, g : 'a => 'b) : 'a => 'c\n
Function composition. comp(f, g)(x) == f(g(x))
.
"},{"location":"sophia_stdlib/#pipe","title":"pipe","text":"Func.pipe(f : 'a => 'b, g : 'b => 'c) : 'a => 'c\n
Flipped function composition. pipe(f, g)(x) == g(f(x))
.
"},{"location":"sophia_stdlib/#rapply","title":"rapply","text":"Func.rapply(x : 'a, f : 'a => 'b) : 'b\n
Reverse application. rapply(x, f) == f(x)
.
"},{"location":"sophia_stdlib/#recur","title":"recur","text":"Func.recur(f : ('arg => 'res, 'arg) => 'res) : 'arg => 'res\n
The Z combinator. Allows performing local recursion and having anonymous recursive lambdas. To make function A => B
recursive the user needs to transform it to take two arguments instead \u2013 one of type A => B
which is going to work as a self-reference, and the other one of type A
which is the original argument. Therefore, transformed function should have (A => B, A) => B
signature.
Example usage:
let factorial = recur((fac, n) => if(n < 2) 1 else n * fac(n - 1))\n
If the function is going to take more than one argument it will need to be either tuplified or have curried out latter arguments.
Example (factorial with custom step):
// tuplified version\nlet factorial_t(n, step) =\n let fac(rec, args) =\n let (n, step) = args\n if(n < 2) 1 else n * rec((n - step, step))\n recur(fac)((n, step))\n\n// curried version\nlet factorial_c(n, step) =\n let fac(rec, n) = (step) =>\n if(n < 2) 1 else n * rec(n - 1)(step)\n recur(fac)(n)(step)\n
"},{"location":"sophia_stdlib/#iter","title":"iter","text":"Func.iter(n : int, f : 'a => 'a) : 'a => 'a\n
n
th composition of f with itself, for instance iter(3, f)
is equivalent to (x) => f(f(f(x)))
.
"},{"location":"sophia_stdlib/#curry","title":"curry","text":"Func.curry2(f : ('a, 'b) => 'c) : 'a => ('b => 'c)\nFunc.curry3(f : ('a, 'b, 'c) => 'd) : 'a => ('b => ('c => 'd))\n
Turns a function that takes n arguments into a curried function that takes one argument and returns a function that waits for the rest in the same manner. For instance curry2((a, b) => a + b)(1)(2) == 3
.
"},{"location":"sophia_stdlib/#uncurry","title":"uncurry","text":"Func.uncurry2(f : 'a => ('b => 'c)) : ('a, 'b) => 'c\nFunc.uncurry3(f : 'a => ('b => ('c => 'd))) : ('a, 'b, 'c) => 'd\n
Opposite to curry.
"},{"location":"sophia_stdlib/#tuplify","title":"tuplify","text":"Func.tuplify2(f : ('a, 'b) => 'c) : (('a * 'b)) => 'c\nFunc.tuplify3(f : ('a, 'b, 'c) => 'd) : 'a * 'b * 'c => 'd\n
Turns a function that takes n arguments into a function that takes an n-tuple.
"},{"location":"sophia_stdlib/#untuplify","title":"untuplify","text":"Func.untuplify2(f : 'a * 'b => 'c) : ('a, 'b) => 'c\nFunc.untuplify3(f : 'a * 'b * 'c => 'd) : ('a, 'b, 'c) => 'd\n
Opposite to tuplify.
"},{"location":"sophia_stdlib/#frac","title":"Frac","text":"This namespace provides operations on rational numbers. A rational number is represented as a fraction of two integers which are stored internally in the frac
datatype.
The datatype consists of three constructors Neg/2
, Zero/0
and Pos/2
which determine the sign of the number. Both values stored in Neg
and Pos
need to be strictly positive integers. However, when creating a frac
you should never use the constructors explicitly. Instead of that, always use provided functions like make_frac
or from_int
. This helps keeping the internal representation well defined.
The described below functions take care of the normalization of the fractions \u2013 they won't grow if it is unnecessary. Please note that the size of frac
can be still very big while the value is actually very close to a natural number \u2013 the division of two extremely big prime numbers will be as big as both of them. To face this issue the optimize function is provided. It will approximate the value of the fraction to fit in the given error margin and to shrink its size as much as possible.
Important note: frac
must not be compared using standard <
-like operators. The operator comparison is not possible to overload at this moment, nor the language provides checkers to prevent unintended usage of them. Therefore the typechecker will allow that and the results of such comparison will be unspecified. You should use lt, geq, eq etc instead.
"},{"location":"sophia_stdlib/#types_3","title":"Types","text":""},{"location":"sophia_stdlib/#frac_1","title":"frac","text":"datatype frac = Pos(int, int) | Zero | Neg(int, int)\n
Internal representation of fractional numbers. First integer encodes the numerator and the second the denominator \u2013 both must be always positive, as the sign is being handled by the choice of the constructor.
"},{"location":"sophia_stdlib/#functions_3","title":"Functions","text":""},{"location":"sophia_stdlib/#make_frac","title":"make_frac","text":"Frac.make_frac(n : int, d : int) : frac
Creates a fraction out of numerator and denominator. Automatically normalizes, so make_frac(2, 4)
and make_frac(1, 2)
will yield same results.
"},{"location":"sophia_stdlib/#num","title":"num","text":"Frac.num(f : frac) : int
Returns the numerator of a fraction.
"},{"location":"sophia_stdlib/#den","title":"den","text":"Frac.den(f : frac) : int
Returns the denominator of a fraction.
"},{"location":"sophia_stdlib/#to_pair","title":"to_pair","text":"Frac.to_pair(f : frac) : int * int
Turns a fraction into a pair of numerator and denominator.
"},{"location":"sophia_stdlib/#sign","title":"sign","text":"Frac.sign(f : frac) : int
Returns the signum of a fraction, -1, 0, 1 if negative, zero, positive respectively.
"},{"location":"sophia_stdlib/#to_str_3","title":"to_str","text":"Frac.to_str(f : frac) : string
Conversion to string. Does not display division by 1 or denominator if equals zero.
"},{"location":"sophia_stdlib/#simplify","title":"simplify","text":"Frac.simplify(f : frac) : frac
Reduces fraction to normal form if for some reason it is not in it.
"},{"location":"sophia_stdlib/#eq","title":"eq","text":"Frac.eq(a : frac, b : frac) : bool
Checks if a
is equal to b
.
"},{"location":"sophia_stdlib/#neq","title":"neq","text":"Frac.neq(a : frac, b : frac) : bool
Checks if a
is not equal to b
.
"},{"location":"sophia_stdlib/#geq","title":"geq","text":"Frac.geq(a : frac, b : frac) : bool
Checks if a
is greater or equal to b
.
"},{"location":"sophia_stdlib/#leq","title":"leq","text":"Frac.leq(a : frac, b : frac) : bool
Checks if a
is lesser or equal to b
.
"},{"location":"sophia_stdlib/#gt_1","title":"gt","text":"Frac.gt(a : frac, b : frac) : bool
Checks if a
is greater than b
.
"},{"location":"sophia_stdlib/#lt","title":"lt","text":"Frac.lt(a : frac, b : frac) : bool
Checks if a
is lesser than b
.
"},{"location":"sophia_stdlib/#min","title":"min","text":"Frac.min(a : frac, b : frac) : frac
Chooses lesser of the two fractions.
"},{"location":"sophia_stdlib/#max","title":"max","text":"Frac.max(a : frac, b : frac) : frac
Chooses greater of the two fractions.
"},{"location":"sophia_stdlib/#abs","title":"abs","text":"Frac.abs(f : frac) : frac
Absolute value.
"},{"location":"sophia_stdlib/#from_int","title":"from_int","text":"Frac.from_int(n : int) : frac
From integer conversion. Effectively make_frac(n, 1)
.
"},{"location":"sophia_stdlib/#floor","title":"floor","text":"Frac.floor(f : frac) : int
Rounds a fraction to the nearest lesser or equal integer.
"},{"location":"sophia_stdlib/#ceil","title":"ceil","text":"Frac.ceil(f : frac) : int
Rounds a fraction to the nearest greater or equal integer.
"},{"location":"sophia_stdlib/#round_to_zero","title":"round_to_zero","text":"Frac.round_to_zero(f : frac) : int
Rounds a fraction towards zero. Effectively ceil
if lesser than zero and floor
if greater.
"},{"location":"sophia_stdlib/#round_from_zero","title":"round_from_zero","text":"Frac.round_from_zero(f : frac) : int
Rounds a fraction from zero. Effectively ceil
if greater than zero and floor
if lesser.
"},{"location":"sophia_stdlib/#round","title":"round","text":"Frac.round(f : frac) : int
Rounds a fraction to a nearest integer. If two integers are in the same distance it will choose the even one.
"},{"location":"sophia_stdlib/#add","title":"add","text":"Frac.add(a : frac, b : frac) : frac
Sum of the fractions.
"},{"location":"sophia_stdlib/#neg","title":"neg","text":"Frac.neg(a : frac) : frac
Negation of the fraction.
"},{"location":"sophia_stdlib/#sub","title":"sub","text":"Frac.sub(a : frac, b : frac) : frac
Subtraction of two fractions.
"},{"location":"sophia_stdlib/#inv","title":"inv","text":"Frac.inv(a : frac) : frac
Inverts a fraction. Throws error if a
is zero.
"},{"location":"sophia_stdlib/#mul","title":"mul","text":"Frac.mul(a : frac, b : frac) : frac
Multiplication of two fractions.
"},{"location":"sophia_stdlib/#div","title":"div","text":"Frac.div(a : frac, b : frac) : frac
Division of two fractions.
"},{"location":"sophia_stdlib/#int_exp","title":"int_exp","text":"Frac.int_exp(b : frac, e : int) : frac
Takes b
to the power of e
. The exponent can be a negative value.
"},{"location":"sophia_stdlib/#optimize","title":"optimize","text":"Frac.optimize(f : frac, loss : frac) : frac
Shrink the internal size of a fraction as much as possible by approximating it to the point where the error would exceed the loss
value.
"},{"location":"sophia_stdlib/#is_sane","title":"is_sane","text":"Frac.is_sane(f : frac) : bool
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
If you expect getting calls with malformed frac
s in your contract, you should use this function to verify the input.
"},{"location":"sophia_stdlib/#list","title":"List","text":"This module contains common operations on lists like constructing, querying, traversing etc.
"},{"location":"sophia_stdlib/#is_empty","title":"is_empty","text":"List.is_empty(l : list('a)) : bool\n
Returns true
iff the list is equal to []
.
"},{"location":"sophia_stdlib/#first","title":"first","text":"List.first(l : list('a)) : option('a)\n
Returns Some
of the first element of a list or None
if the list is empty.
"},{"location":"sophia_stdlib/#tail","title":"tail","text":"List.tail(l : list('a)) : option(list('a))\n
Returns Some
of a list without its first element or None
if the list is empty.
"},{"location":"sophia_stdlib/#last","title":"last","text":"List.last(l : list('a)) : option('a)\n
Returns Some
of the last element of a list or None
if the list is empty.
"},{"location":"sophia_stdlib/#contains","title":"contains","text":"List.contains(e : 'a, l : list('a)) : bool\n
Checks if list l
contains element e
. Equivalent to List.find(x => x == e, l) != None
."},{"location":"sophia_stdlib/#find","title":"find","text":"List.find(p : 'a => bool, l : list('a)) : option('a)\n
Finds first element of l
fulfilling predicate p
as Some
or None
if no such element exists.
"},{"location":"sophia_stdlib/#find_indices","title":"find_indices","text":"List.find_indices(p : 'a => bool, l : list('a)) : list(int)\n
Returns list of all indices of elements from l
that fulfill the predicate p
.
"},{"location":"sophia_stdlib/#nth","title":"nth","text":"List.nth(n : int, l : list('a)) : option('a)\n
Gets n
th element of l
as Some
or None
if l
is shorter than n + 1
or n
is negative.
"},{"location":"sophia_stdlib/#get","title":"get","text":"List.get(n : int, l : list('a)) : 'a\n
Gets n
th element of l
forcefully, throwing and error if l
is shorter than n + 1
or n
is negative.
"},{"location":"sophia_stdlib/#length","title":"length","text":"List.length(l : list('a)) : int\n
Returns length of a list.
"},{"location":"sophia_stdlib/#from_to","title":"from_to","text":"List.from_to(a : int, b : int) : list(int)\n
Creates an ascending sequence of all integer numbers between a
and b
(including a
and b
).
"},{"location":"sophia_stdlib/#from_to_step","title":"from_to_step","text":"List.from_to_step(a : int, b : int, step : int) : list(int)\n
Creates an ascending sequence of integer numbers betweeen a
and b
jumping by given step
. Includes a
and takes b
only if (b - a) mod step == 0
. step
should be bigger than 0.
"},{"location":"sophia_stdlib/#replace_at","title":"replace_at","text":"List.replace_at(n : int, e : 'a, l : list('a)) : list('a)\n
Replaces n
th element of l
with e
. Throws an error if n
is negative or would cause an overflow.
"},{"location":"sophia_stdlib/#insert_at","title":"insert_at","text":"List.insert_at(n : int, e : 'a, l : list('a)) : list('a)\n
Inserts e
into l
to be on position n
by shifting following elements further. For instance,
insert_at(2, 9, [1,2,3,4])\n
will yield [1,2,9,3,4]
."},{"location":"sophia_stdlib/#insert_by","title":"insert_by","text":"List.insert_by(cmp : (('a, 'a) => bool), x : 'a, l : list('a)) : list('a)\n
Assuming that cmp represents <
comparison, inserts x
before the first element in the list l
which is greater than it. For instance,
insert_by((a, b) => a < b, 4, [1,2,3,5,6,7])\n
will yield [1,2,3,4,5,6,7]
"},{"location":"sophia_stdlib/#foldr","title":"foldr","text":"List.foldr(cons : ('a, 'b) => 'b, nil : 'b, l : list('a)) : 'b\n
Right fold of a list. Assuming l = [x, y, z]
will return f(x, f(y, f(z, nil)))
. Not tail recursive.
"},{"location":"sophia_stdlib/#foldl","title":"foldl","text":"List.foldl(rcons : ('b, 'a) => 'b, acc : 'b, l : list('a)) : 'b\n
Left fold of a list. Assuming l = [x, y, z]
will return f(f(f(acc, x), y), z)
. Tail recursive.
"},{"location":"sophia_stdlib/#foreach","title":"foreach","text":"List.foreach(l : list('a), f : 'a => unit) : unit\n
Evaluates f
on each element of a list.
"},{"location":"sophia_stdlib/#reverse","title":"reverse","text":"List.reverse(l : list('a)) : list('a)\n
Returns a copy of l
with reversed order of elements.
"},{"location":"sophia_stdlib/#map_1","title":"map","text":"List.map(f : 'a => 'b, l : list('a)) : list('b)\n
Maps function f
over a list. For instance
map((x) => x == 0, [1, 2, 0, 3, 0])\n
will yield [false, false, true, false, true]
"},{"location":"sophia_stdlib/#flat_map","title":"flat_map","text":"List.flat_map(f : 'a => list('b), l : list('a)) : list('b)\n
Maps f
over a list and then flattens it. For instance
flat_map((x) => [x, x * 10], [1, 2, 3])\n
will yield [1, 10, 2, 20, 3, 30]
"},{"location":"sophia_stdlib/#filter","title":"filter","text":"List.filter(p : 'a => bool, l : list('a)) : list('a)\n
Filters out elements of l
that fulfill predicate p
. For instance
filter((x) => x > 0, [-1, 1, -2, 0, 1, 2, -3])\n
will yield [1, 1, 2]
"},{"location":"sophia_stdlib/#take","title":"take","text":"List.take(n : int, l : list('a)) : list('a)\n
Takes n
first elements of l
. Fails if n
is negative. If n
is greater than length of a list it will return whole list.
"},{"location":"sophia_stdlib/#drop","title":"drop","text":"List.drop(n : int, l : list('a)) : list('a)\n
Removes n
first elements of l
. Fails if n
is negative. If n
is greater than length of a list it will return []
.
"},{"location":"sophia_stdlib/#take_while","title":"take_while","text":"List.take_while(p : 'a => bool, l : list('a)) : list('a)\n
Returns longest prefix of l
in which all elements fulfill p
.
"},{"location":"sophia_stdlib/#drop_while","title":"drop_while","text":"List.drop_while(p : 'a => bool, l : list('a)) : list('a)\n
Removes longest prefix from l
in which all elements fulfill p
.
"},{"location":"sophia_stdlib/#partition","title":"partition","text":"List.partition(p : 'a => bool, l : list('a)) : (list('a) * list('a))\n
Separates elements of l
that fulfill p
and these that do not. Elements fulfilling predicate will be in the right list. For instance
partition((x) => x > 0, [-1, 1, -2, 0, 1, 2, -3])\n
will yield ([1, 1, 2], [-1, -2, 0, -3])
"},{"location":"sophia_stdlib/#flatten","title":"flatten","text":"List.flatten(ll : list(list('a))) : list('a)\n
Flattens a list of lists into a one list.
"},{"location":"sophia_stdlib/#all_1","title":"all","text":"List.all(p : 'a => bool, l : list('a)) : bool\n
Checks if all elements of a list fulfill predicate p
.
"},{"location":"sophia_stdlib/#any","title":"any","text":"List.any(p : 'a => bool, l : list('a)) : bool\n
Checks if any element of a list fulfills predicate p
.
"},{"location":"sophia_stdlib/#sum_1","title":"sum","text":"List.sum(l : list(int)) : int\n
Sums elements of a list. Returns 0 if the list is empty.
"},{"location":"sophia_stdlib/#product","title":"product","text":"List.product(l : list(int)) : int\n
Multiplies elements of a list. Returns 1 if the list is empty.
"},{"location":"sophia_stdlib/#zip_with","title":"zip_with","text":"List.zip_with(f : ('a, 'b) => 'c, l1 : list('a), l2 : list('b)) : list('c)\n
\"zips\" two lists with a function. n-th element of resulting list will be equal to f(x1, x2)
where x1
and x2
are n-th elements of l1
and l2
respectively. Will cut off the tail of the longer list. For instance
zip_with((a, b) => a + b, [1,2], [1,2,3])\n
will yield [2,4]
"},{"location":"sophia_stdlib/#zip","title":"zip","text":"List.zip(l1 : list('a), l2 : list('b)) : list('a * 'b)\n
Special case of zip_with where the zipping function is (a, b) => (a, b)
.
"},{"location":"sophia_stdlib/#unzip","title":"unzip","text":"List.unzip(l : list('a * 'b)) : list('a) * list('b)\n
Opposite to the zip
operation. Takes a list of pairs and returns pair of lists with respective elements on same indices.
"},{"location":"sophia_stdlib/#merge","title":"merge","text":"List.merge(lesser_cmp : ('a, 'a) => bool, l1 : list('a), l2 : list('a)) : list('a)\n
Merges two sorted lists into a single sorted list. O(length(l1) + length(l2))
"},{"location":"sophia_stdlib/#sort","title":"sort","text":"List.sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a)\n
Sorts a list using given comparator. lesser_cmp(x, y)
should return true
iff x < y
. If lesser_cmp
is not transitive or there exists an element x
such that lesser_cmp(x, x)
or there exists a pair of elements x
and y
such that lesser_cmp(x, y) && lesser_cmp(y, x)
then the result is undefined. O(length(l) * log_2(length(l))).
"},{"location":"sophia_stdlib/#intersperse","title":"intersperse","text":"List.intersperse(delim : 'a, l : list('a)) : list('a)\n
Intersperses elements of l
with delim
. Does nothing on empty lists and singletons. For instance
intersperse(0, [1, 2, 3, 4])\n
will yield [1, 0, 2, 0, 3, 0, 4]
"},{"location":"sophia_stdlib/#enumerate","title":"enumerate","text":"List.enumerate(l : list('a)) : list(int * 'a)\n
Equivalent to zip with [0..length(l)]
, but slightly faster.
"},{"location":"sophia_stdlib/#option","title":"Option","text":"Common operations on option
types and lists of option
s.
"},{"location":"sophia_stdlib/#is_none","title":"is_none","text":"Option.is_none(o : option('a)) : bool\n
Returns true iff o == None
"},{"location":"sophia_stdlib/#is_some","title":"is_some","text":"Option.is_some(o : option('a)) : bool\n
Returns true iff o
is not None
.
"},{"location":"sophia_stdlib/#match","title":"match","text":"Option.match(n : 'b, s : 'a => 'b, o : option('a)) : 'b\n
Behaves like pattern matching on option
using two case functions.
"},{"location":"sophia_stdlib/#default","title":"default","text":"Option.default(def : 'a, o : option('a)) : 'a\n
Escapes option
wrapping by providing default value for None
.
"},{"location":"sophia_stdlib/#force","title":"force","text":"Option.force(o : option('a)) : 'a\n
Forcefully escapes the option
wrapping assuming it is Some
. Aborts on None
.
"},{"location":"sophia_stdlib/#force_msg","title":"force_msg","text":"Option.force_msg(o : option('a), err : string) : 'a\n
Forcefully escapes the option
wrapping assuming it is Some
. Aborts with err
error message on None
.
"},{"location":"sophia_stdlib/#contains_1","title":"contains","text":"Option.contains(e : 'a, o : option('a)) : bool\n
Returns true
if and only if o
contains element equal to e
. Equivalent to Option.match(false, x => x == e, o)
."},{"location":"sophia_stdlib/#on_elem","title":"on_elem","text":"Option.on_elem(o : option('a), f : 'a => unit) : unit\n
Evaluates f
on element under Some
. Does nothing on None
.
"},{"location":"sophia_stdlib/#map_2","title":"map","text":"Option.map(f : 'a => 'b, o : option('a)) : option('b)\n
Maps element under Some
. Leaves None
unchanged.
"},{"location":"sophia_stdlib/#map2","title":"map2","text":"Option.map2(f : ('a, 'b) => 'c, o1 : option('a), o2 : option('b)) : option('c)\n
Applies arity 2 function over two option
s' elements. Returns Some
iff both of o1
and o2
were Some
, or None
otherwise. For instance
map2((a, b) => a + b, Some(1), Some(2))\n
will yield Some(3)
and map2((a, b) => a + b, Some(1), None)\n
will yield None
."},{"location":"sophia_stdlib/#map3","title":"map3","text":"Option.map3(f : ('a, 'b, 'c) => 'd, o1 : option('a), o2 : option('b), o3 : option('c)) : option('d)\n
Same as map2 but with arity 3 function.
"},{"location":"sophia_stdlib/#app_over","title":"app_over","text":"Option.app_over(f : option ('a => 'b), o : option('a)) : option('b)\n
Applies function under option
over argument under option
. If either of them is None
the result will be None
as well. For instance
app_over(Some((x) => x + 1), Some(1))\n
will yield Some(2)
and app_over(Some((x) => x + 1), None)\n
will yield None
."},{"location":"sophia_stdlib/#flat_map_1","title":"flat_map","text":"Option.flat_map(f : 'a => option('b), o : option('a)) : option('b)\n
Performs monadic bind on an option
. Extracts element from o
(if present) and forms new option
from it. For instance
flat_map((x) => Some(x + 1), Some(1))\n
will yield Some(2)
and flat_map((x) => Some(x + 1), None)\n
will yield None
."},{"location":"sophia_stdlib/#to_list_1","title":"to_list","text":"Option.to_list(o : option('a)) : list('a)\n
Turns o
into an empty (if None
) or singleton (if Some
) list.
"},{"location":"sophia_stdlib/#filter_options","title":"filter_options","text":"Option.filter_options(l : list(option('a))) : list('a)\n
Removes None
s from list and unpacks all remaining Some
s. For instance
filter_options([Some(1), None, Some(2)])\n
will yield [1, 2]
."},{"location":"sophia_stdlib/#seq_options","title":"seq_options","text":"Option.seq_options(l : list (option('a))) : option (list('a))\n
Tries to unpack all elements of a list from Some
s. Returns None
if at least element of l
is None
. For instance
seq_options([Some(1), Some(2)])\n
will yield Some([1, 2])
, but seq_options([Some(1), Some(2), None])\n
will yield None
."},{"location":"sophia_stdlib/#choose","title":"choose","text":"Option.choose(o1 : option('a), o2 : option('a)) : option('a)\n
Out of two option
s choose the one that is Some
, or None
if both are None
s.
"},{"location":"sophia_stdlib/#choose_first","title":"choose_first","text":"Option.choose_first(l : list(option('a))) : option('a)\n
Same as choose, but chooses from a list insted of two arguments.
"},{"location":"sophia_stdlib/#pair","title":"Pair","text":"Common operations on 2-tuples.
"},{"location":"sophia_stdlib/#fst","title":"fst","text":"Pair.fst(t : ('a * 'b)) : 'a\n
First element projection.
"},{"location":"sophia_stdlib/#snd","title":"snd","text":"Pair.snd(t : ('a * 'b)) : 'b\n
Second element projection.
"},{"location":"sophia_stdlib/#map1","title":"map1","text":"Pair.map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b)\n
Applies function over first element.
"},{"location":"sophia_stdlib/#map2_1","title":"map2","text":"Pair.map2(f : 'b => 'c, t : ('a * 'b)) : ('a * 'c)\n
Applies function over second element.
"},{"location":"sophia_stdlib/#bimap","title":"bimap","text":"Pair.bimap(f : 'a => 'c, g : 'b => 'd, t : ('a * 'b)) : ('c * 'd)\n
Applies functions over respective elements.
"},{"location":"sophia_stdlib/#swap","title":"swap","text":"Pair.swap(t : ('a * 'b)) : ('b * 'a)\n
Swaps elements.
"},{"location":"sophia_stdlib/#set_1","title":"Set","text":""},{"location":"sophia_stdlib/#types_4","title":"Types","text":"record set('a) = { to_map : map('a, unit) }\n
"},{"location":"sophia_stdlib/#functions_4","title":"Functions","text":""},{"location":"sophia_stdlib/#new","title":"new","text":"Set.new() : set('a)\n
Returns an empty set
"},{"location":"sophia_stdlib/#member_1","title":"member","text":"member(e : 'a, s : set('a)) : bool\n
Checks if the element e
is present in the set s
"},{"location":"sophia_stdlib/#insert","title":"insert","text":"insert(e : 'a, s : set('a)) : set('a)\n
Inserts the element e
in the set s
"},{"location":"sophia_stdlib/#delete_1","title":"delete","text":"Set.delete(e : 'a, s : set('a)) : set('a)\n
Removes the element e
from the set s
"},{"location":"sophia_stdlib/#size_1","title":"size","text":"size(s : set('a)) : int\n
Returns the number of elements in the set s
"},{"location":"sophia_stdlib/#to_list_2","title":"to_list","text":"Set.to_list(s : set('a)) : list('a)\n
Returns a list containing the elements of the set s
"},{"location":"sophia_stdlib/#from_list_1","title":"from_list","text":"Set.from_list(l : list('a)) : set('a)\n
Turns the list l
into a set
"},{"location":"sophia_stdlib/#filter_1","title":"filter","text":"Set.filter(p : 'a => bool, s : set('a)) : set('a)\n
Filters out elements of s
that fulfill predicate p
"},{"location":"sophia_stdlib/#fold","title":"fold","text":"Set.fold(f : ('a, 'b) => 'b, acc : 'b, s : set('a)) : 'b\n
Folds the function f
over every element in the set s
and returns the final value of the accumulator acc
.
"},{"location":"sophia_stdlib/#subtract","title":"subtract","text":"Set.subtract(s1 : set('a), s2 : set('a)) : set('a)\n
Returns the elements of s1
that are not members of s2
"},{"location":"sophia_stdlib/#intersection_1","title":"intersection","text":"Set.intersection(s1 : set('a), s2 : set('a)) : set('a)\n
Returns the intersection of the two sets s1
and s2
"},{"location":"sophia_stdlib/#intersection_list","title":"intersection_list","text":"Set.intersection_list(sets : list(set('a))) : set('a)\n
Returns the intersection of all the sets in the given list
"},{"location":"sophia_stdlib/#union_1","title":"union","text":"Set.union(s1 : set('a), s2 : set('a)) : set('a)\n
Returns the union of the two sets s1
and s2
"},{"location":"sophia_stdlib/#union_list","title":"union_list","text":"Set.union_list(sets : list(set('a))) : set('a)\n
Returns the union of all the sets in the given list
"},{"location":"sophia_stdlib/#string","title":"String","text":"Operations on the string
type. A string
is a UTF-8 encoded byte array.
"},{"location":"sophia_stdlib/#length_1","title":"length","text":"length(s : string) : int
The length of a string.
Note: not equivalent to byte size of the string, rather List.length(String.to_list(s))
"},{"location":"sophia_stdlib/#concat_1","title":"concat","text":"concat(s1 : string, s2 : string) : string\n
Concatenates s1
and s2
.
"},{"location":"sophia_stdlib/#concats","title":"concats","text":"concats(ss : list(string)) : string\n
Concatenates a list of strings.
"},{"location":"sophia_stdlib/#to_list_3","title":"to_list","text":"to_list(s : string) : list(char)\n
Converts a string
to a list of char
- the code points are normalized, but composite characters are possibly converted to multiple char
s. For example the string \"\ud83d\ude1ci\u0307\" is converted to [128540,105,775]
- where the smiley is the first code point and the strangely dotted i
becomes [105, 775]
.
"},{"location":"sophia_stdlib/#from_list_2","title":"from_list","text":"from_list(cs : list(char)) : string\n
Converts a list of characters into a normalized UTF-8 string.
"},{"location":"sophia_stdlib/#to_lower","title":"to_lower","text":"to_lower(s : string) : string\n
Converts a string to lowercase.
"},{"location":"sophia_stdlib/#to_upper","title":"to_upper","text":"to_upper(s : string) : string\n
Converts a string to uppercase.
"},{"location":"sophia_stdlib/#at","title":"at","text":"at(ix : int, s : string) : option(char)\n
Returns the character/codepoint at (zero-based) index ix
. Basically the equivalent to List.nth(ix, String.to_list(s))
.
"},{"location":"sophia_stdlib/#split_1","title":"split","text":"split(ix : int, s:string) : string * string\n
Splits a string at (zero-based) index ix
.
"},{"location":"sophia_stdlib/#contains_2","title":"contains","text":"contains(str : string, pat : string) : option(int)\n
Searches for pat
in str
, returning Some(ix)
if pat
is a substring of str
starting at position ix
, otherwise returns None
.
"},{"location":"sophia_stdlib/#tokens","title":"tokens","text":"tokens(str : string, pat : string) : list(string)\n
Splits str
into tokens, pat
is the divider of tokens.
"},{"location":"sophia_stdlib/#to_int_2","title":"to_int","text":"to_int(s : string) : option(int)\n
Converts a decimal (\"123\", \"-253\") or a hexadecimal (\"0xa2f\", \"-0xBBB\") string into an integer. If the string doesn't contain a valid number None
is returned.
"},{"location":"sophia_stdlib/#sha3_1","title":"sha3","text":"sha3(s : string) : hash\n
Computes the SHA3/Keccak hash of the string.
"},{"location":"sophia_stdlib/#sha256_1","title":"sha256","text":"sha256(s : string) : hash\n
Computes the SHA256 hash of the string.
"},{"location":"sophia_stdlib/#blake2b_1","title":"blake2b","text":"blake2b(s : string) : hash\n
Computes the Blake2B hash of the string.
"},{"location":"sophia_stdlib/#triple","title":"Triple","text":""},{"location":"sophia_stdlib/#fst_1","title":"fst","text":"Triple.fst(t : ('a * 'b * 'c)) : 'a\n
First element projection.
"},{"location":"sophia_stdlib/#snd_1","title":"snd","text":"Triple.snd(t : ('a * 'b * 'c)) : 'b\n
Second element projection.
"},{"location":"sophia_stdlib/#thd","title":"thd","text":"Triple.thd(t : ('a * 'b * 'c)) : 'c\n
Third element projection.
"},{"location":"sophia_stdlib/#map1_1","title":"map1","text":"Triple.map1(f : 'a => 'm, t : ('a * 'b * 'c)) : ('m * 'b * 'c)\n
Applies function over first element.
"},{"location":"sophia_stdlib/#map2_2","title":"map2","text":"Triple.map2(f : 'b => 'm, t : ('a * 'b * 'c)) : ('a * 'm * 'c)\n
Applies function over second element.
"},{"location":"sophia_stdlib/#map3_1","title":"map3","text":"Triple.map3(f : 'c => 'm, t : ('a * 'b * 'c)) : ('a * 'b * 'm)\n
Applies function over third element.
"},{"location":"sophia_stdlib/#trimap","title":"trimap","text":"Triple.trimap(f : 'a => 'x, g : 'b => 'y, h : 'c => 'z, t : ('a * 'b * 'c)) : ('x * 'y * 'z)\n
Applies functions over respective elements.
"},{"location":"sophia_stdlib/#swap_1","title":"swap","text":"Triple.swap(t : ('a * 'b * 'c)) : ('c * 'b * 'a)\n
Swaps first and third element.
"},{"location":"sophia_stdlib/#rotr","title":"rotr","text":"Triple.rotr(t : ('a * 'b * 'c)) : ('c * 'a * 'b)\n
Cyclic rotation of the elements to the right.
"},{"location":"sophia_stdlib/#rotl","title":"rotl","text":"Triple.rotl(t : ('a * 'b * 'c)) : ('b * 'c * 'a)\n
Cyclic rotation of the elements to the left.
"},{"location":"sophia_syntax/","title":"Syntax","text":""},{"location":"sophia_syntax/#lexical-syntax","title":"Lexical syntax","text":""},{"location":"sophia_syntax/#comments","title":"Comments","text":"Single line comments start with //
and block comments are enclosed in /*
and */
and can be nested.
"},{"location":"sophia_syntax/#keywords","title":"Keywords","text":"contract include let switch type record datatype if elif else function\nstateful payable true false mod public entrypoint private indexed namespace\ninterface main using as for hiding\n
"},{"location":"sophia_syntax/#tokens","title":"Tokens","text":" Id = [a-z_][A-Za-z0-9_']*
identifiers start with a lower case letter. Con = [A-Z][A-Za-z0-9_']*
constructors start with an upper case letter. QId = (Con\\.)+Id
qualified identifiers (e.g. Map.member
) QCon = (Con\\.)+Con
qualified constructor TVar = 'Id
type variable (e.g 'a
, 'b
) Int = [0-9]+(_[0-9]+)*|0x[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*
integer literal with optional _
separators Bytes = #[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*
byte array literal with optional _
separators String
string literal enclosed in \"
with escape character \\
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
Valid string escape codes are
Escape ASCII \\b
8 \\t
9 \\n
10 \\v
11 \\f
12 \\r
13 \\e
27 \\xHexDigits
HexDigits See the identifier encoding scheme for the details on the base58 literals.
"},{"location":"sophia_syntax/#layout-blocks","title":"Layout blocks","text":"Sophia uses Python-style layout rules to group declarations and statements. A layout block with more than one element must start on a separate line and be indented more than the currently enclosing layout block. Blocks with a single element can be written on the same line as the previous token.
Each element of the block must share the same indentation and no part of an element may be indented less than the indentation of the block. For instance
contract Layout =\nfunction foo() = 0 // no layout\nfunction bar() = // layout block starts on next line\nlet x = foo() // indented more than 2 spaces\nx\n+ 1 // the '+' is indented more than the 'x'\n
"},{"location":"sophia_syntax/#notation","title":"Notation","text":"In describing the syntax below, we use the following conventions:
- Upper-case identifiers denote non-terminals (like
Expr
) or terminals with some associated value (like Id
). - Keywords and symbols are enclosed in single quotes:
'let'
or '='
. - Choices are separated by vertical bars:
|
. - Optional elements are enclosed in
[
square brackets ]
. (
Parentheses )
are used for grouping. - Zero or more repetitions are denoted by a postfix
*
, and one or more repetitions by a +
. Block(X)
denotes a layout block of X
s. Sep(X, S)
is short for [X (S X)*]
, i.e. a possibly empty sequence of X
s separated by S
s. Sep1(X, S)
is short for X (S X)*
, i.e. same as Sep
, but must not be empty.
"},{"location":"sophia_syntax/#declarations","title":"Declarations","text":"A Sophia file consists of a sequence of declarations in a layout block.
File ::= Block(TopDecl)\n\nTopDecl ::= ['payable'] ['main'] 'contract' Con [Implement] '=' Block(Decl)\n| 'contract' 'interface' Con [Implement] '=' Block(Decl)\n| 'namespace' Con '=' Block(Decl)\n| '@compiler' PragmaOp Version\n| 'include' String\n| Using\n\nImplement ::= ':' Sep1(Con, ',')\n\nDecl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias\n| 'record' Id ['(' TVar* ')'] '=' RecordType\n| 'datatype' Id ['(' TVar* ')'] '=' DataType\n| 'let' Id [':' Type] '=' Expr\n| (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)\n| Using\n\nFunDecl ::= Id ':' Type // Type signature\n| Id Args [':' Type] '=' Block(Stmt) // Definition\n| Id Args [':' Type] Block(GuardedDef) // Guarded definitions\n\nGuardedDef ::= '|' Sep1(Expr, ',') '=' Block(Stmt)\n\nUsing ::= 'using' Con ['as' Con] [UsingParts]\nUsingParts ::= 'for' '[' Sep1(Id, ',') ']'\n| 'hiding' '[' Sep1(Id, ',') ']'\n\nPragmaOp ::= '<' | '=<' | '==' | '>=' | '>'\nVersion ::= Sep1(Int, '.')\n\nEModifier ::= 'payable' | 'stateful'\nFModifier ::= 'stateful' | 'private'\n\nArgs ::= '(' Sep(Pattern, ',') ')'\n
Contract declarations must appear at the top-level.
For example,
contract Test =\ntype t = int\nentrypoint add (x : t, y : t) = x + y\n
There are three forms of type declarations: type aliases (declared with the type
keyword), record type definitions (record
) and data type definitions (datatype
):
TypeAlias ::= Type\nRecordType ::= '{' Sep(FieldType, ',') '}'\nDataType ::= Sep1(ConDecl, '|')\n\nFieldType ::= Id ':' Type\nConDecl ::= Con ['(' Sep1(Type, ',') ')']\n
For example,
record point('a) = {x : 'a, y : 'a}\ndatatype shape('a) = Circle(point('a), 'a) | Rect(point('a), point('a))\ntype int_shape = shape(int)\n
"},{"location":"sophia_syntax/#types","title":"Types","text":"Type ::= Domain '=>' Type // Function type\n| Type '(' Sep(Type, ',') ')' // Type application\n| '(' Type ')' // Parens\n| 'unit' | Sep(Type, '*') // Tuples\n| Id | QId | TVar\n\nDomain ::= Type // Single argument\n| '(' Sep(Type, ',') ')' // Multiple arguments\n
The function type arrow associates to the right.
Example,
'a => list('a) => (int * list('a))\n
"},{"location":"sophia_syntax/#statements","title":"Statements","text":"Function bodies are blocks of statements, where a statement is one of the following
Stmt ::= 'switch' '(' Expr ')' Block(Case)\n| 'if' '(' Expr ')' Block(Stmt)\n| 'elif' '(' Expr ')' Block(Stmt)\n| 'else' Block(Stmt)\n| 'let' LetDef\n| Using\n| Expr\n\nLetDef ::= Id Args [':' Type] '=' Block(Stmt) // Function definition\n| Pattern '=' Block(Stmt) // Value definition\n\nCase ::= Pattern '=>' Block(Stmt)\n| Pattern Block(GuardedCase)\n\nGuardedCase ::= '|' Sep1(Expr, ',') '=>' Block(Stmt)\n\nPattern ::= Expr\n
if
statements can be followed by zero or more elif
statements and an optional final else
statement. For example,
let x : int = 4\nswitch(f(x))\nNone => 0\nSome(y) =>\nif(y > 10)\n\"too big\"\nelif(y < 3)\n\"too small\"\nelse\n\"just right\"\n
"},{"location":"sophia_syntax/#expressions","title":"Expressions","text":"Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + 1\n| '(' BinOp ')' // Operator lambda (+)\n| 'if' '(' Expr ')' Expr 'else' Expr // If expression if(x < y) y else x\n| Expr ':' Type // Type annotation 5 : int\n| Expr BinOp Expr // Binary operator x + y\n| UnOp Expr // Unary operator ! b\n| Expr '(' Sep(Expr, ',') ')' // Application f(x, y)\n| Expr '.' Id // Projection state.x\n| Expr '[' Expr ']' // Map lookup map[key]\n| Expr '{' Sep(FieldUpdate, ',') '}' // Record or map update r{ fld[key].x = y }\n| '[' Sep(Expr, ',') ']' // List [1, 2, 3]\n| '[' Expr '|' Sep(Generator, ',') ']'\n// List comprehension [k | x <- [1], if (f(x)), let k = x+1]\n| '[' Expr '..' Expr ']' // List range [1..n]\n| '{' Sep(FieldUpdate, ',') '}' // Record or map value {x = 0, y = 1}, {[key] = val}\n| '(' Expr ')' // Parens (1 + 2) * 3\n| '(' Expr '=' Expr ')' // Assign pattern (y = x::_)\n| Id | Con | QId | QCon // Identifiers x, None, Map.member, AELib.Token\n| Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, \"foo\", '%'\n| AccountAddress | ContractAddress // Chain identifiers\n| OracleAddress | OracleQueryId // Chain identifiers\n| '???' // Hole expression 1 + ???\n\nGenerator ::= Pattern '<-' Expr // Generator\n| 'if' '(' Expr ')' // Guard\n| LetDef // Definition\n\nLamArgs ::= '(' Sep(LamArg, ',') ')'\nLamArg ::= Id [':' Type]\n\nFieldUpdate ::= Path '=' Expr\nPath ::= Id // Record field\n| '[' Expr ']' // Map key\n| Path '.' Id // Nested record field\n| Path '[' Expr ']' // Nested map key\n\nBinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!='\n| '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^'\n| '|>'\nUnOp ::= '-' | '!'\n
"},{"location":"sophia_syntax/#operators-types","title":"Operators types","text":"Operators Type -
+
*
/
mod
^
arithmetic operators !
&&
||
logical operators ==
!=
<
>
=<
>=
comparison operators ::
++
list operators |>
functional operators"},{"location":"sophia_syntax/#operator-precedence","title":"Operator precedence","text":"In order of highest to lowest precedence.
Operators Associativity !
right ^
left *
/
mod
left -
(unary) right +
-
left ::
++
right <
>
=<
>=
==
!=
none &&
right ||
right |>
left"}]}
\ No newline at end of file
diff --git a/master/sitemap.xml.gz b/master/sitemap.xml.gz
index c61e1db..0812581 100644
Binary files a/master/sitemap.xml.gz and b/master/sitemap.xml.gz differ