In the previous articles, I covered the shortcomings of the current smart contract paradigm used by Ethereum and virtually every other DeFi platform – and then described how Radix is addressing this problem by rethinking how the platform offers smart contract functionality in an asset-oriented form with Radix Engine v2.
To make the advantages of the asset-oriented paradigm real and usable for developers, they need a programming language that exposes Radix Engine’s unique features while maintaining a familiar development experience with expressive logic.
Radix’s solution is Scrypto.
Scrypto is based on Rust – and keeps most of Rust’s features – but adds a range of specific functions and syntax for Radix Engine v2. It isn’t just Rust running on a public DLT network; it’s an asset-oriented language that allows Rust-style logic to interact not only with data (as with typical programming, and most smart contracts) but with assets as a native, first-class citizen.
Rather than jumping straight into what Scrypto code looks like, this article provides an overview of Scrypto’s asset-oriented features, and the lifecycle of a component (a Scrypto smart contract). You’ll see how Scrypto naturally allows the developer to focus on their own business logic and lean on Radix Engine for intuitive, safe handling of assets. The result is that building DeFi in Scrypto finally provides the ease, safety, reusability, and composability that DeFi needs to fulfill its world-changing potential.
Let’s start with the assets themselves, such as a token that you might want to create. In Scrypto, tokens aren’t smart contracts at all. Instead they are just a specific type of resource – the platform-native FSM-based “physical” assets described in my previous article on Radix Engine v2. To create a new resource, like a token, you use a built-in Scrypto function where you can specify the parameters you want.
Let’s say you want to create a fixed supply of 1,000,000 tokens called MyCoin. You pass this supply and name (and other parameters) to the resource creation function which then creates a resource definition and returns you 1,000,000 of your new MyCoins. (Note: A resource definition isn’t like an ERC-20 contract; it’s simply a way to refer to the parameters associated with that supply of resources, wherever they may be.)
Because Radix Engine requires that resources always be “physically” located somewhere, the MyCoins returned from the resource creation function must immediately be put in a temporary container called a bucket. A bucket isn’t a variable that holds a number; it behaves like an actual container that resources can be put into or taken from. Buckets vanish at the end of execution, however, (and resources must be stored somewhere at the end of execution) so you’ll also need a more permanent resource container called a vault that is always located within a component (more on this in a moment).
So we can picture creation of a resource like this, with newly-created tokens going into a bucket that is then immediately emptied into a vault for storage until a later transaction:
This can be done in just a couple of lines of Scrypto code.
Scrypto provides functions that allow you to do things like take any quantity of resources from a bucket or vault, and put them into other buckets or vaults. We’ll see later how this makes interacting with resources much more direct and safe than the typical smart contract method.
Now we have resources, but what about smart contract logic?
The Radix Engine version of a smart contract is something we call a “component”. Because components are designed around resources, they don’t just hold data (like ints or strings); they also hold vaults that contain all resources owned by the component. In fact, every vault is owned by a component (and a component may own multiple vaults).
So the component’s code defines the kind of data it holds and the kinds of vaults it holds (each vault only accepts a specific type of resource). It also defines a list of methods that contain all of the logic of the component and create the component’s interface to the world.
Putting it all together, we can picture an example component like this, ready to be used via its method1 and method2:
But wait, what is that “BlueprintA” we see next to the component’s title? That is the name of the blueprint the component was instantiated from. Unlike on typical smart contract platforms, active components aren’t simply deployed directly to the Radix network. All components start their life as a blueprint that is deployed to the Radix network and that acts like a template from which many component copies may be instantiated (each perhaps customized with input parameters).
This means that the Scrypto code (including its definition of the methods and types of data and vaults) is actually in the blueprint; the component’s logic and structure is fully defined there. But the component’s actual state - its data and resources - belong exclusively to that individual component, not the blueprint.
Once a component has been instantiated from a blueprint, it becomes active for use on the network by users (via method calls from transactions) or other components (via method calls from Scrypto code). Instantiation of a component from a blueprint happens using a function on the blueprint that performs the instantiation, like this:
(Note: You might notice one last little detail – the “PackageA” enclosing the blueprint. Multiple blueprints may be grouped together by the developer in a package.)
Blueprints encourage reusability of Scrypto code, and also give the developer great flexibility to perform a variety of setup and configuration actions. In fact, an instantiator function offered by a blueprint may do much more than just instantiate a single component. It may instantiate multiple different components, as well as create new resources. Here’s an example (with the blueprint and component details simplified) of one blueprint instantiating two components and creating two new resources:
In addition to blueprints created by developers, Radix intends to deploy its own set of useful blueprints for anyone to use and instantiate on-ledger.
Now that we can instantiate components, how do we interact with them programmatically? Similar to typical smart contracts, we use the methods offered by the component. But Scrypto methods have a significant difference: they can directly accept (buckets of) resources.
Passing resources to a component method isn’t just sending a number or a reference to some tokens. Radix Engine treats it as actually transferring the ownership of those tokens to the component. Once the component has received a bucket of resources (or multiple buckets), it can take resources out of that bucket and put them elsewhere like a vault it holds, or a different bucket. The Radix Engine guarantees that the caller can no longer access that bucket – it has transferred it away.
The result is a much simpler and safer way of using tokens and other kinds of assets with component-based dApps on Radix. Take the example of a gumball machine component that accepts some USD tokens in exchange for a Gumball token (with a supply held in the gumball machine’s vault):
A bucket of 0.25 USD is passed to the insertCoins method of the myMachine component (previously instantiated from a GumballMachine blueprint), and the machine’s logic sees that the correct price has been paid, puts those tokens in its USD vault, takes 1 Gumball from its Gumball vault, and passes it back to the caller. The component’s logic might also send back change if the user passed in too much USD.
Just how we’d expect a gumball machine to work!
On Ethereum, this would have involved the user calling a USD smart contract to give permission for the machine to withdraw on their behalf and telling the machine that they wish to input 0.25 USD, with the machine then calling the USD contract to do the withdraw, calling a Gumball contract to do the send to the user, and probably updating an internal cache of the number of Gumballs remaining for error checking. Every one of those extra smart contracts, and all of those smart contract calls, are opportunities for error – and this is just a simple gumball machine!
While you can imagine how one component might interact with another like this, at some point there must be a user transaction that kicks all of this off. How do transactions in the asset-oriented model work? What about accounts?
In short, transactions with Radix Engine v2 are also asset-oriented. They describe how the user wants resources that they control to flow to other components. They can even describe how to handle resources that are returned from a component – whether claimed by the user or passed on to another component in a composed multi-component transaction. (This also is a tremendous difference from Ethereum where a transaction is typically just a message to a smart contract that the user hopes will produce the desired result – and where composition of multiple smart contracts is not possible on the fly in transactions.)
But to make sense of this type of transaction, we need to understand how accounts work. An account in Radix Engine v2 is actually just a special kind of component that holds vaults just like any other component. Each account component is instantiated from a common Account blueprint on the Radix network that provides useful methods. So Alice’s account might look (in simplified form) something like this:
Let’s say you’re Alice and you want to send Bob 5 Cerb tokens. Your transaction would specify that you want to withdraw 5 Cerb from your Cerb vault (which you have permission to do via your signature) and then pass all of those tokens to the deposit method of Bob’s account component. As we discussed earlier, resources must always be located somewhere, so a bucket is used to pass them to Bob in the transaction. So the transaction contents would look something like this:
(Note: That’s not the actual syntax of transactions, but it gives you the right idea.)
Notice again that when we pass the 5 Cerb to Bob, we are actually passing a bucket containing those resources to the deposit method, not a reference or a call to a token smart contract elsewhere.
Interacting with any other component, such as our gumball machine above or a DeFi dApp component works in just the same way. You typically withdraw some tokens from your account, and pass them to the relevant method of the component you wish to call (perhaps along with some data that it may also require as input arguments).
Safely managing tokens and other assets is one of the recurring challenges with Ethereum and other typical smart contract platforms, but another is that of authorization. Virtually every smart contract has some methods that it wishes to protect. For example, authorization might be used on special methods reserved for the contract’s owner, the rights to mint and burn tokens, or to restrict access to a whitelist of members.
Today this is typically done by keeping track of a list of account addresses (or smart contract addresses) that are allowed to do certain things. Unfortunately handling authorization in this way is inflexible and frequently creates a new vector of attack if the right checks on the list aren’t performed correctly.
This problem is also solved elegantly by Radix Engine’s asset-oriented design. Previously we’ve only mentioned one type of resource, tokens, but there is another called badges. Badge resources have the same sort of “physical” behavior as token resources – with a difference. Scrypto provides special functions that easily allow components (including account components) to present a badge to another component.
Presenting a badge isn’t the same as transferring it; it provides the presentee with a reference to that badge so that it can be certain that the presenter in fact owns it without the badge changing ownership. This makes badges excellent for use in even very complex authorization patterns. Rather than checking a whitelist of addresses, a set of custom badges can be created and issued to accounts (or other components) which a component can easily check for. If the right badge isn’t present in the relevant method call, it can be rejected right away.
More work remains to be done on the precise implementation of badge-based authorization, but they will offer a powerful tool to make component logic yet more safe, predictable, and flexible.
Now, understanding the new tools provided by Scrypto (and Radix Engine behind the scenes), hopefully you can see the benefits of an asset-oriented approach. If you wish to write something like Uniswap in Scrypto, you can focus on writing only the code that matters: your unique swapping logic. Interacting with tokens (as resources), pools (vaults), and users (just another component) is direct and simple.
In fact, let's return to the comparison between “Uniswap as we imagine it” and “Uniswap on Ethereum today” from the top of the previous article. What would a swap transaction with the Scrypto implementation look like? Something like this:
Just as we imagined it to be! Using Scrypto, the only code that needs to be written is an implementation of the trade method that looks at the incoming bucket of Token A resources, calculates the current exchange rate (based directly on the contents of its own internally-held pool vaults), and returns the right amount of Token B. All of the token manipulation performed by the trade method implementation happens via simple take/put functions on buckets and vaults.
The transaction itself would look something like this:
No need for the user to give blanket withdrawal permission to Uniswap here; just a direct specification of the desired movement of resources between components. Safe, flexible, and naturally composable.
This sort of simplicity and directness should fire the imagination of developers with great ideas for the next generation of DeFi, and finally make possible a tidal wave of the kind of rich, robust dApps that are needed to revolutionize and remake global finance for the better.
Early access to Scrypto and Radix Engine v2 are coming this year as part of Radix Alexandria. Alexandria will let developers experiment with creating, compiling, and interacting with Scrypto-based blueprints and components in a simulator environment running on their local machine. Deployment of blueprints and components to the Radix Public Network will follow in Radix Babylon, coming in 2022.
Want to get connected to the Radix developer community and get first access to Scrypto? Sign up to our developer mailing list, or connect on Telegram or Discord.