Opinion Post by Josh Primero, Head Protocol Architect at RDX Works
In my previous article, I went over how Radix Engine avoided some of the flaws in Sui’s MoveVM. To summarize:
- Sui’s MoveVM is too low-level and generic, making for a cumbersome DeFi programming environment.
- Radix Engine is high-level and asset + business logic oriented, allowing developers to focus on DeFi logic rather than low level details.
Sui’s MoveVM follows the original Ethereum philosophy of a “clean, simple, beautiful” protocol which delegates everything to the application layer. This allows developers the freedom to explore any domain, including unknown ones at the time.
A protocol which is too simple and unstructured, though, creates significant compromises. For example, common system features like authorization would require a complex implementation in the application space, standard interfaces may become more fragmented, and performance optimization becomes more difficult.
Radix Engine, on the other hand, is designed to execute non-generic, system-wide logic as a core technical goal as it allows us to do the following:
- Enforce system-wide standards/implementations. An opinionated standard allows for development/maintainability/tooling to be easier across the stack as apis are not fragmented (e.g. ERC-20 vs ERC-721 vs ERC-404) and understanding behavior does not require bytecode interpretation (no more ERC-20 rugpulls). This gives developers and end users a safer, more consistent DeFi experience.
- Execute logic closer to hardware. As system logic doesn’t need to be run by an interpreter for correctness, execution of that logic can be run as close to hardware as possible.
- Parallelize execution. By having in-hand knowledge of the behavior of certain objects it’s easier to make static analysis decisions before execution allowing for parallel execution.
Vitalik recently touched on this idea with the term “enshrinement”, or the idea of selectively breaking away from abstraction in order to gain benefits of protocol-enforced logic. Stealing one of his images, he frames the problem as a tradeoff between abstraction vs. enshrinement:
This balance of supporting abstraction (i.e. future/diverse user needs) while maintaining performance/security/usability (i.e. enshrinement) is actually a very old computer science problem. The design of Operating Systems specifically has been trying to solve a similar problem for decades: How do we support a range of abstract applications while maintaining a fast, secure, usable system?
In this article I will go over how Radix Engine uses the Operating System model to create a framework capable of all types of “enshrinement” without the load to protocol complexity or loss of flexibility that Vitalik fears.
Let’s start by looking at the current industry standard, the Ethereum Virtual Machine (“EVM”).
EVM as a VM
The EVM is basic enough that it can be modeled as a virtual machine (“VM”) with the following opcodes:
- Turing-complete opcode set
- Opcodes for calls to other smart contracts
- Opcodes for reading/writing to persistent storage owned by the current smart contract
Smart contracts compiled into EVM bytecode can then be executed on top of such a VM.
In this model, any form of “enshrinement” requires changes to the EVM, or the VM hardware. For example, enshrining BLS signature support would require adding a new precompile. Or implementing EIP-2938 would require the addition of new opcodes. Expanding on what is enshrined inevitably results in a larger, more complicated VM and forces the protocol designer into the choose one-or-the-other decision Vitalik describes.
The general problem with this model is that the Abstraction/Enshrinement dichotomy is too coupled with the Software/Hardware dichotomy. That is, enshrining any logic into the protocol forces it to be embedded into the VM. There is no way to express “enshrined software” or software which is part of the system.
Operating Systems solved this dichotomy with the notion of “system software”.
Let’s take a closer look.
The Operating System Model
One of the main goals of an Operating System is to manage the software/hardware dichotomy – or more specifically the application/hardware dichotomy. The core part of any Operating System is the kernel, software which manages user space applications and their access to hardware. Kernel modules and drivers are additional pieces of system software that expand the set of supported hardware or extend kernel functionality.
From an “enshrinement” perspective, the kernel and its modules are enshrined parts of the system, but have the flexibility of software. Kernel modules, virtual machines (“VMs”), and user-space system processes are even more “soft” since these are abstracted from the kernel itself.
In this model, the layer of indirection between applications and hardware allows the software/hardware dichotomy to be decoupled from the abstraction/enshrinement dichotomy.
“System software” allows for enshrinement without overburdening the hardware.
Radix Engine as an Operating System
Using this Operating System model as inspiration, Radix Engine includes multiple layers where each layer has specific responsibilities and abstractions allowing for system modularity and pluggability.
Such a modular design allows for system logic to be enforced while also allowing the following:
- Inherit the isolation properties of the layer it’s in. For example, an enshrined package, though enshrined, cannot access arbitrary state but must follow application layer bounds. This is a similar type of security seen in user-space drivers or microkernel design. That is, risk is mitigated by isolating each part of the system so that any updates in the system (enshrinement) do not expose the entire system to risk.
- Access the features of the layer it’s in. For example, an enshrined package can inherit auth and/or upgradability features provided by the system.
- Decouple governance. This modular design allows innovation to each of these modules to occur in parallel and at different paces.
Let’s now go over each of these layers and see what their responsibilities are.
Application Layer
The application layer is responsible for defining high level logic. Bytecode that describes this logic, along with other static information, is bundled up in an executable format called a Package. Packages are then stored on-ledger and available for execution.
Applications written in Scrypto, the Rust-based language we’ve built for DeFi development, live in this layer. Scrypto programs get compiled into WASM programs with access to a set of function exports which allows the program to access system/kernel services. This System Call API is similar to the Linux system calls which provide access to shared I/O.
VM Layer
Moving to the next layer down, the VM Layer is responsible for providing the computing environment to the application layer. This includes a Turing-complete VM as well as the interface to access the system layer.
Radix Engine currently supports two VMs: a Scrypto WASM VM used to execute Scrypto applications and a native VM which executes native packages which are compiled to the host’s environment.
If we take a look at the Scrypto WASM VM specifically it looks like:
This may look essentially the same as the EVM model but there are two crucial differences:
- Removal of direct access to storage. Rather than each smart contract being able to access only its owned storage, any state read/write is done through system calls. This layer of indirection allows many interesting things to be implemented in the system such as state sharing across applications, state virtualization, etc. This layer of indirection is similar to the indirection provided by virtual memory or Linux’s file descriptors.
- Addition of system calls. System calls are the mechanism by which the application layer can access services of the System layer such as making invocations to other applications or writing data to an object. These system calls are similar to software interrupt instructions in real CPUs (e.g. INT instruction in x86).
System Layer
The System Layer is responsible for maintaining a set of System Modules, or pluggable software which can extend the functionality of the system. These are similar to Linux’s kernel modules.
On every system call, each system module gets called before the system layer passes control to the kernel layer. When called, each system module may update some particular state (e.g. update fees spent) or panic to end the transaction (e.g. if the type checker fails).
This pattern allows functionality such as authorization, royalties or type checking to be implemented by the system while being decoupled from both the application and kernel layers.
Kernel Layer
The kernel layer is responsible for the two core functionalities of Radix Engine: storage access and communication between applications. This is somewhat similar to the traditional Operating System’s responsibility for disk and network access.
For Radix Engine, this includes the following low-level management:
- Check that move/borrow semantics are maintained on any invocation or data write. The single owner rule and borrow rules are enforced by the kernel. On failure on any of these rules, the transaction will panic.
- Manage transient vs. persistent objects. An object at any point in time may be in the global space or may be owned by a call frame. The kernel maintains correct pointers to these objects during runtime as references to these objects are passed around.
- Manage transaction state updates. The kernel keeps track of the state updates which have occurred during a transaction and which will be subsequently committed to the database at the end of the transaction.
Enshrined Software
How do these layers relate to enshrinement in a DLT protocol? Similar to the kernel layer in Operating Systems, these middle layers of Radix Engine provide the indirection required to decouple the abstraction/enshrinement dichotomy from the software/hardware dichotomy and create the notion of “enshrined software”.
Enshrined software has the flexibility and security properties of software while maintaining the ability to enforce system-wide logic.
Let’s go over a few enshrinement examples that are currently running on the Radix network and see how they are implemented.
Enshrined Resources
The core differentiator of the Radix DeFi/Web3 platform is the idea that a resource (i.e. asset) is a fundamental primitive which should be understood separately from business logic. Enshrining this concept allows all dApps, wallets and tooling to have a common understanding of what an asset’s interface and behavior looks like making composability much easier.
Though resources are one of the most ingrained parts of the system, implementing its enshrinement only requires two modular pieces of software:
- A native package which handles the logic of resource objects such as Buckets, Vaults and Proofs
- A system module which enforces the lifetime invariants of these objects (such as the movability and burnability of resources)
The fact that Radix Engine could express the deep concept of a standardized, movable resource while being abstracted from the system/kernel shows the power of a modular system software framework.
Enshrined Authorization and Royalties
Radix Engine standardizes authorization and royalties by decoupling this logic from business logic and implementing these as system features. This allows users and developers to have a built-in common way of understanding the requirements to access any function on-ledger.
The modularity from decoupling business logic from system logic also allows for convenient development/debugging options like the ability to preview a transaction without the normal auth checks (want to simulate the result of sending 10 million USDC somewhere? With authorization disabled, your preview transaction can do the minting!).
Enshrining auth and royalties requires four pieces of modular software:
- Auth and Royalties native packages which allow the application layer to access the auth/royalties of any object (for example, to retrieve the auth rule to access a method or to claim royalties).
- Auth and Royalties system modules which get called before an object method call to verify whether the caller has sufficient authorization to make the call and to collect royalties.
Enshrined Transaction
The correct interface between user and system is paramount for any system to be usable. And to be usable the interface must find the right balance between ease-of-use and power/flexibility.
In the Operating System world, the most common interface is the terminal, a user space process which gives a user a command line tool to call and compose various system calls.
In the DLT world, this interface is the transaction. The industry standard for a transaction is to use a single low-level, generic invocation call. Unfortunately this is too simple in that it makes it hard to understand what one is actually doing when interacting with the system.
Radix Engine, on the other hand, uses the traditional OS pattern and enshrines an application language (similar to a terminal scripting language such as bash) to call and compose system calls in a single transaction.
Because the entry point of a transaction operates in the application layer, implementing the language interpreter is done by adding a Transaction Processor native package.
Enshrined BLS
BLS signatures are an important crypto primitive as they allow for the possibility of threshold signatures. Unfortunately, running such logic in WASM quickly uses up the maximum cost unit amount. In the recent “Anemone” update, Radix enshrined BLS by executing it natively and found a 500x gain in performance when compared to WASM.
Because BLS logic is stateless, it is easily added as an additional precompile to the Scrypto WASM VM.
Conclusion
What to enshrine and what not to enshrine is important for any DLT protocol. Unfortunately, the industry’s existing VM model makes every enshrinement decision a high stakes decision.
With the Operating System model as inspiration, Radix Engine solves this problem by adding a layer of indirection between “software” and “hardware”. This allows Radix Engine to express the notion of “enshrined software” and makes it easy for the protocol to add, modify and express new enshrined systems without making high stakes compromise decisions.
Originally the operating system was meant to be a small piece of software designed for the sole purpose of managing shared resources for multiple applications. As user demands for a better, faster, more secure platform grew, it has taken on more and more responsibility with a larger and larger suite of software.
DeFi will be no different. As demand for a faster, more secure, and more usable DeFi platform grows, increased enshrinement will follow. Radix Engine was designed with this in mind and provides the scalable and secure framework needed for expanding enshrinement into the future.
Articles in This Series
Article 1: Thoughts on Sui’s MoveVM
Article 2: How Radix Engine Avoids the Flaws of Sui's MoveVM
Article 3: Radix Engine: A Better Model for “Enshrinement”