Move was born in 2018 during the early stages of the Libra project - two of Mysten's founders (Evan and I) were also on the founding team of Libra. Before we decided to create a new language, the early Libra team intensively studied existing smart contract use cases and languages to understand what developers wanted to do and where existing languages didn't provide. The key problem we discovered was that smart contracts are all about assets and access control, yet early smart contract languages lacked type/value representation for both. Our hypothesis was that if we provided first-class abstractions for these key concepts, we could greatly improve smart contract security and smart contract programmer productivity - having the right vocabulary for the task at hand changes everything. Over the years, many people have contributed to the design and implementation of Move as the language evolved from a key idea to a platform-agnostic smart contract language with the bold goal of becoming the "JavaScript for web3".
Today, we are pleased to announce a milestone in the integration of Move and Sui. Sui Move is feature complete, supported by advanced tools, and has extensive documentation and examples, including the following parts:
A series of tutorials on programming with Sui Move objects A developer document on Sui Move basics, design patterns, and samples A VSCode enhancement plugin developed by the Mysten Move team that supports code parsing and error diagnosis, integrating Move builds, tests, package management, documentation generation, and Move validators with the sui CLI
What makes Move unique
Move is a cross-platform, embeddable language. The core syntax itself is very simple: it has general concepts like structs, integers, and addresses, but it has no blockchain-specific concepts like accounts, transactions, time, cryptography, etc. These features must be provided by the blockchain platform that integrates Move. Importantly, these blockchains do not need their own forks of Move - each platform uses the same Move virtual machine, bytecode verifier, compiler, validator, package manager, and CLI, but adds blockchain-specific features through code built on top of these core components. Diem was the first blockchain to embed Move, and subsequent Move-based blockchains (including 0L, StarCoin, and Aptos) have mostly adopted a Diem-style approach. While the Diem-style Move has some great qualities, both the permissioned nature of Diem and certain implementation details of the Diem blockchain (particularly the storage model) make some basic smart contract use cases difficult to implement. In particular, the original designs of Move and Diem predate the explosion in popularity of NFTs and have some quirks that make the implementation of NFT-related use cases particularly tricky. In this post, we’ll walk through three such examples, show the problem with the original Diem-style Move embedding, and describe how we address this in Sui Move. We assume some basic familiarity with Move, but hopefully the key points will be understandable to anyone with a programming background.
Smooth experience when creating assets at scale
The ability to create and distribute assets in bulk is critical for both onboarding and attracting web3 users. Perhaps a Twitch streamer wants to distribute commemorative NFTs, a creator wants to send tickets for a special event, or a game developer wants to airdrop new items to all players. Here is a (failed) attempt to write code for mass minting assets in Diem-style Move. This code takes a vector of recipient addresses as input, generates an asset for each recipient, and attempts to transfer the asset.
In Diem-style Move, global storage is keyed by (address, type name) pairs — that is, each address can store at most one asset of a specific type. Thus, move_to(receiverient, CoolAsset { ...} attempts to transfer a CoolAsset by storing it at the recipient’s address. However, this code fails to compile at the move_to(receiverient, ...) line. The key problem is that in Diem-style Move, you cannot send a value of type CoolAsset to an address A unless: an address other than A sends a transaction that creates an account at A. The owner of A sends a transaction explicitly opting in to receiving objects of type CoolAsset. That’s two transactions, just to receive one asset! This decision makes sense for Diem, which is a permissioned system that needs to carefully restrict account creation and prevent accounts from holding too many assets due to the limitations of the storage system. But it’s very limiting for an open system that wants to use asset allocation as an onboarding mechanism, or just generally allow assets to flow freely between users, as they do on Ethereum and similar blockchains.
The code to implement the same function in Sui Move is as follows:
Sui Move's global storage is keyed by object IDs. Every struct with a key-value pair is a "Sui object", which must have a globally unique id field. Instead of using the restrictive move_to struct, Sui Move introduces a transfer primitive that can be used on any Sui object. Under the hood, this primitive maps an id to a CoolAsset in global storage, and adds metadata to indicate that the value is owned by the recipient. An interesting property of the Sui version of mass_mint is that it is exchanged with all other transactions (including other transactions that call mass_mint!). The Sui runtime will notice this and send the transaction that calls this function through a Byzantine consistent broadcast "fast path" that does not require consensus. Such a transaction can both be submitted and executed in parallel. This requires no effort from the programmer (they just write the code above and the runtime takes care of the rest.) Perhaps subtly, this is not the case with the Diem variant of this code - even if the above code works, the calls to exists and guid::create will both create arguments with other transactions that generate GUIDs or touch account resources. In some cases, it is possible to rewrite Diem-style Move code to avoid arguments, but many idiomatic ways of writing Diem-style Move introduce small hindrances that prevent parallel execution.
Local Asset Ownership and Transfers
Let's extend the Diem-style Move code with a workaround that actually compiles and runs. The idiomatic way to do this is with the "wrapper pattern": since Bob can't move a CoolAsset directly to Alice's address, we require Alice to "opt in" to receive a CoolAsset by first publishing a wrapper type CoolAssetStore with a collection type (a table) inside it. Alice can do this by calling the opt_in function. We then add code to allow Bob to move a CoolAsset from his CoolAssetStore to Alice's CoolAssetStore. To this code, let's add one other wrinkle: we will only allow transfers of CoolAssets if they are at least 30 days old since they were created. This kind of policy is important for creators who (for example) want to prevent speculators from buying/hyping tickets to their events, so that real fans can more easily get those tickets at a reasonable price.
This code is valid. But it is a rather complicated way to accomplish the task of transferring assets from Alice to Bob! Let’s look at another implementation of Sui Move.
This code is much shorter. The key thing to note here is that cool_transfer is an entry point function (meaning it can be called directly by the Sui runtime through a transaction), yet it has a parameter of type CoolAsset as input. This is again the magic of the Sui runtime. A transaction includes a set of object IDs it wants to operate on, and when Sui Runtime:
Resolve the ID to an object value (no need for the borrow_global_mut and table_remove parts of the Diem-style code above). Check if the object is owned by the sender of the transaction (eliminating the need for the signer::address_of part and related code above). This part is particularly interesting, and we will explain it shortly: in Sui, safe object ownership checks are part of the runtime. Check the type of the object value against the type of the argument to the called function cool_transfer. Bind the object value and other arguments to the arguments of cool_transfer and call the function.
This enables Sui Move programmers to skip the boilerplate of the “withdrawal” portion of the logic and jump right to the interesting part: checking the 30-day expiration policy. Similarly, the “deposit” portion is greatly simplified by the Sui Move transfer structure explained above. Finally, there is no need to introduce a wrapper type with internal collections like CoolAssetStore does - Sui global storage indexed by id allows an address to store any number of values of a given type. Another difference to point out is that while Diem-style cool_transfer has 5 ways to abort (i.e. fail and charge the user gas fees without completing the transfer), Sui Move cool_transfer has only one way to abort: when the 30-day policy is violated. Offloading object ownership checks to the runtime is a big win, not only in terms of ergonomics, but also in terms of security. A safe implementation at the runtime level prevents bugs that implement these checks on construction (or forget about them altogether!). Finally, notice that Sui Move’s entry point function signature cool_transfer(asset: CoolAsset, ...) gives us a lot of information about what this function is going to do (compared to Diem-style function signatures, which are much more opaque). We can think of this function as requesting permission to transfer CoolAsset, while the other function f(asset: &mut CoolAsset, ...) is requesting permission to write (but not transfer) CoolAsset, and g(asset: &CoolAsset, ...) is only requesting read permission.
Because this information is available directly in the function signature (no execution or static analysis required!), it can be used directly by wallets and other client tools. In Sui Wallet, we are working on human-readable signed requests, leveraging these structured function signatures to provide iOS/Android style permission prompts to users. The wallet can say something like: "This transaction requires permission to read your CoolAsset, write your AssetCollection, and transfer your ConcertTicket. Continue?".
Human-readable signature requests address a large attack vector present on many existing platforms (including those using Diem-style Move!), where wallet users must blindly sign transactions without understanding the impact they may have. We believe making the wallet experience less dangerous is a critical step in promoting mainstream adoption of cryptocurrency wallets, and designed Sui Move to support this goal by enabling features like human-readable signature requests.
Bundling different assets
Finally, let's consider an example about bundling different types of assets. This is a fairly common use case: programmers may want to bundle different types of NFTs into a collection, bundle items together to sell on a marketplace, or add accessories to existing items. Suppose we have the following situation:
Alice defines a character object that is used in the game. Alice wants to support decorating her character with different types of third-party accessories that are created later. Anyone should be able to create an accessory, but the owner of a character should decide whether to add an accessory. Transferring a character should automatically transfer all of its accessories.
This time, let's start with the code for Sui Move. We'll take advantage of another aspect of the object ownership functionality built into the Sui runtime: an object can be owned by another object. Every object has a unique owner, but a parent object can have any number of child objects. The parent/child object relationship is created by using the transfer_to_object function, which is the associated object of the transfer function introduced above.
In this code, the character module includes an accessorize function that lets the character's owner add an accessory object with any type as a child object. This allows Bob and Clarissa to create their own types of accessories, with different properties and functions that Alice does not have, but builds on what Alice has already done. For example, Bob's shirt can only be equipped if it is the character's favorite color, and Clarissa's sword can only be wielded if the character is powerful enough. Below is a practical demonstration of Diem-style Move, but none of them succeeded, that is, Diem-style Move cannot achieve the above scenario.
It can be seen that the problems in Diem-style Move are as follows
Only collections of the same type are supported (as shown in the first test), but accessories are not fundamentally a class. Associations between objects can only be created by "wrapping" (i.e. storing one object inside another); but the collection of objects that can be wrapped must be predefined (as in the second attempt) or added in an ad hoc manner, and additional object composition is not supported (as in the third test).
Summarize
Sui is the first platform to depart significantly from the original Diem design in how Move is used. Designing a platform that takes advantage of Move and the unique capabilities of the platform is both an art and a science, and requires a deep understanding of the Move language and the capabilities of the underlying blockchain. We are very excited about the progress made with Sui Move and the new use cases it will enable!".[1] Another argument in favor of a Diem-style Move policy is that "requires opt-in to receive a particular asset type" is a good mechanism to prevent spam. However, we believe that spam prevention belongs at the application layer. Rather than requiring users to send transactions that cost real money to opt-in to receive an asset, spam can be easily addressed at the wallet level (for example) with rich user-defined policies and automatic spam filters.


