paint-brush
Radix Engine: A Better Model for “Enshrinement"by@RadixDLT
339 reads
339 reads

Radix Engine: A Better Model for “Enshrinement"

by Radix PublishingApril 10th, 2024
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

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.
featured image - Radix Engine: A Better Model for “Enshrinement"
Radix Publishing HackerNoon profile picture


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 that 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 rug pulls). 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 the benefits of protocol-enforced logic. Stealing one of his images, he frames the problem as a tradeoff between abstraction vs. enshrinement:



Vitalik’s abstraction vs. enshrinement tradeoff



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 the past 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.


EVM model



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. 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 to make the one-or-the-other decision Vitalik describes.



Enshrinement in the EVM model


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 that 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 that 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.


Operating System Model


\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.


Enshrinement in the Operating System Model



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, each with a specific responsibility and abstraction, allowing for system modularity and pluggability.


Radix Engine Layers



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 in 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 that allow 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, the VM layer is responsible for providing the computing environment for 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 that executes native packages that are compiled to the host’s environment.


If we take a look at the Scrypto WASM VM specifically, it looks like this:


Scrypto WASM VM Model



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 that 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 the system to implement functionality such as authorization, royalties, or type checking while being decoupled from both the application and kernel layers.


A System Call must pass through the filters of several system modules before being passed onto the kernel.


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 of any of these rules, the transaction will panic.
  • Manage transient vs. persistent objects. An object may be in the global space at any point in time 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”.


Decoupling of abstraction/enshrinement vs. software/hardware



Enshrined software has the flexibility and security properties of software while maintaining the ability to enforce system-wide logic.


Radix Engine’s Enshrined Software

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 that 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 look 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 that handles the logic of resource objects such as Buckets, Vaults, and Proofs

  • A system module that enforces the lifetime invariants of these objects (such as the movability and burnability of resources)


Radix Engine’s Enshrined 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 of 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 require four pieces of modular software:


  • Auth and Royalties native packages that 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 get called before an object method call to verify whether the caller has sufficient authorization to make the call and to collect royalties.



Radix Engine’s Enshrined Authorization and Royalties


Enshrined Transaction

The correct interface between the user and the system is paramount for any system to be usable. 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 that 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, the language interpreter is implemented by adding a Transaction Processor native package.


Radix Engine’s Enshrined Transaction


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. In the recent “Anemone” update, we enshrined BLS by executing it natively and found a 500x gain in performance when compared to WASM.


Because BLS logic is stateless, it can easily be added as an additional precompile to the Scrypto WASM VM.


Radix Engine’s Enshrined BLS

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 to manage 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.