Fae is designed according to ideas from functional programming: contracts are functions, transactions are function calls, and execution follows evaluation of function arguments. Execution therefore does not strictly follow transaction order, but occurs as-needed for each individual user, which yields the maximum logical degree of transaction paralellization (scalability of execution). Decisions concerning computational burden therefore can be, and are, made the responsibility of these users, eliminating the "gas" concept, accounting of machine instructions, and the integrated currency that would pay for it. In place of a native coin is a flexible scarcity mechanism ("escrows"), and all executable code is written in a high-level language (Haskell, in this implementation).
Many of the architectural concepts presented here are also discussed in the Reference Manual, but here we take a structural or design perspective, while the manual takes a practical software-development one. The lack of implementation details here should not be construed as their total absence (an implementation does, after all, exist), but rather, as a focus on universally applicable concepts in smart contracts. In addition, most of the detail in a document such as the Ethereum yellow paper is unnecessary here, both because of the lack of a simulated computer architecture, and because Fae is the analogue only of the Ethereum virtual machine, and does not specify its relationship with a consensus mechanism.
In short, this document is a declarative description of Fae, as opposed to a procedural one, appropriately similarly to a functional program being declarative rather than procedural.
The exposition that follows unavoidably makes forward references to concepts yet to be introduced when discussing others that rely on, or at least utilize them. Here are the words to watch for with some brief descriptions to suffice until they are properly defined:
- Contract — A stored "mathematical" function, meaning one whose effects and return value are completely determined by its argument.
- Transaction body — A one-time "mathematical" function receiving the return values of contract calls.
- Transaction — A set of contract calls together with a transaction body that receives their return values.
- Transaction message — The text file containing a transaction and its associated signatures.
- Signer — A cryptographic identity belonging to someone who signed a transaction.
- Contract code — Source code employing the Fae API that is interpreted into a contract or transaction body.
- Fae API — A set of functions (in Haskell, acting in a monad) constituting the functionality of contract code to receive or create effects in Fae's provided environment for contract and transaction execution.
- Global storage — A memory space contaning records of each transaction execution, in particular output contracts.
- Context — A notional scope belonging to a particular execution of a contract or transaction body, containing data specific to that execution. This includes signer identities and escrow storage.
- Output (contract) — A contract in global storage that may be called from a transaction, but never from contract code, returning a value to the transaction.
- Escrow — A contract stored in a particular context that is called by reference from contract code in that context, but cannot be called from a transaction.
- Input (contract) — A contract as called in a transaction; possibly, one of the explicit arguments to the transaction body.
- Material — An input contract provided in a "dictionary" (as in Python) rather than as an explicit function argument, either to the transaction body or, hierarchically, to another input contract.
The foundation of Fae's model is the structure of its contract storage, which was the primary factor influencing all other aspects of the design, and due to which it is possible to lazily execute transactions.
(transaction ID) -> (transaction results)
- return value of transaction body
- (index) -> (output contracts from the transaction body's execution)
- (index) -> (transaction input results) [transaction body arguments]
- (name) -> (transaction input results) [transaction materials]
(transaction input results)
- (index) -> (output contracts from the input contract's execution)
- (name) -> (transaction input results) [input call materials]
Entries in the storage are transaction results, each indexed by the corresponding transaction ID, which is just the hash of the transaction message. A transaction results object contains the actual transaction result, the body's return value, as well as the results of the various input contracts, organized hierarchically as in the transaction message (the return value of each input is passed to the contract or transaction above it).
From this storage structure, every output contract has a "path" to its point of origin, describing the particular transaction responsible, which piece of contract code (transaction body or particular input contract) produced that output, and in what order the outputs were produced.
A transaction specifies input contract calls and provides their results to the transaction body:
- Argument inputs are provided in a list to the body function.
- Materials inputs are assigned names in the transaction message and provided in a dictionary to the body's context.
The transaction body itself is a function of many arguments (or none), interpreted from contract code. It may return a value, which cannot be used within Fae, but may (and should) be interesting to a user who observes the transaction. Like all contract code, it may produce output contracts and call or transfer escrows.
Lazy evaluation of contract calls
The input contracts are the transaction's direct dependencies, and the input calls in previous transactions, as well as the contract code that actually created the contract, are the indirect (or transitive) dependencies. The direct dependencies are statically available in the transaction message and do not depend on the content of any contract code, and so the call stack for each contract can be constructed without executing any user-provided code. This includes the point of contract creation, which is identified specifically by the path-style contract ID. A user who chooses to evaluate a particular transaction result will selectively evaluate only the direct and transitive dependencies of that transaction.
Transitive dependencies rely on a specific linear transaction order, so Fae does assume that transactions are provided sequentially, even if they are executed non-sequentially. This is the only requirement it places on the consensus mechanism carrying the transactions.
Contract call stack
The diagram at right illustrates the effect of a new Fae transaction being introduced to the interpreter, and of being "queried" (its result examined), as compared to the much simpler process of adding a transaction to Ethereum or any other sequential-execution smart contract machine.
In the first two pictures, the green objects are the ones introduced by the new transaction: whereas in Ethereum it is just another opaque step in state evolution, in Fae we can pinpoint the specific effects on stored contracts right away. Each one builds a stack of successive calls by new transactions.
In the third picture, a query (
6?) entails walking back through the relevant call stacks, causing those contracts to be updated, and ultimately evaluating transaction bodies
2, which created the contracts
B that were called. Other transaction bodies, and other contracts called as inputs, are not affected.
Transactions also contain signer identities, proven by cryptographic signatures of the transaction message by a set of cryptographic identities (e.g. public keys) that are provided in the transaction context to contract code. These constitute "authorization" by their owners, which can be enforced in the code to limit access to certain contract actions. In this way, contract owners can obligate users who might (via a transferred escrow) impose a computational burden on them to seek their approval in advance outside of Fae.
When the transaction body ends, its context is dropped. This will cause the loss of any escrows still in it, possibly destroying value. Fae provides a "catch" mechanism in the form of fallback bodies, alternative contract functions with the same arguments that may perform cleanup on the valuables present in the arguments and materials that were provided as inputs. The fallbacks are run in the event of an exception in the transaction body, and do not protect against carelessness in handling escrows in a normally-exiting transaction body.
This allowance of exceptions in a transaction body reflects Fae's permissive attitude towards transaction validity. Any transaction message that successfully parses and interprets as Haskell is valid, whether or not it functions. Users can exercise discretion in evaluating these, so they are not a burden, except if present in excessive numbers, which Fae considers a problem to be solved by consensus (and, therefore, before Fae enters the picture).
Fae strictly adheres to the principle that the author of any contract code may not be forced to execute other contract code unknowingly. This means that:
- Contract arguments in the input calls are parsed from text (and the parser provided by the contract author), and not programmatically constructed.
- Input call return values are lazily evaluated when presented to contract code as materials or as transaction body arguments. That code's author may take precautions, such as requiring signer authorization, before forcing their evaluation.
- No user-provided code at all is executed when constructing the dependency graph from transaction messages.
A contract is a function as defined in the contract language (i.e. Haskell), taking one argument and returning one value, and empowered to use the Fae API to handle signers, materials, and escrows, and to create new escrows and output contracts. The contract function is referentially transparent: its return value depends only on the data provided explicitly as an argument or as materials (which function like named function arguments in Python), and on the contextual data of the transaction such as the signers, but not on global data that may be affected by other contract code. In particular, output (globally stored) contract calls cannot be made from within contract code, though escrow calls can be.
Contract storage, update, and deletion
An entry in contract storage (whether global or context) may be updated when the contract it contains is called. The contract ID therefore identifies not a single contract function, but a single contract location. The update occurs when a contract "releases" a value, interrupting its execution and storing the continuation as the updated contract. This continuation, which is a closure, includes all local data that is in scope, according to ordinary Haskell semantics, at the time of release; this is how contracts may maintain state. A contract may also "spend" its return value and be deleted from storage.
Vulnerability and its mitigation
Contracts have only one vulnerability to external attacks, given Fae's principle of no-forced-execution: escrows transferred with materials (or, in the case of contracts called as escrows, with the argument itself). Contract authors need to be aware that any escrow-bearing value is an attack vector and accordingly take precautions against blindly calling those escrows or causing them to be called. It is most likely sufficient to lock the call behind a check that the author (or other designated "owner") has provided their signature to the contract's context, which signifies that in "real life" they have had the opportunity to make a test run.
The result of the test run can be etched into the transaction in the form of a contract version, a content-based hash constructed from the final state of an input contract after its call is evaluated. A contract call by an ID including a version will execute normally if, and only if, the stored contract as observed by the user performing the execution has that exact version. Otherwise the call is an error. A contract owner who has made a test run as above can put the contract version they used into the transaction message to ensure that the "live" transaction receives the same inputs that they tested.
This mechanism does imply that contract owners will likely not accept payments directly from contracts that are updated frequently (say, by being called by many independent users). The payor would need to separately withdraw that value in advance in order to stabilize it.
Fae, at least as implemented in Haskell, allows transactions to attach source modules with the transaction body, which can be imported from other transactions, and so may provide functions and types intended to be used in other contract code, rather than requiring them to be implemented as special contracts.
An escrow is a contract stored in context storage: it is present "in" a specific contract or transaction run, and not in any other one, including other contracts called in the same transaction or other escrows called within the same context. Escrows are manipulated via their escrow ID, a reference value that prevents the escrow from being copied except through Fae's internal transfer mechanism. For this reason, escrows form the basis for scarce valuables in Fae.
When contract code releases or spends an escrow ID, the backing escrow is transferred from the returning context to the calling one. Escrows can also be transferred into the initial context of an output contract when it is created. This transfer extends to any values "containing" escrow IDs, as determined by their structure (say, as a field in a static data type or an entry in a dynamic container). Such values are called valuables or value-bearing, and their value is backed by the corresponding escrows; therefore, they are actually valuable only if the backing escrow is present in their context and was not transferred separately. The transfer semantics enforce the concept of "ownership of a valuable", and thus scarcity; the nature of the valuable is controlled by its underlying contract, which may be considered as a "contract to pay a held valuable", hence the name "escrow".
All contracts, but most importantly escrows, are created indirectly via a contract name, a value of a custom data type containing initialization data for the contract, and from which the contract function is constructed. Escrow IDs backing the contract name are transferred into the new contract's context, as mentioned earlier. Names are potentially serializable to text (binary) and so the return values of a transaction's inputs can be exchanged among Fae users, to be validated by the contract versions present in the transaction message. This has a similar effect as the Ethereum light client protocol in allowing users to elide past contract calls when catching up, though the contract that would have been called is never placed into the user's environment and so all its future calls would also have to be exchanged from elsewhere.