Sui from basics to coding practice

Table of contents

Sui actual combat

trade

consensus engine

Advantages

Sui smart contract

Anso sui

Sui package layout and syntax

object

Functions

Capabilities

object classification

Objects and NFTs

Create NFT in Sui Move

object wrapper

dynamic fields

NFT

contract development

contract deployment

Test the Sui Move package

Publish Sui Move package

Call the Sui Move function


This series ranges from the basics of Sui to tutorials on building smart contracts and using objects in Sui Move.

Sui actual combat

This article covered the basics of the Sui infrastructure. Covers different types of transactions and how they improve scalability, components of a consensus engine, and key benefits of Sui.

trade

Sui divides transactions into two categories: simple and complex.

For simple transactions, such as sending tokens from one account to another or minting NFTs, transactions may bypass the consensus protocol. This makes Sui scalable and increases the throughput of the blockchain by allowing uncorrelated assets to reach finality almost immediately without going through a relatively longer and more expensive consensus process.

For complex transactions, such as liquidity pools, order books, or any of the myriad other DeFi use cases that use shared objects, transactions go through Sui's novel Narwhal and Bullshark based Directed Acyclic Graph (DAG) based mempool and efficient Byzantine Fault Tolerant ( BFT) consensus.

Due to Sui's object-centric view, and Move's strong ownership types, dependencies are explicitly encoded. As a result, Sui agrees and executes transactions on many objects in parallel.

consensus engine

DAG is essentially a directed graph that never loops. It consists of edges and vertices, and each edge points from one vertex to another such that a closed loop is never formed along these directions.

A directed acyclic graph (DAG) consists of linked edges and vertices arranged in such a way that they never form a closed loop.

Sui uses a DAG in the form of its mempool engine, Narwhal, which is separate from its consensus engine, Bullshark. By decoupling transaction propagation from consensus, Sui is able to achieve very high throughput.

Bullshark is a state-of-the-art consensus engine that, unlike its predecessor, allows for consistently high transactions per second regardless of the presence or absence of weaker validators in the ensemble.

Sui uses an object-oriented data model. Different objects on Sui include coin balances, NFT instances, and smart contracts. This data model allows smart contracts to express computations on objects. This also means that transactions are naturally organized into groups based on who they are for.

 

Advantages

Through features such as horizontal scalability, composability, sparse replay, and on-chain storage, Sui's architecture solves common pain points of first-generation blockchains.

horizontal zoom

On the Sui network, each set of transactions is processed in parallel, as opposed to the bottlenecks that arose in earlier blockchains due to the lack of distinction between various objects, resources, accounts, and other components.

Composability

In Sui, unlike most other blockchains, assets (such as NFTs) can be passed directly into the function parameters. Sui's object-centric approach also allows for more esoteric data structures, and the ability to store assets within such data structures or within the assets themselves.

sparse replay

Naturally, the blockchain provides a ledger of every transaction. For the Sui-specific example, game developers do not need to track transactions interacting with unrelated dApps. Because querying on-chain data can be expensive, products on Sui will be able to follow the evolution of this in-game object without mining the data from the Merkle tree .

On-chain storage

Since assets are stored directly as objects on the Sui blockchain, they are never subject to a Merkle tree index. Storing assets directly on-chain is used in conjunction with traditional means such as IPFS to solve the problem of on-chain storage, as it is much cheaper to update assets directly on-chain.

Note: This article has been updated from its original form to correct the sentence about the higher latency in the consensus model. While the original Narwhal paper made this discovery, the Sui network has been modified to provide low latency.

Sui  smart contract

Anso sui

The easiest way to install the Sui binaries and any necessary prerequisites is to visit the Install Sui to Build documentation. Here you'll find step-by-step instructions and links to all downloads. If you are just starting out, visiting this page will also help you familiarize yourself with the Sui documentation

Sui package layout and syntax

After downloading the binaries, you will be able to use the command sui move new [package name]to create a new Sui Move package in the current path directory. Running this command should create two projects in the root of your workspace: a Move.tomlfile and a package manifest file and subdirectories called . Compiling sourcesthe Sui Move code . Other optional subdirectories include: , , and .Move.tomlsourcesexamplesscriptstests

The primary unit of Move code organization (and distribution) is the package. A package consists of a set of modules defined in .moveseparate files with an extension. These files include Move functions and type definitions. A package must contain Move.tomla manifest file that describes the package configuration, such as package metadata or package dependencies.

object

In contrast to many other blockchains whose storage is account-centric and each account contains a key-value store, Sui's basic storage unit is an object. Objects are the building blocks of programming in Sui Move, and they are characterized by the following two properties:

  • All objects are has keyannotated with keywords after their structure declaration.
  • All objects have id: UIDthis as the first field.
struct Car has key {
	id: UID,
	speed: u8,
	acceleration: u8,
	handling: u8
}

                       This simple declaration in Sui Move names an object "Car". 

Owned by Address:  Once a Move object is created in Move code, it can be transferred to an address. After the transfer, the address owns the object. Objects owned by an address can only be used by transactions signed by that owner address (i.e. passed as arguments to Move calls). The owning object can be passed as a Move call parameter in any of three forms: read-only reference (  &T), mutable reference (  &mut T), and by value (  T).

Owned by another object: When an object is owned by another object, it is not wrapped. Child objects still exist independently as top-level objects and can be accessed directly in Sui storage. We'll cover this in more detail when we discuss dynamic fields in Section IV.

Immutable: You cannot change an immutable object, and an immutable object has no exclusive owner. Anyone can use immutable objects in Move calls. All Move packages are immutable objects: you cannot change a Move package after it has been published. You can use this operation to convert a Move object to an immutable object freeze_object. You can only pass immutable objects as read-only references (  &T) in Move calls.

Shared: An object can be shared, which means that anyone can read or write this object. In contrast to mutable owned objects (single writer), shared objects require consensus to order reads and writes. In other blockchains, every object is shared. However, Sui programmers often have the option of using shared objects, owned objects, or composition to achieve specific use cases. This choice can have performance, security, and implementation complexity implications.

Functions

Several visibility modifiers limit or restrict access to Sui Move functionality. publicfunctions allows other modules to import specific functions. public(friend)functions allows modules to gain explicit permission to import specific functions. Entry functions allow calling functions directly, for example within a transaction. A special function named initis executed only once, when its associated module is published. It always has the same signature and has only one parameter: ctx: &mut TxContext.

Capabilities

Abilities are a special mode in Sui Move that control access to certain features. Capabilities can be used in conjunction with function initto ensure that only one capability ever exists and send it to the module publisher.

struct AdminCapability has key { 
	id: UID 
}

fun init(ctx: &mut TxContext) {
	transfer::transfer(AdminCapability {
		id: object::new(ctx),
	}, tx_context::sender(ctx))
}

       An AdminCapability object is instantiated and passed to the module publisher's address. This ensures that only one AdminCapability will ever exist.

object classification

Address owned object

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-rust"><span style="color:#cc99cd">struct</span> <span style="color:#f8c555">ObjectA</span> has key <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#f8c555">UID</span> <span style="color:#cccccc">}</span>

public entry fun <span style="color:#f08d49">create_object_owned_by_an_address</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">:</span> <span style="color:#67cdcc">&</span><span style="color:#cc99cd">mut</span> <span style="color:#f8c555">TxContext</span><span style="color:#cccccc">)</span> <span style="color:#cccccc">{</span>
	<span style="color:#e2777a">transfer<span style="color:#cccccc">::</span></span><span style="color:#f08d49">transfer</span><span style="color:#cccccc">(</span><span style="color:#cccccc">{</span>
		<span style="color:#f8c555">ObjectA</span> <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#e2777a">object<span style="color:#cccccc">::</span></span><span style="color:#f08d49">new</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">)</span> <span style="color:#cccccc">}</span>
	<span style="color:#cccccc">}</span><span style="color:#cccccc">,</span> <span style="color:#e2777a">tx_context<span style="color:#cccccc">::</span></span><span style="color:#f08d49">sender</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">)</span><span style="color:#cccccc">)</span>
<span style="color:#cccccc">}</span></code></span></span>

object owned by the object

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-rust"><span style="color:#cc99cd">struct</span> <span style="color:#f8c555">ObjectB</span> has key<span style="color:#cccccc">,</span> store <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#f8c555">UID</span> <span style="color:#cccccc">}</span>

public entry fun <span style="color:#f08d49">create_object_owned_by_an_object</span><span style="color:#cccccc">(</span>parent<span style="color:#cccccc">:</span> <span style="color:#67cdcc">&</span><span style="color:#cc99cd">mut</span> <span style="color:#f8c555">ObjectA</span><span style="color:#cccccc">,</span> ctx<span style="color:#cccccc">:</span> <span style="color:#67cdcc">&</span><span style="color:#cc99cd">mut</span> <span style="color:#f8c555">TxContext</span><span style="color:#cccccc">)</span> <span style="color:#cccccc">{</span>
	<span style="color:#cc99cd">let</span> child <span style="color:#67cdcc">=</span> <span style="color:#f8c555">ObjectB</span> <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#e2777a">object<span style="color:#cccccc">::</span></span><span style="color:#f08d49">new</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">)</span> <span style="color:#cccccc">}</span><span style="color:#cccccc">;</span>
	<span style="color:#e2777a">ofield<span style="color:#cccccc">::</span></span><span style="color:#f08d49">add</span><span style="color:#cccccc">(</span><span style="color:#67cdcc">&</span><span style="color:#cc99cd">mut</span> parent<span style="color:#cccccc">.</span>id<span style="color:#cccccc">,</span> <span style="color:#7ec699">b"child"</span><span style="color:#cccccc">,</span> child<span style="color:#cccccc">)</span><span style="color:#cccccc">;</span>
<span style="color:#cccccc">}</span></code></span></span>

shared object

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-rust"><span style="color:#cc99cd">struct</span> <span style="color:#f8c555">ObjectC</span> has key <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#f8c555">UID</span> <span style="color:#cccccc">}</span>

public entry fun <span style="color:#f08d49">create_shared_object</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">:</span> <span style="color:#67cdcc">&</span><span style="color:#cc99cd">mut</span> <span style="color:#f8c555">TxContext</span><span style="color:#cccccc">)</span> <span style="color:#cccccc">{</span>
	<span style="color:#e2777a">transfer<span style="color:#cccccc">::</span></span><span style="color:#f08d49">share_object</span><span style="color:#cccccc">(</span><span style="color:#f8c555">ObjectC</span> <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#e2777a">object<span style="color:#cccccc">::</span></span><span style="color:#f08d49">new</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">)</span> <span style="color:#cccccc">}</span><span style="color:#cccccc">)</span>
<span style="color:#cccccc">}</span></code></span></span>

immutable object

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-rust"><span style="color:#cc99cd">struct</span> <span style="color:#f8c555">ObjectD</span> has key <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#f8c555">UID</span> <span style="color:#cccccc">}</span>

public entry fun <span style="color:#f08d49">create_immutable_object</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">:</span> <span style="color:#67cdcc">&</span><span style="color:#cc99cd">mut</span> <span style="color:#f8c555">TxContext</span><span style="color:#cccccc">)</span> <span style="color:#cccccc">{</span>
	<span style="color:#e2777a">transfer<span style="color:#cccccc">::</span></span><span style="color:#f08d49">freeze_object</span><span style="color:#cccccc">(</span><span style="color:#f8c555">ObjectD</span> <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#e2777a">object<span style="color:#cccccc">::</span></span><span style="color:#f08d49">new</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">)</span> <span style="color:#cccccc">}</span><span style="color:#cccccc">)</span>
<span style="color:#cccccc">}</span></code></span></span>

Objects and NFTs

Technically, there is no difference between objects on Sui and NFTs. Let's examine the Sui standard library's definition of a unique identifier (UID) in option.move using the following code snippet .

/// Globally unique IDs that define an object's ID in storage. Any Sui object, that is a struct
/// with the `key` ability, must have `id: UID` as its first field.
/// These are globally unique in the sense that no two values of type `UID` are ever equal, in
/// other words for any two values `id1: UID` and `id2: UID`, `id1` != `id2`.
/// This is a privileged type that can only be derived from a `TxContext`.
/// `UID` doesn't have the `drop` ability, so deleting a `UID` requires a call to `delete`.
struct UID has store {
	id: ID,
}

Because Sui generates a globally unique ID every time a new object is created, no two objects are truly interchangeable, even if their remaining fields are identical.

Create NFT in Sui Move

Due to Sui's object-centric approach, creating an NFT is relatively simple, as shown in the code snippet below.

struct NFT has key, store {
	id: UID,
	name: String,
	description: String,
	url: Url,
	// ... Additional attributes for various use cases (i.e. game, social profile, etc.)
}

A basic example is shown above. Sui's unique programming model allows for virtually unlimited use cases.

object wrapper

Objects can own other objects in Sui, but one object wrapping another object is a different concept

struct A has key {
	id: UID,
	b: B
}

struct B has key, store { id: UID }

In the above example, an object of type B is wrapped into an object of type A. With object wrapping, the wrapped object (object B in our example) is not stored as a top-level object. Therefore, it cannot be accessed by its object ID, so any API that interacts with that object (ie through the front end) will not be able to access object B directly.

This approach has some disadvantages:

  • Objects have a finite number of fields. These are the fields in the structure declaration.
  • Objects that wrap around other objects can get very large, resulting in higher gas bills. There is also an upper limit on the size of objects.
  • It is not possible to store collections of heterogeneous objects (objects of different types) through object wrappers. Because the Move vector type must be instantiated with a single type T, heterogeneous collections using vectors are not supported.

dynamic fields

Dynamic fields solve the above problems. Dynamic fields have arbitrary names and are added and removed temporarily. Regarding gas, you only pay for what you access, not what you can access. Dynamic fields also support heterogeneous types, allowing different types to be stored in an object.

There are two different types of dynamic fields: fields and object fields. Fields can store any value. These fields are not separate objects and cannot be accessed by ID from the external API. On the other hand, object fields must have key, store after their structure declaration. The latter has the advantage that the objects keep the same object ID and can still be read from external APIs.

NFT

Dynamic fields introduce a way to extend existing objects, and collections go a step further. Collections are built on top of dynamic fields, which additionally support counting the number of items they contain and prevent accidental non-null deletions.

Sets support both homogeneous and heterogeneous values. The isomorphic collection type is Table. All values ​​stored in a Table must be of the same type. The heterogeneous collection type is Bag. The value stored in Bag can be of any type. Like dynamic fields and dynamic object fields, table and bag also provide object-only versions: object_table and object_bag. All values ​​of table and bag must have only store, and all values ​​of object_table and object_bag must have key and store.

contract development

Due to Sui Move's object-centric programming model and its scalability, Sui is poised to be the first blockchain to truly deliver the web2 experience to web3. At the forefront of this experience is gaming. Game programming is inherently complex and requires a robust infrastructure to ensure a seamless experience for players. Thanks to the above two points, Sui was able to rise to the challenge.

Start writing the following

/// Our hero!
struct Hero has key, store {
	id: UID,
	/// Hit points. If they go to zero, the hero can't do anything
	hp: u64,
	/// Experience of the hero. Begins at zero
	experience: u64,
	/// The hero's minimal inventory
	sword: Option<Sword>,
	/// An ID of the game user is playing
	game_id: ID,
}

The code above defines our playable character. As can be seen from its domain, this hero is comparable to other characters in role-playing games. It has health (HP), experience and inventory.

/// The hero's trusty sword
struct Sword has key, store {
	id: UID,
	/// Constant set at creation. Acts as a multiplier on sword's strength.
	/// Swords with high magic are rarer (because they cost more).
	magic: u64,
	/// Sword grows in strength as we use it
	strength: u64,
	/// An ID of the game
	game_id: ID,
}

The code above shows our hero's sword. Note that this sword has key and storage capabilities. Looking back at the previous lessons in this series, a key means that it is an ownable asset that can exist in top-level storage. Moving objects in this category can also be accessed from external APIs, creating unique possibilities for Sui to use items in multiple games. And storage means that this object can be freely packaged and transferred.

/// A creature that the hero can slay to level up
struct Boar has key {
	id: UID,
	/// Hit points before the boar is slain
	hp: u64,
	/// Strength of this particular boar
	strength: u64,
	/// An ID of the game
	game_id: ID,
}

Above, we defined boars, non-playable characters (NPCs), or enemies in the game. Similar to other games of this genre, we can create NPCs for our heroes to fight and gain experience, or to buy items and accept quests.

action description

/// Slay the `boar` with the `hero`'s sword, get experience.
/// Aborts if the hero has 0 HP or is not strong enough to slay the boar
public entry fun slay(
	game: &GameInfo, hero: &mut Hero, boar: Boar, ctx: &TxContext
) {
	check_id(game, hero.game_id);
	check_id(game, boar.game_id);
	let Boar { id: boar_id, strength: boar_strength, hp, game_id: _ } = boar;
	let hero_strength = hero_strength(hero);
	let boar_hp = hp;
	let hero_hp = hero.hp;
	// attack the boar with the sword until its HP goes to zero
	while (boar_hp > hero_strength) {
		// first, the hero attacks
		boar_hp = boar_hp - hero_strength;
		// then, the boar gets a turn to attack. if the boar would kill
		// the hero, abort--we can't let the boar win!
		assert!(hero_hp >= boar_strength , EBOAR_WON);
		hero_hp = hero_hp - boar_strength;
	};
	// hero takes their licks
	hero.hp = hero_hp;
	// hero gains experience proportional to the boar, sword grows in
	// strength by one (if hero is using a sword)
	hero.experience = hero.experience + hp;
	if (option::is_some(&hero.sword)) {
		level_up_sword(option::borrow_mut(&mut hero.sword), 1)
	};
	// let the world know about the hero's triumph by emitting an event!
	event::emit(BoarSlainEvent {
		slayer_address: tx_context::sender(ctx),
		hero: object::uid_to_inner(&hero.id),
		boar: object::uid_to_inner(&boar_id),
		game_id: id(game)
	});
	object::delete(boar_id);
}

The actions shown in the code above describe the slay function. At a high level, this function first checks to make sure both Hero and Boar belong to the same game instance. Then a duel between the hero and the boar happens, and a check is made to make sure the hero's HP doesn't drop to zero. After the duel is completed, the hero gains experience points proportional to the boar, and the strength of the hero's sword is doubled (if the hero is wielding a sword). Finally, the function emits an event BoarSlayEvent. Events in Sui Move let indexers track on-chain operations, which is an important means of achieving universally agreed-upon object states.

The code sample above is a brief excerpt of Sam's hero.move code. This code provides a valuable example for game developers on Sui, and since it's open source, feel free to fork the repository and build your own games!

contract deployment

Test the Sui Move package

Once we've written the code, we want to test its functionality. Move has two types of test frameworks, a generic Move test framework and a Sui-specific test framework. Since Sui Move includes several features that don't exist in core Move, we'll focus on the latter.

Sui-specific tests can be found in the sui::test_scenario module. The test_scenario module provides an environment in which builders can test their code through a series of transactions. All updates to the Sui ledger occur through transactions, and Move calls in Sui are encapsulated in transactions. Builders can create simulated transactions to see the interaction between multiple different transactions (i.e. one transaction creates an object, another transfers the object, and another mutates the object).

Publish Sui Move package

Now that we've tested our code, we can actually publish it! The Sui Move function can only be called after the corresponding package is published to the Sui Network, where it is represented as an immutable object on the Sui Ledger. To publish a package, navigate to the package directory, then invoke sui client publish --gas-budget <gas_budget> (that is, <gas_budget> of 2000) from the command line interface (CLI). If successful, you will publish your package to the Sui network!

Call the Sui Move function

Calling our code on the Sui network allows modules to interact with each other to create motion in the game. In the examples in our video , we used Sui Explorer to invoke the code, which makes it easier to visualize the lessons. We can call the same function using Sui CLI. When creating your own dApp, you probably want to provide your users with a nice front end instead of making them use Sui Explorer or the CLI. Sui  JSON-RPC API allows you to connect smart contracts to the frontend.

Guess you like

Origin blog.csdn.net/FENGQIYUNRAN/article/details/130042889