# Workflows¶

A workflow—also known as smart contract—is a script whose inputs and outputs are data stored on a distributed database. A workflow provides the means to effect reads and writes to this database. Workflows can model a specific process associated with a real world financial instrument or contract, but are only a subset of the logic which involves counterparty communication and settlement. They are not an embodiment of legal logic or terms of a contract.

Put simply, workflows are a data reconciliation mechanism on a database that corresponds to a business process, such as the one below.

## Financial Core Language¶

Workflows are written in FCL; Uplink’s scripting language. This is a domain-specific language that is especially tailored towards modelling and analyzing contracts and assets. FCL’s design principle is safety first; as such FCL aims to be a very specialized language that gets exactly one job done: building distributed ledger workflows. FCL should only enable the user to do what they need to and no more.

### Turing Incompleteness¶

There are rumours that FCL actually stands for Finally, a Constrained Language. We shall neither confirm, nor deny these rumours, but we can confirm that FCL was specifically designed as a Turing incomplete programming language.

General purpose languages like Python and Java are Turing complete, giving the programmer the ability to write arbitrary programs which may never halt. Alan Turing, one of the fathers of computing, proved that in a Turing-complete language it is impossible to predetermine if an arbitrary program will ever finish running.

As such, for specifying financial and business transactions, Turing completeness is not a feature, it is a liability. FCL sidesteps these issues, meaning that every method call is guaranteed to terminate. In return, we gain exciting possiblities of static analysis, as well as security guarantees. For example we can statically ensure that a workflow doesn’t leak or indefinitely freeze funds, something which is a real and pressing issue with most blockchain scripting languages.

You may wonder whether FCL’s constrained expressiveness can mean that scripts could be more tedious to write than in a conventional programming language. The answer is in theory yes, but in practice these cases are rare and the benefits of better analyzability and ease of use far outweigh this. For more sophisticated applications, FCL can be generated from a Turing-complete language—this way the best of both worlds can be achieved, since the generated FCL will still always be checked by FCL’s compiler as part of deployment on the Uplink ledger.

### Workflow Net Representation¶

Every workflow in FCL is represented by a nondeterministic transition system called a workflow net. Workflow nets are based on Petri nets, a mathematical modelling language for distributed systems. They can be thought of as a concurrent flowchart where independent tasks can happen in parallel. This allows the workflow to be written in a way that closely fits the underlying logic of the business process that we model. Another benefit is that workflow nets are amenable to static analysis, allowing us to utilise automatic procedures to statically verify and query properties about smart contracts, improving our confidence that we only deploy well-behaved workflows.

The possible interactions with the ledger that a workflow provides are called methods. These are sequences of instructions that modify the ledger, e.g. by transferring assets between parties if certain conditions are met. A method can be executed when its input places are active. If the method call is successful, the input places will get deactivated, while the output places get activated. What this means can best be understand when looking at a concrete example. Below is an example workflow schematic for a currency swap between two parties.

The solid gray circle at the top corresponds to the workflow’s initial place, where every workflow begins. The outlined gray circle at the bottom is the workflow’s terminal place, where every workflow ends; once the workflow is in the terminal place, no more methods can be called on it. The other gray ellipses correspond to intermediate places; the workflow can be in one or more of these during its lifetime and the overall set of active places is said to be the workflow state. The white rectangles correspond to methods; that is, actions which can be performed depending on the current workflow state. For example, in the initial state, only the initial place is active and the two methods that are callable (by Alice) are propose and noDeal.

The workflow state can consist of more than one active place when there are concurrent tasks in the workflow. An example of a concurrent workflow is given below.

Concurrency happens when a single method has more than one output places, in this case requestValues. In the Petri net literature, this is often referred to as an AND split. We will then be in the workflow state {todoAlice, todoBob}, in which the methods setValueAlice and setValueBob can be called independently of each other, in any order. Before the workflow can terminate, an AND join needs to be performed; this happens when a method has more than one input places, as is the case for calculateTotal, which can only be called if the places doneAlice and doneBob are active.

### Workflow Soundness¶

When we compile an FCL script, the FCL workflow soundness checker will ensure that our workflow satisfies the following three criteria:

 Reachability Every place lies on a path between the initial place and the terminal place. Proper completion When the terminal place is reached, no other places are active. One boundedness No execution of the workflow will cause activation of a place that is already active.

The FCL compiler will tell us if our contract violates any of these criteria and point to the part of our script causes the soundness violation. This check ensures that scripts don’t become “stuck” and takes away a possible source of errors which a workflow deployer would otherwise have to worry about.

## Transition Declarations¶

Each FCL script declares its possible transitions. For the currency swap above, the FCL script will have the folowing transition declarations:

transition initial -> proposed;
transition proposed -> initial;
transition proposed -> terminal;


This is a sort of summary of the workflow for the benefit of people writing and reading the workflow script. Note that transitions refer to sets of methods, for example the currency swap has two implementations for the transition proposed -> initial: the methods reject and retract. In the Petri net literature this is referred to as an XOR split, since both methods are mutually exclusive. Again, eventually all mutually exclusive paths have to meet, at the latest in the terminal state; this is referred to as XOR join.

For concurrent tasks, as in the concurrent workflow at the end of section Workflow Net Representation, a transition will have a set of places as input or output. As such, the transition declarations for said workflow are as follows:

transition initial -> {todoAlice, todoBob};
transition todoAlice -> doneAlice;
transition todoBob -> doneBob;
transition {todoAlice, todoBob} -> terminal;


Here transitions to more than one place correspond to an AND-split and transitions from more than one place correspond to an AND-join. Note that transitions are always between sets of places, but we may omit the curly braces for singleton sets, i.e. transition foo -> bar is the same as transition {foo} -> {bar}. Shortly we will see that the same syntax, but prefixed with @ is used when referring to places in other parts of our FCL scripts.

Transitions can be inferred from a set of methods using the command uplink scripts transitions <FILE> (where <FILE> should be replaced with the path to the input file for which the transitions are to be inferred).

## Method Preconditions¶

Methods are annotated with preconditions which must hold for the method to be callable. Firstly, the set of input places is given, followed by an optional association list of dynamically checked preconditions (p) and expressions (e) of the form [p1: e1, p2: e2, ..., pn: en].

Precondition Type Description
after datetime The method is only callable after the given datetime.
before datetime The method is only callable before the given datetime.
roles set<account> The method is only callable by one of the accounts in the given set.

An example from the currency swap workflow:

@initial [roles: {alice}]
propose() {
// method body
transitionTo(@proposed)
}


Here we state that the method propose can be called by Alice when the initial place is active. Upon calling the method, its body will get executed and finally, via the transitionTo statement, the workflow will transition to the stipulated output places, resulting in a new workflow state where the method’s input places are removed and its output places are added. In this example, we would deactive the initial place and activate the place proposed.

The FCL compiler checks for every method that its input places and output places correspond to transitions given in the transition declarations. Vice versa, it is checked that every transition declaration has at least one method that can trigger that particular transition.

Another way to interpret this is that transition declarations provide a specification of the contract in terms of a workflow net. The compiler then ensures that the collection of methods form a valid implementation of this specification.

Dynamic preconditions like roles and after are checked at the time the transaction is to be issued. For example, bob cannot call the propose() method.

## Methods¶

FCL methods form the main body of an FCL workflow and directly follow global variable declarations and assignment. They begin with @ followed by a set of input places and optionally preconditions (as described above in the Method Preconditions section), the method name, a list of zero or more arguments, and a function body which may contain variable assignments and primitive operations (defined in the Primitive operations section).

### Calling a method¶

Calling a method is done by issuing a CallContract transaction on the ledger. Methods are publicly available interfaces that define the means by which workflow participants can interact with a workflow. A transaction that includes a successful call to a method may result in an altered contract state. Methods consist of sequences of primitive operations and Helper Functions.

Example:

/* a method */
@someState
transfer(account u, assetFrac2 a, fixed2 amount) {
if (accountExists u && holderBalance(sender()) >= amount) {
}
}

/* a helper function */
amount + amount * 0.05f;
}


## Types¶

FCL is a typed language which assigns a static type to all possible values in the language. This means that unlike e.g. a Javascript or Python script, a deployed FCL script can never encounter a runtime type mismatch.

### Kinds¶

In FCL there is a notion of “classes” or “kinds” of types: general, asset, holdings and collection a. Most types are of kind general, such that it is legal to use these types only where the exact type is specified. Types of other kinds may be used anywhere that their general kind is mentioned, as well as anywhere their explicit type is specified. For example, the type account is a general type and thus may only be used anywhere the type account is mentioned in a method, helper function, or prim-op type signature. In contrast, the type map<account,int> has kind collection a and can be used anywhere the kind collection a is specified, but also wherever the specific type map<account,int> is mentioned. Furethermore, the collection a kind is parameterized by another type; That is, the type kind collection a can be read as “a collection type with values of type a” (where a is any type). The type-kind hierarchy is depecticted below:

               general
_____________/ | \_____________
/               |               \
asset          holdings       collection a


i.e If a type is of kind asset, holdings, or collection a then it is also of kind general, but if a type is of kind general then it is not of any other kind.

Kinds of types can be thought of as “classes of types”, where all types are in the class of general types, but not all general types are in the subclasses asset, holdings, and collection. The kind of a type dictates which primops it may be used as an argument for and/or returned from; In other domains, this language feature is known as polymorphism. Furthermore, because of FCL’s sophisticated type inference, certain arguments of primops are fixed to be a certain type depending on the kind of type passed in as other arguments to the same primop. For example, the circulate primop takes two arguments, one of kind asset and the other of kind holdings; when the asset type assetDisc is applied as the first argument, the holdings type is fixed to be int, as you can only circulate an integer amount of an asset that is divided discretely.

FCL has a type system that represents exactly the types of values needed in the domain of designing complex multi-party workflows:

Type Kind Description
int holdings Type of 64 bit integers
float holdings Type of double precision floats
fixed1 holdings Type of fixed-point number with precision 1
fixed2 holdings Type of fixed-point number with precision 2
fixed3 holdings Type of fixed-point number with precision 3
fixed4 holdings Type of fixed-point number with precision 4
fixed5 holdings Type of fixed-point number with precision 5
fixed6 holdings Type of fixed-point number with precision 6
bool general Type of booleans
account general Type of account addresses
assetDisc asset Type of discrete asset addresses
assetBin asset Type of binary asset addresses
assetFrac1 asset Type of fractional asset addresses with precision 1
assetFrac2 asset Type of fractional asset addresses with precision 2
assetFrac3 asset Type of fractional asset addresses with precision 3
assetFrac4 asset Type of fractional asset addresses with precision 4
assetFrac5 asset Type of fractional asset addresses with precision 5
assetFrac6 asset Type of fractional asset addresses with precision 6
contract general Type of contract addresses
msg general Type of messages
sig general Type of ECDSA signature
datetime general Type of date and time values
timedelta general Type of periods of time
state general Type of graph state labels
map<a,b> collection Type of a key value store mapping keys of type a to values of type b
set<a> collection Type an unordered collection of unique values of type a
(a,b,...) -> c helper Type of helper function in FCL
any general The type of type; This type cannot be written by the user

## Primitive Operations¶

Primitive operations (primops) are methods defined in the FCL language specification, usable in any workflow’s method body. They provide functionality that allow workflow composers to perform useful operations that cannot be expressed using other language primitives.

Some of the primops included in FCL operate over only specific kinds of types (as described above); Furthermore, other primops operate over specific types and/or kinds of types with which other arguments supplied to the same primop are forced to be of a certain type based on the type of the most important argument.

*Note: For primops operating over kinds of types, we will use the ' prefix to denote argument and returns types ranging over a kind of type. For instance, if the transferHoldings primop takes a type of kind asset as an argument, we will denote it as such transferHoldings('asset, ...).

Function Arity Return Type Description
verify(account, msg, sig) 3 bool Verify a signature
sign(msg) 1 sig Sign a message
block() 0 int Active block
deployer() 0 account Get the deployer (or owner) of a contract
sender() 0 account Get the creator of current transaction
created() 0 datetime Time of contract creation
address() 0 address Address of contract
validator() 0 account Current validator of contract
sha256(any) 1 msg SHA256 digest of a message
accountExists(account) 1 bool Check if an account with the given address exists in world state
contractExists(contract) 1 bool Check if a contract with the given address exists in world state
terminate(msg) 1 void Transition to terminal state
now() 0 datetime Get block creation time of current transaction
transitionTo(state) 1 void Transition to named state
currentState() 0 state Get the current state
between(datetime, datetime, e) 3 bool Evaluate the expr e if now is between the arg1 and arg1
txHash() 0 msg Get hash of current transaction
contractValue(contract, msg) 2 <inferred> Get a value in the contract’s storage, return type inferred
contractValueExists(contract, varName) 2 bool Get a value’s existence in a contract’s global storage
contractState(contract) 1 state Get the state of a smart contract
isBusinessDayUK(datetime) 1 bool Check if datetime is a business day or not
nextBusinessDayUK(datetime) 1 datetime Get the next business day after the supplied datetime
isBusinessDayNYSE(datetime) 1 bool Checking if datetime is a business day or not
nextBusinessDayNYSE(datetime) 1 datetime Get the next business day after the supplied datetime
fixed1ToFloat(fixed1) 1 float Coerce a fixed point number into a floating point number
fixed2ToFloat(fixed2) 1 float Coerce a fixed point number into a floating point number
fixed3ToFloat(fixed3) 1 float Coerce a fixed point number into a floating point number
fixed4ToFloat(fixed4) 1 float Coerce a fixed point number into a floating point number
fixed5ToFloat(fixed5) 1 float Coerce a fixed point number into a floating point number
fixed6ToFloat(fixed6) 1 float Coerce a fixed point number into a floating point number
floatToFixed1(float) 1 fixed1 Coerce a floating point number into a fixed point number
floatToFixed2(float) 1 fixed2 Coerce a floating point number into a fixed point number
floatToFixed3(float) 1 fixed3 Coerce a floating point number into a fixed point number
floatToFixed4(float) 1 fixed4 Coerce a floating point number into a fixed point number
floatToFixed5(float) 1 fixed5 Coerce a floating point number into a fixed point number
floatToFixed6(float) 1 fixed6 Coerce a floating point number into a fixed point number

### Asset Operations¶

The subset of the primops operate over only the asset kind, known as “asset-primops”, have the type of one of their arguments or their return type decided by the type of asset that was passed as an argument. For example, a call to the transferHoldings asset-primop with the argument of type assetDisc, the type of the third argument (the amount of holdings to be transferred) must be int. This is because discrete asset holdings are referred to with integers, as integers are a numeric type that cannot be divided non-whole numbers. A more explicit example is denoted by this concise FCL contract:

transition initial -> terminal;

@initial
transfer(account from, assetFrac2 a, fixed2 amount, account to)
transferHoldings(from, a, amount  to);
terminate("transfer complete");
}


In this example, the transferHoldings primop takes an argument of type asset, such that the argument amount must have type fixed2, since the type of the holdings (illustrated below) of the assetFrac2 or fractional assets which are divisible up to two decimal places. In fact, FCL will report a type error if a user passes in an amount to transfer of a different type than fixed2 due to the languages type inference engine. A list of all the primops that operate over types of kind asset and kind holdings are found below:

Asset Function (AssetPrimOp) Arity Return Type Description
assetExists('asset) 1 bool Check if an asset with the given address exists in world state
holderBalance('asset, account) 2 'holdings Get balance of a holder of a of any type of asset
transferTo('asset, 'holdings) 2 void Transfer n asset holdings to contract
transferFrom('asset, 'holdings, account) 3 void Transfer n asset holdings from contract address to account
circulate('asset, 'holdings) 2 void Circulate n asset supply from the asset supply to the asset issuer
transferHoldings(account, 'asset, 'holdings, account) 4 void Transfer n asset holdings from an account to another account

The mapping of types of kind asset to the types of their holdings is provided below:

Asset Type FCL Type (kind asset) Asset Holdings Type (kind holdings)
Binary assetBin bool
Discrete assetDisc int
Fractional Prec1 assetFrac1 fixed1
Fractional Prec2 assetFrac2 fixed2
Fractional Prec3 assetFrac3 fixed3
Fractional Prec4 assetFrac4 fixed4
Fractional Prec5 assetFrac5 fixed5
Fractional Prec6 assetFrac6 fixed6

### Collections¶

Collections are a kind of type that represents a structure agnostic collection of values; This type kind is parameterized by another type, the type of values contained in the collection. The two kinds of collections currently supported by FCL are maps (general key/value stores), and sets (unordered list of unique values). The reason for this generalization of collections over discussing the specific map and set types is due to the nature of the structure of collections; There are many operations that operate over a collection of values and rather than define a specific function to operate over a specific collection, these operations can be generalized. In this case, instead of defining the transform primop for both maps and sets explicitly, FCL can define the primop once and generalize its arguments to take any collection as an argument. This allows the FCL primop list and code to be general and succinct, instead of having to defined a specific primop for both maps (mapTransform) and sets (setTransform), and for every type of kind collection added to FCL in the future.

#### Higher Order Functions¶

Along with collection primops comes the notion of higher-order functions, functions that can either take functions as arguments or return them; i.e. functions become values, like integers or booleans. In FCL, collection primops (and some primops that operate over specific types of collections) and helper functions adopt this property, where the former may sometimes take a helper function name as an argument denoting the function to apply to each member of the collection. This feature allows for FCL users to update or modify existing collection values in the workflow clearly and succinctly.

*Note: As the values of collection types can be of any type, we will specify these types with a type variable, an arbitrary variable name that must unify with all occurrences of the same type variable in the type signature given. For instance, if a primop type signature was (a,b) -> a then the first argument and the return type must be the same type.

#### Collection Operations¶

Collection Primop Arity Return Type Description
aggregate(a, (a,b) -> a, 'collection a) 3 a Combines all the values in the collection with the provided initial value and accumulator function.
transform(a -> b, 'collection b) 3 'collection b Modifies each value in the collection with the provided helper function
filter(a -> bool, 'collection a) 2 'collection a Returns the original collection without the values that do not satisfy the provided predicate
element(a, 'collection a) 2 bool Returns true if the value specified is an element of the provided collection
isEmpty('collection a) 1 bool Returns true if the collection is empty, and false otherwise

Example:

global int totalMap;

global map<account, int> balances =
( u'H1tbrEKWGpbPjSeG856kz2DjViCwMU3qTw3i1PqCLz65' : 1
, u'fwBVDsVh8SYQy98CzYpNPcbyTRczVUZ96HszhNRB8Ve'  : 2
, u'6pxGdGG6nQP3VoCW7HoGkCGDNCiCEWP3P5jHtrvgphBc' : 3
);

transition initial -> terminal;

@initial
sumBalances() {
totalMap = aggregate(0, sum, balances);
transitionTo(@terminal);
}

sum (int x, int y) { x + y; }


In this example, the aggregate higher order function is used, and the helper function sum is passed as an argument along with the initial accumulated value of 0 such that the sum of all the values in the map is computed.

#### Maps¶

Maps are key/value stores that map each unique key to a corresponding value.

#### Map Operations¶

The subset of primops that operate exclusively over values of type map also have the types of some of their arguments determined by the type of the map that is passed as an argument to the primop.

Map Primop Arity Return Type Description
mapInsert(a, b, map<a, b>) 3 map<a, b> Insert a value into the map at the provided key
mapDelete(a, map<a, b>) 2 map<a, b> Delete the value from the map at the provided key
lookup(a, map<a, b>) 2 b Lookup a value at the provided key
modify(a, b -> b, map<a,b>) 3 map<a, b> Modify the value at the provided key with the *helper function

Example:

global asset a;
global map<account, int> shares = ();
global map<account, int> invested = ();
global int totalRaised;

transition initial -> raise;
transition raise   -> terminal;

@initial
setAsset(asset a') {
if (sender() == deployer()) {
a = a';
transitionTo(@raise);
};
}

@raise
addInvestor (account a, int amount)  {
invested = mapInsert(a, amount, invested);
if (amount >= 100) {
shares = mapInsert(a, 100, shares);
} else {
shares = mapInsert(a, 10, shares);
};
}

@raise
removeInvestor (account a) {
shares = mapDelete(a, shares);
}

@raise
end () {
if (sender() == deployer()) {
totalRaised = aggregate(0, sum, invested);
terminate("");
};
}

sum(int x,int y) { x + y; }


#### Sets¶

Sets are unordered collections of unique values of the same type.

#### Set Operations¶

The subset of primops that operate exclusively over values of type set also have the types of some of their arguments determined by the type of the map that is passed as an argument to the primop.

Set Primop Arity Return Type Description
setInsert(a, set<a>) 2 set<a> Insert a value into the set
setDelete(a, set<a>) 2 set<a> Delete the value from the set
enum role
{ BigInvestor
, MedInvestor
, SmallInvestor
};

global map<enum role, set<account>> investors =
( BigInvestor : {}
, MedInvestor : {}
, SmallInvestor : {}
);

transition initial -> terminal;

@initial
insertInvestor(account a, enum role x) {
currSet = lookup(x, investors);
newSet =
if (!element(a, currSet)) {
setInsert(a, currSet);
} else {
setInsert(a, {});
};
investors = mapInsert(x, newSet, investors);
}

@initial
deleteInvestor(account a, enum role x) {
currSet = lookup(x, investors);
newSet =
if (element(a, currSet)) {
setDelete(a, currSet);
} else {
currSet;
};
investors = mapInsert(x, newSet, investors);
}

@initial
end() { terminate(""); }


## Effects¶

Expressions and statements in FCL may induce a side-effect: we may have methods that read from the ledger state, methods that may modify it, or not have any effect at all.

To see what kind of effects we keep track of in FCL, consider the following contract:

transition initial -> otherState;
transition otherState -> terminal;

@initial
noEffects () {
1 + 2;
void;
}

@initial
sender();
void;
}

@initial
writes () {
transitionTo(@otherState);
}

@initial
readsAndWrites (assetDisc asst, account acct) {
circulate(asst, 100);
}

@otherState
terminates () {
terminate("Bye");
}


Running uplink scripts compile on this contract, we get back the following list of method signatures:

Signatures:
noEffects: () -> () {}
writes: () -> () {write}
terminates: () -> any {write}


The first method, noEffects, contains some expressions, but it does not modify the ledger. The signature therefore shows an empty set of effects (denoted as {}).

We can use certain primitive operations (see Primitive operations) to read from the ledger state. In the method reads, we use sender(), which means that the method has effects {read}. An example of a primitive operation that writes to the ledger is transitionTo, as used in the method writes. Similarly, terminate also has as effect {write}, as can be seen in the method terminates. Operations that circulate or transfer assets modify the ledger state, hence have effect {write}.

The effects we consider in FCL, form the following so-called lattice:

The lattice tells us how the effects combine. For example, combining an expression with no effects with an expression with read effects yields an expression that has read effects. Combining an expression with read effects with one that has write effects, brings us to the bottom of the lattice: the resulting expression has a “read and write” effect.

One are where FCL checks the effects is in the definitions of global variables, see Global Variables for more information.

## Helper Functions¶

Helper functions in FCL are method-like function definitions that are the last construct to occur in the FCL program structure, following method declarations. FCL Helper functions were introduced to simplify method definitions; they allow users to separate redundant code (lines of code repeated in several places throughout the contract source code) from lines of code in the method body more integral to overarching method semantics.

There are several properties of helper functions that limit their semantics due to semantic constraints on FCL given because of the restricted domain that which FCL programs are designed for:

• Helper functions cannot refer to themselves in their function body due to the prohibition of recursion; If they could, self-recursion would be introduced.
• Helper functions can only call helper functions in their function body that have been defined previously in the FCL program; If they could refer to helper functions defined later in the script, helper functions would be able to “mutually recurse”.
• Helper functions are not annotated by contract states as methods are; Helper functions can be used in any method.

Example:

...

add100(int x) { x + 100; }

calcTotal(float rate, fixed2 gross) {
grossFloat = fixed2ToFloat(gross);
grossFloat + grossFloat * rate);
}


Another, equally important reason to add helper functions to FCL is the support of higher-order primitive operations, such that helper functions are higher-order function values. With regards to prim-ops, “higher-order” means that some primitive operations (built in functions) can take helper functions as arguments; Conversely, in the context of helper functions “higher-order” means that these functions can be passed as arguments to other functions (specifically prim-ops) as discussed in the collections section.

## Deltas¶

A delta is a “side-effect” induced by calling a method of a contract, that alters some aspect of the ledger world state. When a block is validated, all deltas are computed for all transactions and applied to the end-result world-state of the block.

There different types of deltas are:

Delta Description
ModifyGlobal Modify a contract state variable
ModifyLocal Modify a local state variable
ModifyAsset Modify an asset
Terminate Terminate the contract

## Global Variables¶

At the top of an FCL contract we can declare our global and local variables, which form part of the state of the contract. We can assign initial values to these variables upon declaration.

For example, if we want to keep track of how many times a certain method has been called by its deployer, we can define the following contract:

global int count = 0;

@initial
repeat() {
if (count > 4) {
} else {
count = count + 1;
transitionTo(@initial);
};
}

finish() {
terminate("Bye");
}


We can put any FCL expression on the right-hand side of a definition of a global variable, as long as the expression does not have any side-effects. For example, we can have the following global variable definitions:

global datetime startDate = "2018-03-02T09:30:00+00:00";
global datetime endDate = startDate + 10d;


Definitions that have side-effects are not allowed, for example:

global assetDisc asst = '39oS7aToiKTazHDL7hu5ktbBUETRzVFybwGbhwj2DifC';
global void result = transferTo(asst, 100);


The above will give us the following error message:

Expected no effects
Actual effects: {write}
location: Line 2, Column 22


We can also leave the global variable uninitialized when declaring it. A scenario where this is useful is when we do not know the initial values at the time of deployment. We may want a special user (for example the deployer of the script) to initialize these variables at some point after deploying the contract. For example, we can write:

global int deployerInt;

transition initial -> set;
transition set -> set;
transition set -> terminal;

@initial [roles: {deployer()}]
initialize(int val) {
deployerInt = val;
transitionTo(@set);
}

@set
update() {
deployerInt = deployerInt + 1;
transitionTo(@set);
}

@set
finish() {
terminate("Bye");
}


However, we may only refer to a global once it has been assigned a value. The FCL compiler checks for this. If we were to replace the method initialize in the above script with the following, the compiler will raise an error message:

@initial [roles: {deployer()}]
initialize(int val) {
if (val > 5) {
deployerInt = val;
};
transitionTo(@set);
}


The compiler will give us the following error message:

In method update:
Variable "deployerInt" undefined at:
- line 17:3
Stack trace leading up to error:
- @set:update to @set
- @initial:initialize to @set


If we were to call initialize with a value smaller than 5, the contract state would transition to the state :set. This then would allow us to invoke the method update(), which refers to the global variable deployerInt, but this has not been initialized yet, hence we cannot evaluate the method. The compiler therefore checks that if a method refers to a global variable, each path leading up to the method’s source state assigns a value to this variable.

Finally, we may also add preconditions to variables, in a similar way to Method Preconditions:

global int [roles: {deployer()}, after: "2020-10-10T00:00:00Z"] deployerInt;


This would mean that deployerInt may be written to only by the contract deployer and after the specified datetime.

NB: variable preconditions depend on method preconditions and the compiler must be able to statically verify that at every assignment to the variable in question the precondition will hold by looking at the method preconditions. Currently the check is not very elaborate and errs on the side of caution—it is sound but not complete, i.e. all preconditions will hold, but not all preconditions that hold will be accepted; it only works with literals and syntactically equal variables; e.g.:

global datetime d = "2001-01-01T00:00:00Z";
global int [after: d] n;

... // transition declarations ommitted

@initial [after: d + 1y]
m1() {
n = 0; // ok, but won't pass the check
transitionTo(@terminal)
}

@initial [after: d]
m2() {
n = 0; // ok
transitionTo(@terminal)
}


## Data Model¶

There are two main forms of data storage that exists in Uplink. The main storages are denoted on-ledger and off-ledger, in which data is either contained within a contract’s state or stored locally to a party’s node, respectively. The third is temporary storage, containing data that exists only for the duration of the execution of a contract method call.

• Off-ledger - Data stored off-ledger on counterparties’ local node (denoted by keyword “local” in top-level variable definitions in contracts).
• On-ledger - Data stored on-ledger within a contract’s state (denoted by keyword “global” in top-level variable definitions in contracts).
• On-ledger encrypted - Data stored on-ledger within a contract’s state and computed on using cryptographic protocols.

Global Stores An on-ledger storage that maps variables to their values. These values are stored unencrypted such that their values are visible to all nodes on the network running the contract.

Local Stores An off-ledger storage that maps variables to their values. The state of of these variables is stored on the counterparties local system and are kept in sync by exchanging hashes of their state after every contract interaction. The local state of a contract synchronizes all counterparties local stores without explicitly sharing their data. This is used for private data that is kept hidden from the rest of the network.

## Datetimes¶

In Uplink contracts, Dates and times are represented by Datetime values, syntactically designated by ISO8601 formatted strings. These values can be compared to other datetime values and manipulated by adding and subtracting timedeltas, allowing users to write sophisticated datetime logic in contracts like you may expect in any useful financial DSL.

Syntax

"YYYY-MM-DDThh:mm:ssTZD"
where
YYYY = four-digit year
MM   = two-digit month (01=January, etc.)
DD   = two-digit day of month (01 through 31)
hh   = two digits of hour (00 through 23) (am/pm NOT allowed)
mm   = two digits of minute (00 through 59)
ss   = two digits of second (00 through 59)
s    = one or more digits representing a decimal fraction of a second
TZD  = time zone designator (Z or +hh:mm or -hh:mm)


Example:

"1999-02-23T23:13:40+05:00"
"2015-10-10T00:00:00Z"


Operations:

Datetime == Datetime
Datetime < Datetime
Datetime <= Datetime
Datetime > Datetime
Datetime >= Datetime
Datetime + TimeDelta (*)
Datetime - TimeDelta (*)


Because Uplink contracts support adding and subtracting timedeltas using the time units of years and months, there are some non-intuitive properties of these operations that emerge; Notably, adding and then subtracting the same timedelta (or vice versa) from a datetime value does not always result in the initial datetime. This arises in situations where the intermediate datetime month has fewer days than the initial datetime. In this case, adding 1 month does not add a consistent number of days to the initial datetime, but instead increments the month number by one. In the case that the resulting day of the month is not a valid day of the month, the day of the month is reduced to the last valid day of the resulting month. This behavior is the same for subtracting delta values from datetime values.:

"2017-08-31T00:00:00Z" + 1mo == "2017-09-30T00:00:00Z"
"2017-10-31T00:00:00Z" - 1mo == "2017-09-30T00:00:00Z"


In the case of leap years, adding or subtracting timedeltas behave the same as in the example above, but the only instance in which this happens is when the initial and resulting years are both a leap year or both not a leap year, and the initial datetime is Feb 29th of the leap year.:

"2016-02-29T00:00:00Z" + 1y    == "2017-02-28T00:00:00Z"
"2015-01-31T00:00:00Z" + 1y1mo == "2016-02-29T00:00:00Z"
"2017-03-31T00:00:00Z" - 1y1mo == "2016-02-29T00:00:00Z"


## TimeDeltas¶

In Uplink contracts a timedelta represents an amount of time comprised of years, months, days, hours, minutes, and seconds. The values of each must be positive, and any number of each may be specified. Internal to Uplink, hours, minutes, and seconds are capped at 23, 59, and 59 respectively. that if a larger number of each is specified in the body of a timedelta literal, they will roll over into the next time quantity (e.g. 25h will be turned into 1d1h). Though the time units must be written in order, it is not necessary to include every time unit in the value representation of the timedelta. For instance, if adding only 1 month and 12 hours is desired, one can write 1mo12h instead of 0y1mo0d12h0m0s.

Syntax:

NyNmoNdNhNmNs
where
N = natural number


Examples:

1y6mo3d4h4m2s
(read as 1 year, 6 months, 3 days, 4 hours, 4 mins, and 2 seconds)

6mo12h
(read as 6 months and 12 hours)

1y100d59s
(read as 1 year, 100 days, and 59s)


Operations:

TimeDelta + TimeDelta
TimeDelta - TimeDelta (*)
Datetime + TimeDelta
Datetime - TimeDelta


[*] Note: If the result of one of the units of time in a timedelta is negative after subtraction, that particular unit of time is trimmed to 0. This results from the invariant that timedeltas cannot be negative, i.e. a change in time is intuitively positive, and this positive value can be added or subtracted from a datetime.:

1y6mo2d - 5mo3d == 1y1mo
3y20d50m - 21d35m == 3y15m


## Enumeration Types¶

Apart from using any of the pre-defined types, we can define our own enumeration types in a contract. For example:

enum Color { Red , Green , Blue , Orange };


Defining a global variable of an enumeration type can be done as follows:

global enum Color = Red;


Note that when referring to an element of an enumeration type outside the type its definition is preceded by a backtick ().

We can perform case analysis on a value of an enumeration type:

complement = case(color) {
Red    -> Green;
Green  -> Red;
Blue   -> Orange;
Orange -> Blue;
};


Such a case analysis must be exhaustive: there must be a case for every value of the enumeration type.

A contract its address is derived from the transaction that deployed the contract, as described in the documentation about Transactions. The addresses are represented as base58 encoded byte strings within Uplink.

$\text{address}_{\small \text{CONTRACT}} \ = \ \texttt{base58}(\texttt{sha3}(\texttt{base16}(\texttt{sha3}(\text{transaction}))))$

## Access Restriction¶

Some operations on a workflow should be reserved to a privileged group of accounts. This includes

• method calls

In FCL this can be introduced by including access restrictions. An access restriction consists of a set of expressions of type account and must not have any write effects. This includes account literals like u'CssRnWaxBhRRwhVL7ESgXkP7cgZ9vjFzV5nmr4jj3ZAh', variables of type account and the primops deployer(), sender() and validator().

The syntax for an access restriction is

$\texttt{\{} u_1 \texttt{,} \ u_2 \ ...\texttt{,} \ u_n \texttt{\}}$

where u ranges over expressions.

Note: It is not valid to syntactically repeat an expression, so {deployer(), admin, deployer()} will cause the script to be rejected with a duplication error. This is purely syntactic; it is fine if admin and deployer() happen to be one and the same account.

An example of a workflow that uses access restrictions:

global account admin = u'CssRnWaxBhRRwhVL7ESgXkP7cgZ9vjFzV5nmr4jj3ZAh';

transition initial -> intermediate;
transition intermediate -> terminal;

init() {
transitionTo(@intermediate);
}

}

@intermediate
finish() {
terminate("Goodbye.");
}


In the above example, either admin or the deployer may call init(). A call to this method by any other account will result in a failed transaction (meaning that the ledger state does not get altered).

A simple notion of roles can be achieved by assigning accounts to variables: In the intermediate state, admin may set a new value for admin. This is because of the the {admin} access restriction on the global variable at the top of the script. After a successful transaction, only the new admin may change the admin role.

## Example: Minimal¶

global int x = 0 ;

transition initial -> set;
transition set -> terminal;

@set
end () {
terminate("End contract.");
}

@initial
setX () {
x = 42;
transitionTo(@set);
}


## Example: Loan Product¶

global account issuer = 'CssRnWaxBhRRwhVL7ESgXkP7cgZ9vjFzV5nmr4jj3ZAh';
global account borrower = 'vVVq8PMEa9qGLL7LEmHUZKM78ze5JUgDPnjSE6FES63';
global assetFrac2 asset_ = 'HjdMu5LtF7BW6pGzvZGsV7ABUA4dkZFB2TmXQiymNpue';
global fixed2 interest= 0.05f;
global fixed2 principal;
global fixed2 payout;

global datetime maturityDate = "2018-03-08T10:12:00+00:00";

transition initial -> amountConfirmed;
transition amountConfirmed -> amountTransferred;
transition amountTransferred -> terminal;

@initial {issuer}
transitionTo(@amountConfirmed);
}

@amountConfirmed
loan() {issuer} {
transferHoldings(issuer, asset_, principal, borrower);
transitionTo(@amountTransferred);
}

@amountTransferred
settle() {issuer} {
after(maturityDate) {
payout = (principal + (principal * interest));
transferHoldings(issuer, asset_, payout, borrower);
transitionTo(@terminal);
};
};
}


## Example: Notary¶

global sig signature;

transition initial -> set;
transition set -> terminal;

@initial { deployer() }
put(msg x) {
signature = sign(x);
transitionTo(@set);
}

@set
end() {
terminate("This is the end.");
}


## Simulating Contract Execution¶

Within the Uplink ledger it may be beneficial (perhaps recommended) to simulate an FCL contract before deploying the contract to the production ledger– Uplink is targeted to facilitate the modeling of structured products and derivatives such that secure multi-party workflows can be realized on-chain, and these are often hard to get right the first time– Even comprehensive static analysis does not catch all logical bugs within the contracts. Contract simulation entails deploying a contract in a closed environment, distinctly separate from the simulation node’s copy of the ledger. The simulation uses a snapshot of either a previously saved ledger state or the current ledger state of the node on which the simulation is being created and run.

Since multiple contracts can be simulated at the same time, each simulated contract is assigned a simulation-id, calculated during the simulation creation process, and returned to the simulation creator (the “simulator”) such that the simulator can denote which simulated contract they wish to interact with. In future versions of uplink, simulated contracts will be able to reference each other such that simulations of groups of contracts that represent secure, complex, multi-party workflows may be tested off-chain before deploying to the ledger.

Contract simulation provides the following interface through which one can issue updates to the variables within the closed environment of a particular contract simulation, or query the current state of the contract of its encapsulating environment. All updates and queries are issued with a specific contract simulation identifier specifying the contract simulation to which the update/query should be issued.

### Simulation Creation¶

Create Simulation: Creates a simulation, returning a simulation identifier such that the creator of the simulation can reference when issuing updates or queries to a specific simulated contract.

Set Timestamp: Sets the current timestamp of the evaluation context; Users can set the time at which the contract is being evaluated to any time after the Unix epoch. If the contract logic has a strong temporal foundation (i.e. its logical progression relies heavily on the current time of method evaluation), then setting the current time of the contract is necessary for a useful simulation to be conducted. The timestamp should be in the form of an ISO_8601 string.

Add Timedelta: Adds the supplied timedelta to the simulation’s current timestamp. This is useful for incrementing the timestamp by a set duration rather than having to deal with explicitly ISO_8601 timestamps.

Call Method: Naturally, contract methods can be called on a simulated contract. Simulated contract method calls are conducted by supplying the name of the method and the arguments to be supplied to the method call. This action updates the encapsulated world state associated with the simulated contract.

### Simulation Query¶

Query Methods: Query the callable methods of the simulated contract. “Callable” methods refers to the notion that the methods that are available to be called on a contract is determined by the state the contract is in.

Query Contract: Query the contract that is being simulated.

Query Assets: Query the assets in the contract simulation environment.

Query Asset: Query a specific asset in the contract simulation environment.

It is important to note that contract simulations are held entirely in memory, and not persisted to disk; i.e. contract simulation data will be lost if the node that is performing the simulation stops running. This is contrary to uplink ledger data, which is persisted to disk, as a node is able to be stopped and started as determined by the user running the node, without fear that ledger data will be lost or corrupted. In future versions of uplink, contract simulations will be persisted to disk and a more complete interface will be supplied.

### Contract REPL¶

Built into the Uplink executable is the FCL REPL, a command line tool that allows Uplink users to simulate contracts utilizing most of the update and query interface outlined above, along with a few other bits of functionality. In the instance of using the REPL, a simulated method call returns the list of Deltas emitted during the evaluation of the function body. Currently, the REPL is not fully integrated with the contract simulation feature of uplink, so only one contract is able to be simulated at a time.

Starting the REPL with the simple contract loaded:

uplink repl examples/simple.s


An interactive session using the simple.s contract:

>>> :contract
State: "initial"
Global Storage:
x : int = 0

>>> :methods
Methods:
setX : int -> ()

>>> setX(42)
>>> :contract
State: "set"
Global Storage:
x : int = 84
>>> end()
>>> :contract
State: "terminal"
Global Storage:
x : int = 84


uplink repl -v examples/simple.s


Invoking the same commands as above now yields the following interaction:

>>> setX(10)
State: "set"
Global Storage:
x : int = 52

>>> end()
State: "terminal"
Global Storage:
x : int = 52

>>>


Of interest are the Methods and GlobalStorage contents fields. Signatures tells us which methods are callable in the current state and what arguments they take. GlobalStorage contents depicts a mapping of global variables to their current values stored on the ledger.

### Providing a Ledger State¶

A JSON dump of the ledger state obtained with:

uplink data export ledger -f JSON ledger_dump.json


can be passed as the Ledger state to simulate the contract in as an optional parameter to the repl command:

uplink repl examples/simple.s ledger_dump.json
`