From 1c1a8c35562f4b5330fae7727a65ae65527ca114 Mon Sep 17 00:00:00 2001 From: Damir S Date: Mon, 11 Jul 2022 20:46:37 +0300 Subject: [PATCH] [book] Sui Move by Example (#3126) - adds a book to the repo - sets a layout + structure - fills in main parts of the sui move --- doc/book/.gitignore | 2 + doc/book/README.md | 7 ++ doc/book/book.toml | 11 +++ doc/book/examples/.gitignore | 1 + doc/book/examples/Move.toml | 9 ++ doc/book/examples/README.md | 3 + doc/book/examples/sources/basics/bag.move | 6 ++ .../sources/basics/custom-transfer.move | 75 ++++++++++++++ .../sources/basics/entry-functions.move | 26 +++++ doc/book/examples/sources/basics/events.move | 93 +++++++++++++++++ .../sources/basics/init-function.move | 23 +++++ .../sources/basics/owned-objects.move | 3 + .../sources/basics/shared-object.move | 82 +++++++++++++++ doc/book/examples/sources/basics/strings.move | 28 ++++++ .../examples/sources/basics/transfer.move | 79 +++++++++++++++ doc/book/examples/sources/basics/vec-map.move | 3 + .../examples/sources/patterns/capability.move | 35 +++++++ .../examples/sources/patterns/hot-potato.move | 3 + .../patterns/transferable-witness.move | 36 +++++++ .../examples/sources/patterns/witness.move | 45 +++++++++ doc/book/examples/sources/samples/coin.move | 21 ++++ doc/book/examples/sources/samples/nft.move | 99 +++++++++++++++++++ doc/book/misc/move.js | 87 ++++++++++++++++ doc/book/src/README.md | 10 ++ doc/book/src/SUMMARY.md | 24 +++++ doc/book/src/basics/README.md | 3 + doc/book/src/basics/bag.md | 7 ++ doc/book/src/basics/custom-transfer.md | 9 ++ doc/book/src/basics/entry-functions.md | 8 ++ doc/book/src/basics/events.md | 7 ++ doc/book/src/basics/init-function.md | 13 +++ doc/book/src/basics/move-toml.md | 12 +++ doc/book/src/basics/owned-objects.md | 5 + doc/book/src/basics/shared-object.md | 7 ++ doc/book/src/basics/strings.md | 7 ++ doc/book/src/basics/transfer.md | 7 ++ doc/book/src/basics/vec-map.md | 5 + doc/book/src/patterns/README.md | 3 + doc/book/src/patterns/capability.md | 8 ++ doc/book/src/patterns/hot-potato.md | 5 + doc/book/src/patterns/transferable-witness.md | 5 + doc/book/src/patterns/witness.md | 7 ++ doc/book/src/samples/README.md | 3 + doc/book/src/samples/character.md | 1 + doc/book/src/samples/coin.md | 9 ++ doc/book/src/samples/nft.md | 7 ++ doc/book/theme/highlight.js | 10 ++ 47 files changed, 959 insertions(+) create mode 100644 doc/book/.gitignore create mode 100644 doc/book/README.md create mode 100644 doc/book/book.toml create mode 100644 doc/book/examples/.gitignore create mode 100644 doc/book/examples/Move.toml create mode 100644 doc/book/examples/README.md create mode 100644 doc/book/examples/sources/basics/bag.move create mode 100644 doc/book/examples/sources/basics/custom-transfer.move create mode 100644 doc/book/examples/sources/basics/entry-functions.move create mode 100644 doc/book/examples/sources/basics/events.move create mode 100644 doc/book/examples/sources/basics/init-function.move create mode 100644 doc/book/examples/sources/basics/owned-objects.move create mode 100644 doc/book/examples/sources/basics/shared-object.move create mode 100644 doc/book/examples/sources/basics/strings.move create mode 100644 doc/book/examples/sources/basics/transfer.move create mode 100644 doc/book/examples/sources/basics/vec-map.move create mode 100644 doc/book/examples/sources/patterns/capability.move create mode 100644 doc/book/examples/sources/patterns/hot-potato.move create mode 100644 doc/book/examples/sources/patterns/transferable-witness.move create mode 100644 doc/book/examples/sources/patterns/witness.move create mode 100644 doc/book/examples/sources/samples/coin.move create mode 100644 doc/book/examples/sources/samples/nft.move create mode 100644 doc/book/misc/move.js create mode 100644 doc/book/src/README.md create mode 100644 doc/book/src/SUMMARY.md create mode 100644 doc/book/src/basics/README.md create mode 100644 doc/book/src/basics/bag.md create mode 100644 doc/book/src/basics/custom-transfer.md create mode 100644 doc/book/src/basics/entry-functions.md create mode 100644 doc/book/src/basics/events.md create mode 100644 doc/book/src/basics/init-function.md create mode 100644 doc/book/src/basics/move-toml.md create mode 100644 doc/book/src/basics/owned-objects.md create mode 100644 doc/book/src/basics/shared-object.md create mode 100644 doc/book/src/basics/strings.md create mode 100644 doc/book/src/basics/transfer.md create mode 100644 doc/book/src/basics/vec-map.md create mode 100644 doc/book/src/patterns/README.md create mode 100644 doc/book/src/patterns/capability.md create mode 100644 doc/book/src/patterns/hot-potato.md create mode 100644 doc/book/src/patterns/transferable-witness.md create mode 100644 doc/book/src/patterns/witness.md create mode 100644 doc/book/src/samples/README.md create mode 100644 doc/book/src/samples/character.md create mode 100644 doc/book/src/samples/coin.md create mode 100644 doc/book/src/samples/nft.md create mode 100644 doc/book/theme/highlight.js diff --git a/doc/book/.gitignore b/doc/book/.gitignore new file mode 100644 index 0000000000000..bd3a7c8bd2b94 --- /dev/null +++ b/doc/book/.gitignore @@ -0,0 +1,2 @@ +book +.DS_Store diff --git a/doc/book/README.md b/doc/book/README.md new file mode 100644 index 0000000000000..d62610b98c390 --- /dev/null +++ b/doc/book/README.md @@ -0,0 +1,7 @@ +# Sui Move by Example + +This is the home for Sui Move by Example book. + +## Contributions + +Feel free to contribute to the book by submitting a pull request. diff --git a/doc/book/book.toml b/doc/book/book.toml new file mode 100644 index 0000000000000..78c99108e6867 --- /dev/null +++ b/doc/book/book.toml @@ -0,0 +1,11 @@ +[book] +title = "Sui Move by Example" +description = "Handy collection of Examples for Sui blockchain. Made with love by MystenLabs" +authors = ["damirka"] +language = "en" +multilingual = false +src = "src" + +[output.html] +git-repository-url = "https://github.com/MystenLabs/sui" +git-repository-icon = "fa-github" diff --git a/doc/book/examples/.gitignore b/doc/book/examples/.gitignore new file mode 100644 index 0000000000000..378eac25d3117 --- /dev/null +++ b/doc/book/examples/.gitignore @@ -0,0 +1 @@ +build diff --git a/doc/book/examples/Move.toml b/doc/book/examples/Move.toml new file mode 100644 index 0000000000000..101a287761c3e --- /dev/null +++ b/doc/book/examples/Move.toml @@ -0,0 +1,9 @@ +[package] +name = "sui-by-example" +version = "0.1.0" + +[dependencies] +Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "main" } + +[addresses] +examples = "0x0" diff --git a/doc/book/examples/README.md b/doc/book/examples/README.md new file mode 100644 index 0000000000000..10fb52713410f --- /dev/null +++ b/doc/book/examples/README.md @@ -0,0 +1,3 @@ +# Sui Move Examples + +This directory contains actual samples of the Move code used in the Sui Move by Example book. diff --git a/doc/book/examples/sources/basics/bag.move b/doc/book/examples/sources/basics/bag.move new file mode 100644 index 0000000000000..3ce2ebe5ecd3e --- /dev/null +++ b/doc/book/examples/sources/basics/bag.move @@ -0,0 +1,6 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +module examples::bag_example { + +} diff --git a/doc/book/examples/sources/basics/custom-transfer.move b/doc/book/examples/sources/basics/custom-transfer.move new file mode 100644 index 0000000000000..2423bb5e453bf --- /dev/null +++ b/doc/book/examples/sources/basics/custom-transfer.move @@ -0,0 +1,75 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +module examples::restricted_transfer { + use sui::tx_context::{Self, TxContext}; + use sui::balance::{Self, Balance}; + use sui::coin::{Self, Coin}; + use sui::id::VersionedID; + use sui::transfer; + use sui::sui::SUI; + + /// For when paid amount is not equal to the transfer price. + const EWrongAmount: u64 = 0; + + /// A Capability that allows bearer to create new `TitleDeed`s. + struct GovernmentCapability has key { id: VersionedID } + + /// An object that marks a property ownership. Can only be issued + /// by an authority. + struct TitleDeed has key { + id: VersionedID, + // ... some additional fields + } + + /// A centralized registry that approves property ownership + /// transfers and collects fees. + struct LandRegistry has key { + id: VersionedID, + balance: Balance, + fee: u64 + } + + /// Create a `LandRegistry` on module init. + fun init(ctx: &mut TxContext) { + transfer::transfer(GovernmentCapability { + id: tx_context::new_id(ctx) + }, tx_context::sender(ctx)); + + transfer::share_object(LandRegistry { + id: tx_context::new_id(ctx), + balance: balance::zero(), + fee: 10000 + }) + } + + /// Create `TitleDeed` and transfer it to the property owner. + /// Only owner of the `GovernmentCapability` can perform this action. + public entry fun issue_title_deed( + _: &GovernmentCapability, + for: address, + ctx: &mut TxContext + ) { + transfer::transfer(TitleDeed { + id: tx_context::new_id(ctx) + }, for) + } + + /// A custom transfer function. Required due to `TitleDeed` not having + /// a `store` ability. All transfers of `TitleDeed`s have to go through + /// this function and pay a fee to the `LandRegistry`. + public entry fun transfer_ownership( + registry: &mut LandRegistry, + paper: TitleDeed, + fee: Coin, + to: address, + ) { + assert!(coin::value(&fee) == registry.fee, EWrongAmount); + + // add a payment to the LandRegistry balance + balance::join(&mut registry.balance, coin::into_balance(fee)); + + // finally call the transfer function + transfer::transfer(paper, to) + } +} diff --git a/doc/book/examples/sources/basics/entry-functions.move b/doc/book/examples/sources/basics/entry-functions.move new file mode 100644 index 0000000000000..28e7b7ed68b9a --- /dev/null +++ b/doc/book/examples/sources/basics/entry-functions.move @@ -0,0 +1,26 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +module examples::object { + use sui::transfer; + use sui::id::VersionedID; + use sui::tx_context::{Self, TxContext}; + + struct Object has key { + id: VersionedID + } + + /// If function is defined as public - any module can call it. + /// Non-entry functions are also allowed to have return values. + public fun create(ctx: &mut TxContext): Object { + Object { id: tx_context::new_id(ctx) } + } + + /// Entrypoints can't have return values as they can only be called + /// directly in a transaction and the returned value can't be used. + /// However, `entry` without `public` disallows calling this method from + /// other Move modules. + entry fun create_and_transfer(to: address, ctx: &mut TxContext) { + transfer::transfer(create(ctx), to) + } +} diff --git a/doc/book/examples/sources/basics/events.move b/doc/book/examples/sources/basics/events.move new file mode 100644 index 0000000000000..6e62048d18975 --- /dev/null +++ b/doc/book/examples/sources/basics/events.move @@ -0,0 +1,93 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Extended example of a shared object. Now with addition of events! +module examples::donuts_with_events { + use sui::transfer; + use sui::sui::SUI; + use sui::coin::{Self, Coin}; + use sui::id::{Self, ID, VersionedID}; + use sui::balance::{Self, Balance}; + use sui::tx_context::{Self, TxContext}; + + // This is the only dependency you need for events. + use sui::event; + + /// For when Coin balance is too low. + const ENotEnough: u64 = 0; + + /// Capability that grants an owner the right to collect profits. + struct ShopOwnerCap has key { id: VersionedID } + + /// A purchasable Donut. For simplicity's sake we ignore implementation. + struct Donut has key { id: VersionedID } + + struct DonutShop has key { + id: VersionedID, + price: u64, + balance: Balance + } + + // ====== Events ====== + + /// For when someone has purchased a donut. + struct DonutBought has copy, drop { + id: ID + } + + /// For when DonutShop owner has collected profits. + struct ProfitsCollected has copy, drop { + amount: u64 + } + + // ====== Functions ====== + + fun init(ctx: &mut TxContext) { + transfer::transfer(ShopOwnerCap { + id: tx_context::new_id(ctx) + }, tx_context::sender(ctx)); + + transfer::share_object(DonutShop { + id: tx_context::new_id(ctx), + price: 1000, + balance: balance::zero() + }) + } + + /// Buy a donut. + public entry fun buy_donut( + shop: &mut DonutShop, payment: &mut Coin, ctx: &mut TxContext + ) { + assert!(coin::value(payment) >= shop.price, ENotEnough); + + let coin_balance = coin::balance_mut(payment); + let paid = balance::split(coin_balance, shop.price); + let id = tx_context::new_id(ctx); + + balance::join(&mut shop.balance, paid); + + // Emit the event using future object's ID. + event::emit(DonutBought { id: *id::inner(&id) }); + transfer::transfer(Donut { id }, tx_context::sender(ctx)) + } + + /// Consume donut and get nothing... + public entry fun eat_donut(d: Donut) { + let Donut { id } = d; + id::delete(id); + } + + /// Take coin from `DonutShop` and transfer it to tx sender. + /// Requires authorization with `ShopOwnerCap`. + public entry fun collect_profits( + _: &ShopOwnerCap, shop: &mut DonutShop, ctx: &mut TxContext + ) { + let amount = balance::value(&shop.balance); + let profits = coin::take(&mut shop.balance, amount, ctx); + + // simply create new type instance and emit it + event::emit(ProfitsCollected { amount }); + + transfer::transfer(profits, tx_context::sender(ctx)) + } +} diff --git a/doc/book/examples/sources/basics/init-function.move b/doc/book/examples/sources/basics/init-function.move new file mode 100644 index 0000000000000..4902c76d52bdc --- /dev/null +++ b/doc/book/examples/sources/basics/init-function.move @@ -0,0 +1,23 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +module examples::one_timer { + use sui::transfer; + use sui::id::VersionedID; + use sui::tx_context::{Self, TxContext}; + + /// The one of a kind - created in the module initializer. + struct CreatorCapability has key { + id: VersionedID + } + + /// This function is only called once on module publish. + /// Use it to make sure something has happened only once, like + /// here - only module author will own a version of a + /// `CreatorCapability` struct. + fun init(ctx: &mut TxContext) { + transfer::transfer(CreatorCapability { + id: tx_context::new_id(ctx), + }, tx_context::sender(ctx)) + } +} diff --git a/doc/book/examples/sources/basics/owned-objects.move b/doc/book/examples/sources/basics/owned-objects.move new file mode 100644 index 0000000000000..d34702a7d23cf --- /dev/null +++ b/doc/book/examples/sources/basics/owned-objects.move @@ -0,0 +1,3 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + diff --git a/doc/book/examples/sources/basics/shared-object.move b/doc/book/examples/sources/basics/shared-object.move new file mode 100644 index 0000000000000..a450e0a2b5e3c --- /dev/null +++ b/doc/book/examples/sources/basics/shared-object.move @@ -0,0 +1,82 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Unlike `Owned` objects, `Shared` ones can be accessed by anyone on the +/// network. Extended functionality and accessibility of this kind of objects +/// requires additional effort by securing access if needed. +module examples::donuts { + use sui::transfer; + use sui::sui::SUI; + use sui::coin::{Self, Coin}; + use sui::id::{Self, VersionedID}; + use sui::balance::{Self, Balance}; + use sui::tx_context::{Self, TxContext}; + + /// For when Coin balance is too low. + const ENotEnough: u64 = 0; + + /// Capability that grants an owner the right to collect profits. + struct ShopOwnerCap has key { id: VersionedID } + + /// A purchasable Donut. For simplicity's sake we ignore implementation. + struct Donut has key { id: VersionedID } + + /// A shared object. `key` ability is required. + struct DonutShop has key { + id: VersionedID, + price: u64, + balance: Balance + } + + /// Init function is often ideal place for initializing + /// a shared object as it is called only once. + /// + /// To share an object `transfer::share_object` is used. + fun init(ctx: &mut TxContext) { + transfer::transfer(ShopOwnerCap { + id: tx_context::new_id(ctx) + }, tx_context::sender(ctx)); + + // Share the object to make it accessible to everyone! + transfer::share_object(DonutShop { + id: tx_context::new_id(ctx), + price: 1000, + balance: balance::zero() + }) + } + + /// Entry function available to everyone who owns a Coin. + public entry fun buy_donut( + shop: &mut DonutShop, payment: &mut Coin, ctx: &mut TxContext + ) { + assert!(coin::value(payment) >= shop.price, ENotEnough); + + // Take amount = `shop.price` from Coin + let coin_balance = coin::balance_mut(payment); + let paid = balance::split(coin_balance, shop.price); + + // Put the coin to the Shop's balance + balance::join(&mut shop.balance, paid); + + transfer::transfer(Donut { + id: tx_context::new_id(ctx) + }, tx_context::sender(ctx)) + } + + /// Consume donut and get nothing... + public entry fun eat_donut(d: Donut) { + let Donut { id } = d; + id::delete(id); + } + + /// Take coin from `DonutShop` and transfer it to tx sender. + /// Requires authorization with `ShopOwnerCap`. + public entry fun collect_profits( + _: &ShopOwnerCap, shop: &mut DonutShop, ctx: &mut TxContext + ) { + let amount = balance::value(&shop.balance); + let profits = coin::take(&mut shop.balance, amount, ctx); + + transfer::transfer(profits, tx_context::sender(ctx)) + } +} diff --git a/doc/book/examples/sources/basics/strings.move b/doc/book/examples/sources/basics/strings.move new file mode 100644 index 0000000000000..70024b89da9bb --- /dev/null +++ b/doc/book/examples/sources/basics/strings.move @@ -0,0 +1,28 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +module examples::strings { + use sui::id::VersionedID; + use sui::tx_context::{Self, TxContext}; + + // Use this dependency to get a type wrapper for UTF8 Strings + use sui::utf8::{Self, String}; + + /// A dummy Object that holds a String type + struct Name has key, store { + id: VersionedID, + + /// Here it is - the String type + name: String + } + + /// Create a name Object by passing raw bytes + public fun issue_name_nft( + name_bytes: vector, ctx: &mut TxContext + ): Name { + Name { + id: tx_context::new_id(ctx), + name: utf8::string_unsafe(name_bytes) + } + } +} diff --git a/doc/book/examples/sources/basics/transfer.move b/doc/book/examples/sources/basics/transfer.move new file mode 100644 index 0000000000000..d0d69dc84e473 --- /dev/null +++ b/doc/book/examples/sources/basics/transfer.move @@ -0,0 +1,79 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// A freely transfererrable Wrapper for custom data. +module examples::wrapper { + use sui::id::{Self, VersionedID}; + use sui::tx_context::{Self, TxContext}; + + /// An object with `store` can be transferred in any + /// module without a custom transfer implementation. + struct Wrapper has key, store { + id: VersionedID, + contents: T + } + + /// View function to read contents of a `Container`. + public fun contents(c: &Wrapper): &T { + &c.contents + } + + /// Anyone can create a new object + public fun create( + contents: T, ctx: &mut TxContext + ): Wrapper { + Wrapper { + contents, + id: tx_context::new_id(ctx), + } + } + + /// Destroy `Wrapper` and get T. + public fun destroy (c: Wrapper): T { + let Wrapper { id, contents } = c; + id::delete(id); + contents + } +} + +module examples::profile { + use sui::transfer; + use sui::url::{Self, Url}; + use sui::utf8::{Self, String}; + use sui::tx_context::{Self, TxContext}; + + // using Wrapper functionality + use 0x0::wrapper; + + /// A profile information, not an object, can be wrapped + /// into a transferable container + struct ProfileInfo has store { + name: String, + url: Url + } + + /// Read `name` field from `ProfileInfo`. + public fun name(info: &ProfileInfo): &String { + &info.name + } + + /// Read `url` field from `ProfileInfo`. + public fun url(info: &ProfileInfo): &Url { + &info.url + } + + /// Creates new `ProfileInfo` and wraps into `Wrapper`. + /// Then transfers to sender. + public fun create_profile( + name: vector, url: vector, ctx: &mut TxContext + ) { + // create a new container and wrap ProfileInfo into it + let container = wrapper::create(ProfileInfo { + name: utf8::string_unsafe(name), + url: url::new_unsafe_from_bytes(url) + }, ctx); + + // `Wrapper` type is freely transferable + transfer::transfer(container, tx_context::sender(ctx)) + } +} diff --git a/doc/book/examples/sources/basics/vec-map.move b/doc/book/examples/sources/basics/vec-map.move new file mode 100644 index 0000000000000..d34702a7d23cf --- /dev/null +++ b/doc/book/examples/sources/basics/vec-map.move @@ -0,0 +1,3 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + diff --git a/doc/book/examples/sources/patterns/capability.move b/doc/book/examples/sources/patterns/capability.move new file mode 100644 index 0000000000000..366c7e74aea7b --- /dev/null +++ b/doc/book/examples/sources/patterns/capability.move @@ -0,0 +1,35 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +module examples::item { + use sui::transfer; + use sui::id::VersionedID; + use sui::utf8::{Self, String}; + use sui::tx_context::{Self, TxContext}; + + /// Type that marks Capability to create new `Item`s. + struct AdminCap has key { id: VersionedID } + + /// Custom NFT-like type. + struct Item has key, store { id: VersionedID, name: String } + + /// Module initializer is called once on module publish. + /// Here we create only one instance of `AdminCap` and send it to the publisher. + fun item(ctx: &mut TxContext) { + transfer::transfer(AdminCap { + id: tx_context::new_id(ctx) + }, tx_context::sender(ctx)) + } + + /// The entry function can not be called if `AdminCap` is not passed as + /// the first argument. Hence only owner of the `AdminCap` can perform + /// this action. + public entry fun create_and_send( + _: &AdminCap, name: vector, to: address, ctx: &mut TxContext + ) { + transfer::transfer(Item { + id: tx_context::new_id(ctx), + name: utf8::string_unsafe(name) + }, to) + } +} diff --git a/doc/book/examples/sources/patterns/hot-potato.move b/doc/book/examples/sources/patterns/hot-potato.move new file mode 100644 index 0000000000000..d34702a7d23cf --- /dev/null +++ b/doc/book/examples/sources/patterns/hot-potato.move @@ -0,0 +1,3 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + diff --git a/doc/book/examples/sources/patterns/transferable-witness.move b/doc/book/examples/sources/patterns/transferable-witness.move new file mode 100644 index 0000000000000..0aa1133083ede --- /dev/null +++ b/doc/book/examples/sources/patterns/transferable-witness.move @@ -0,0 +1,36 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// This pattern is based on combination of two others: Capability and a Witness. +/// Since Witness is something to be careful with, spawning it should be only +/// allowed to authorized users (ideally only once). But some scenarios require +/// type authorization by module X to be used in another module Y. Or, possibly, +/// there's a case where authorization should be performed after some time. +/// +/// For these, rather rare, scerarios a storable witness is a perfect solution. +module examples::transferable_witness { + use sui::transfer; + use sui::id::{Self, VersionedID}; + use sui::tx_context::{Self, TxContext}; + + /// Witness now has a `store` which allows us to store it inside a wrapper. + struct WITNESS has store, drop {} + + /// Carries the witness type. Can only be used once to get a Witness. + struct WitnessCarrier has key { id: VersionedID, witness: WITNESS } + + /// Send a `WitnessCarrier` to the module publisher. + fun init(ctx: &mut TxContext) { + transfer::transfer( + WitnessCarrier { id: tx_context::new_id(ctx), witness: WITNESS {} }, + tx_context::sender(ctx) + ) + } + + /// Unwrap a carrier and get the inner WITNESS type. + public fun get_witness(carrier: WitnessCarrier): WITNESS { + let WitnessCarrier { id, witness } = carrier; + id::delete(id); + witness + } +} diff --git a/doc/book/examples/sources/patterns/witness.move b/doc/book/examples/sources/patterns/witness.move new file mode 100644 index 0000000000000..e77b1afcf8af1 --- /dev/null +++ b/doc/book/examples/sources/patterns/witness.move @@ -0,0 +1,45 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Module that defines a generic type `Guardian` which can only be +/// instantiated with a witness. +module examples::guardian { + use sui::id::VersionedID; + use sui::tx_context::{Self, TxContext}; + + /// Phantom parameter T can only be initialized in the `create_guardian` + /// function. But the types passed here must have `drop`. + struct Guardian has key, store { + id: VersionedID + } + + /// The first argument of this function is an actual instance of the + /// type T with `drop` ability. It is dropped as soon as received. + public fun create_guardian( + _witness: T, ctx: &mut TxContext + ): Guardian { + Guardian { id: tx_context::new_id(ctx) } + } +} + +/// Custom module that makes use of the `guardian`. +module examples::peace { + use sui::transfer; + use sui::tx_context::{Self, TxContext}; + + // Use the `guardian` as a dependency. + use 0x0::guardian; + + /// This type is intended to be used only once. + struct PEACE has drop {} + + /// Module initializer is the best way to ensure that the + /// code is called only once. With `Witness` pattern it is + /// often the best practice. + fun init(ctx: &mut TxContext) { + transfer::transfer( + guardian::create_guardian(PEACE {}, ctx), + tx_context::sender(ctx) + ) + } +} diff --git a/doc/book/examples/sources/samples/coin.move b/doc/book/examples/sources/samples/coin.move new file mode 100644 index 0000000000000..655bb3847b15b --- /dev/null +++ b/doc/book/examples/sources/samples/coin.move @@ -0,0 +1,21 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +module examples::my_coin { + use sui::coin; + use sui::transfer; + use sui::tx_context::{Self, TxContext}; + + /// The type identifier of coin. The coin will have a type + /// tag of kind: `Coin` + struct MYCOIN has drop {} + + /// Module initializer is called once on module publish. A treasury + /// cap is sent to the publisher, who then controls minting and burning + fun init(ctx: &mut TxContext) { + transfer::transfer( + coin::create_currency(MYCOIN {}, ctx), + tx_context::sender(ctx) + ) + } +} diff --git a/doc/book/examples/sources/samples/nft.move b/doc/book/examples/sources/samples/nft.move new file mode 100644 index 0000000000000..4b1a17f608aec --- /dev/null +++ b/doc/book/examples/sources/samples/nft.move @@ -0,0 +1,99 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +module examples::devnet_nft { + use sui::url::{Self, Url}; + use sui::utf8; + use sui::id::{Self, ID, VersionedID}; + use sui::event; + use sui::transfer; + use sui::tx_context::{Self, TxContext}; + + /// An example NFT that can be minted by anybody + struct DevNetNFT has key, store { + id: VersionedID, + /// Name for the token + name: utf8::String, + /// Description of the token + description: utf8::String, + /// URL for the token + url: Url, + // TODO: allow custom attributes + } + + // ===== Events ===== + + struct NFTMinted has copy, drop { + // The Object ID of the NFT + object_id: ID, + // The creator of the NFT + creator: address, + // The name of the NFT + name: utf8::String, + } + + // ===== Public view functions ===== + + /// Get the NFT's `name` + public fun name(nft: &DevNetNFT): &utf8::String { + &nft.name + } + + /// Get the NFT's `description` + public fun description(nft: &DevNetNFT): &utf8::String { + &nft.description + } + + /// Get the NFT's `url` + public fun url(nft: &DevNetNFT): &Url { + &nft.url + } + + // ===== Entrypoints ===== + + /// Create a new devnet_nft + public entry fun mint_to_sender( + name: vector, + description: vector, + url: vector, + ctx: &mut TxContext + ) { + let sender = tx_context::sender(ctx); + let nft = DevNetNFT { + id: tx_context::new_id(ctx), + name: utf8::string_unsafe(name), + description: utf8::string_unsafe(description), + url: url::new_unsafe_from_bytes(url) + }; + + event::emit(NFTMinted { + object_id: *id::inner(&nft.id), + creator: sender, + name: nft.name, + }); + + transfer::transfer(nft, sender); + } + + /// Transfer `nft` to `recipient` + public entry fun transfer( + nft: DevNetNFT, recipient: address, _: &mut TxContext + ) { + transfer::transfer(nft, recipient) + } + + /// Update the `description` of `nft` to `new_description` + public entry fun update_description( + nft: &mut DevNetNFT, + new_description: vector, + _: &mut TxContext + ) { + nft.description = utf8::string_unsafe(new_description) + } + + /// Permanently delete `nft` + public entry fun burn(nft: DevNetNFT, _: &mut TxContext) { + let DevNetNFT { id, name: _, description: _, url: _ } = nft; + id::delete(id) + } +} diff --git a/doc/book/misc/move.js b/doc/book/misc/move.js new file mode 100644 index 0000000000000..b544928ff8c26 --- /dev/null +++ b/doc/book/misc/move.js @@ -0,0 +1,87 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/** + * HighlightJS Move syntax + * Use this file to make custom HighlightJS build + */ + +var module = module || {}; + +function hljsDefineMove(hljs) { + + var NUM_SUFFIX = '(u(8|64|128))\?'; + var KEYWORDS = + 'abort acquires as break continue copy copyable define else false fun ' + + 'if invariant let loop module move native public resource return spec ' + + 'struct true use while'; + var BUILTINS = + // functions + 'move_to_sender borrow_global emit_event borrow_global_mut ' + + 'move_from exists ' + + // types + 'u8 u64 u128 ' + + 'vector address bool'; + + return { + name: 'Move', + aliases: ['move'], + keywords: { + keyword: + KEYWORDS, + literal: + 'true false', + built_in: + BUILTINS + }, + lexemes: hljs.IDENT_RE + '!?', + illegal: ' + + +- [Patterns](./patterns/README.md) + - [Capability](./patterns/capability.md) + - [Witness](./patterns/witness.md) + - [Transferable Witness](./patterns/transferable-witness.md) + +- [Samples](./samples/README.md) + - [Make an NFT](./samples/nft.md) + - [Create a Coin (ERC20)](./samples/coin.md) + diff --git a/doc/book/src/basics/README.md b/doc/book/src/basics/README.md new file mode 100644 index 0000000000000..943c8213ff305 --- /dev/null +++ b/doc/book/src/basics/README.md @@ -0,0 +1,3 @@ +# Basics + +This section covers main features of Sui Move. diff --git a/doc/book/src/basics/bag.md b/doc/book/src/basics/bag.md new file mode 100644 index 0000000000000..50aac84782e6c --- /dev/null +++ b/doc/book/src/basics/bag.md @@ -0,0 +1,7 @@ +# Bag + +Bag is the only way to group a set of objects of different types. Unlike vector, it does not store everything inside but acts as a parent object. + +```move +{{#include ../../examples/sources/basics/bag.move:4:}} +``` diff --git a/doc/book/src/basics/custom-transfer.md b/doc/book/src/basics/custom-transfer.md new file mode 100644 index 0000000000000..1d791913d63aa --- /dev/null +++ b/doc/book/src/basics/custom-transfer.md @@ -0,0 +1,9 @@ +# Custom transfer + +In Sui Move, objects defined with only `key` ability can not be transferred by default. To enable +transfers, publisher has to create a custom transfer function. This function can include any arguments, +for example a fee, that users have to pay to transfer. + +```move +{{#include ../../examples/sources/basics/custom-transfer.move:4:}} +``` diff --git a/doc/book/src/basics/entry-functions.md b/doc/book/src/basics/entry-functions.md new file mode 100644 index 0000000000000..cfbe545821a91 --- /dev/null +++ b/doc/book/src/basics/entry-functions.md @@ -0,0 +1,8 @@ +# Entry Functions + +Entry function visibility modifier allows a function to be called directly (eg in transaction). It is combinable with other +visibility modifiers such as `public` (allows calling from other modules), `public(friend)` - for calling from *friend* modules. + +```move +{{#include ../../examples/sources/basics/entry-functions.move:4:}} +``` diff --git a/doc/book/src/basics/events.md b/doc/book/src/basics/events.md new file mode 100644 index 0000000000000..c8857b7438dcc --- /dev/null +++ b/doc/book/src/basics/events.md @@ -0,0 +1,7 @@ +# Events + +Events are the main way to track actions on chain. + +```move +{{#include ../../examples/sources/basics/events.move:4:}} +``` diff --git a/doc/book/src/basics/init-function.md b/doc/book/src/basics/init-function.md new file mode 100644 index 0000000000000..3fbbc89639039 --- /dev/null +++ b/doc/book/src/basics/init-function.md @@ -0,0 +1,13 @@ +# Init Function + +Init function is a special function which gets executed only once - when module is published. It always has the same signature and only +one argument. +```move +fun init(ctx: &mut TxContext) { /* ... */ } +``` + +Example: + +```move +{{#include ../../examples/sources/basics/init-function.move:4:}} +``` diff --git a/doc/book/src/basics/move-toml.md b/doc/book/src/basics/move-toml.md new file mode 100644 index 0000000000000..22cffa11b49e0 --- /dev/null +++ b/doc/book/src/basics/move-toml.md @@ -0,0 +1,12 @@ +# Move.toml + +Every Move package has a *package manifest* - it is placed in the root of the package. The manifest itself +contains a number of sections, main of which are: + +- `[package]` - includes package metadata such as name and author +- `[dependencies]` - specifies dependencies of the project +- `[addresses]` - address aliases (eg `@me` will be treated as a `0x0` address) + +```toml +{{#include ../../examples/Move.toml}} +``` diff --git a/doc/book/src/basics/owned-objects.md b/doc/book/src/basics/owned-objects.md new file mode 100644 index 0000000000000..7d100803b03f8 --- /dev/null +++ b/doc/book/src/basics/owned-objects.md @@ -0,0 +1,5 @@ +# Owned Objects + +```move +{{#include ../../examples/sources/basics/owned-objects.move:4:}} +``` diff --git a/doc/book/src/basics/shared-object.md b/doc/book/src/basics/shared-object.md new file mode 100644 index 0000000000000..845b5a3657835 --- /dev/null +++ b/doc/book/src/basics/shared-object.md @@ -0,0 +1,7 @@ +# Shared Object + +Shared object is an object that is shared using a `sui::transfer::share_object` function and is accessible to everyone. + +```move +{{#include ../../examples/sources/basics/shared-object.move:4:}} +``` diff --git a/doc/book/src/basics/strings.md b/doc/book/src/basics/strings.md new file mode 100644 index 0000000000000..af34b36ed8136 --- /dev/null +++ b/doc/book/src/basics/strings.md @@ -0,0 +1,7 @@ +# Strings + +Move does not have a native type for strings, but it has a handy wrapper! + +```move +{{#include ../../examples/sources/basics/strings.move:4:}} +``` diff --git a/doc/book/src/basics/transfer.md b/doc/book/src/basics/transfer.md new file mode 100644 index 0000000000000..a3cf1813a0ccc --- /dev/null +++ b/doc/book/src/basics/transfer.md @@ -0,0 +1,7 @@ +# Transfer + +To make object freely transferable, use a combination of `key` and `store` abilities. + +```move +{{#include ../../examples/sources/basics/transfer.move:4:}} +``` diff --git a/doc/book/src/basics/vec-map.md b/doc/book/src/basics/vec-map.md new file mode 100644 index 0000000000000..321f402361038 --- /dev/null +++ b/doc/book/src/basics/vec-map.md @@ -0,0 +1,5 @@ +# Map - Indexed Collection + +```move +{{#include ../../examples/sources/basics/vec-map.move}} +``` diff --git a/doc/book/src/patterns/README.md b/doc/book/src/patterns/README.md new file mode 100644 index 0000000000000..25ea2fd6cd9a3 --- /dev/null +++ b/doc/book/src/patterns/README.md @@ -0,0 +1,3 @@ +# Patterns + +This part covers the programming patterns that are widely used in Move; some of which can only exist in Move. diff --git a/doc/book/src/patterns/capability.md b/doc/book/src/patterns/capability.md new file mode 100644 index 0000000000000..473d798f144bf --- /dev/null +++ b/doc/book/src/patterns/capability.md @@ -0,0 +1,8 @@ +# Capability + +Capability is a pattern which allows *authorizing* actions with an object. One of the most common capabilities is `TreasuryCap` (defined in [sui::coin](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/coin.move#L19)). + + +```move +{{#include ../../examples/sources/patterns/capability.move:4:}} +``` diff --git a/doc/book/src/patterns/hot-potato.md b/doc/book/src/patterns/hot-potato.md new file mode 100644 index 0000000000000..ffbd394768b9a --- /dev/null +++ b/doc/book/src/patterns/hot-potato.md @@ -0,0 +1,5 @@ +# Hot Potato + +```move +{{#include ../../examples/sources/patterns/hot-potato.move:4:}} +``` diff --git a/doc/book/src/patterns/transferable-witness.md b/doc/book/src/patterns/transferable-witness.md new file mode 100644 index 0000000000000..bf737bca526fb --- /dev/null +++ b/doc/book/src/patterns/transferable-witness.md @@ -0,0 +1,5 @@ +# Transferable Witness + +```move +{{#include ../../examples/sources/patterns/transferable-witness.move:4:}} +``` diff --git a/doc/book/src/patterns/witness.md b/doc/book/src/patterns/witness.md new file mode 100644 index 0000000000000..ddc7931204fdc --- /dev/null +++ b/doc/book/src/patterns/witness.md @@ -0,0 +1,7 @@ +# Witness + +Witness is a pattern that is used for confirming the ownership of a type. To do so, one passes a `drop` instance of a type. Coin relies on this implementation. + +```move +{{#include ../../examples/sources/patterns/witness.move:4:}} +``` diff --git a/doc/book/src/samples/README.md b/doc/book/src/samples/README.md new file mode 100644 index 0000000000000..b4851c047b728 --- /dev/null +++ b/doc/book/src/samples/README.md @@ -0,0 +1,3 @@ +# Samples + +A collection of ready to go samples for common blockchain usecases. diff --git a/doc/book/src/samples/character.md b/doc/book/src/samples/character.md new file mode 100644 index 0000000000000..1e36694009e69 --- /dev/null +++ b/doc/book/src/samples/character.md @@ -0,0 +1 @@ +# Make a Character diff --git a/doc/book/src/samples/coin.md b/doc/book/src/samples/coin.md new file mode 100644 index 0000000000000..f666c54d93853 --- /dev/null +++ b/doc/book/src/samples/coin.md @@ -0,0 +1,9 @@ +# Create a Coin + +Publishing a coin is Sui is almost as simple as publishing a new type. However it is a bit tricky as it requires using a Witness pattern. + +```move +{{#include ../../examples/sources/samples/coin.move:4:}} +``` + +The `Coin` is a generic implementation of a Coin on Sui. Owner of the `TreasuryCap` gets control over the minting and burning of coins. Further transactions can be sent directly to the `sui::coin::Coin` with `TreasuryCap` object as authorization. diff --git a/doc/book/src/samples/nft.md b/doc/book/src/samples/nft.md new file mode 100644 index 0000000000000..7497a8fe1df80 --- /dev/null +++ b/doc/book/src/samples/nft.md @@ -0,0 +1,7 @@ +# NFT + +In Sui everything is an NFT - Objects are unique, non-fungible and owned. So technically to have something like this, a simple type publishing is enough. + +```move +{{#include ../../examples/sources/samples/nft.move:4:}} +``` diff --git a/doc/book/theme/highlight.js b/doc/book/theme/highlight.js new file mode 100644 index 0000000000000..c963c3406bed2 --- /dev/null +++ b/doc/book/theme/highlight.js @@ -0,0 +1,10 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/* + Highlight.js 10.0.0 (9e4f2dc2) + License: BSD-3-Clause + Copyright (c) 2006-2020, Ivan Sagalaev +*/ +var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!n.hasOwnProperty(r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&").replace(//g,">")}function r(e){var n={},t=Array.prototype.slice.call(arguments,1);for(const t in e)n[t]=e[t];return t.forEach((function(e){for(const t in e)n[t]=e[t]})),n}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function u(e){s+=""}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var g=l();if(s+=t(r.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+t(r.substr(i))}});const s="",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}span(e){this.buffer+=``}value(){return this.buffer}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){e.children&&(e.children.every(e=>"string"==typeof e)?(e.text=e.children.join(""),delete e.children):e.children.forEach(e=>{"string"!=typeof e&&c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function d(e){return e&&e.source||e}const g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},b={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,t){var a=r({className:"comment",begin:e,end:n,contains:[]},t||{});return a.contains.push(b),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=m("//","$"),x=m("/\\*","\\*/"),E=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:g,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>d(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:b,COMMENT:m,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:g,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),w="of and for in not or if then".split(" ");function R(e,n){return n?+n:(t=e,w.includes(t.toLowerCase())?0:1);var t}const y=t,N=r,{nodeStream:k,mergeStreams:M}=i,O=Symbol("nomatch");return function(t){var a=[],i={},s={},o=[],l=!0,c=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,g="Could not find the language '{}', did you forget to load/include a language module?",h={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function f(e){return h.noHighlightRe.test(e)}function p(e,n,t,r){var a={code:n,language:e};I("before:highlight",a);var i=a.result?a.result:b(a.language,a.code,t,r);return i.code=a.code,I("after:highlight",i),i}function b(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=w.subLanguage?function(){if(""!==M){var e="string"==typeof w.subLanguage;if(!e||i[w.subLanguage]){var n=e?b(w.subLanguage,M,!0,N[w.subLanguage]):m(M,w.subLanguage.length?w.subLanguage:null);w.relevance>0&&(A+=n.relevance),e&&(N[w.subLanguage]=n.top),k.addSublanguage(n.emitter,n.language)}else k.addText(M)}}():function(){if(!w.keywords)return void k.addText(M);let e=0;w.keywordPatternRe.lastIndex=0;let n=w.keywordPatternRe.exec(M),t="";for(;n;){t+=M.substring(e,n.index);const r=c(w,n);if(r){const[e,a]=r;k.addText(t),t="",A+=a,k.addKeyword(n[0],e)}else t+=n[0];e=w.keywordPatternRe.lastIndex,n=w.keywordPatternRe.exec(M)}t+=M.substr(e),k.addText(t)}(),M=""}function f(e){return e.className&&k.openNode(e.className),w=Object.create(e,{parent:{value:w}})}function p(e){return 0===w.matcher.regexIndex?(M+=e[0],1):(B=!0,0)}var v={};function x(t,r){var i=r&&r[0];if(M+=t,null==i)return u(),0;if("begin"===v.type&&"end"===r.type&&v.index===r.index&&""===i){if(M+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=v.rule,n}return 1}if(v=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;let a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(let n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?M+=t:(r.excludeBegin&&(M+=t),u(),r.returnBegin||r.excludeBegin||(M=t)),f(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(w.className||"")+'"');throw e.mode=w,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){let e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(w,e,r);if(!a)return O;var i=w;i.skip?M+=t:(i.returnEnd||i.excludeEnd||(M+=t),u(),i.excludeEnd&&(M=t));do{w.className&&k.closeNode(),w.skip||w.subLanguage||(A+=w.relevance),w=w.parent}while(w!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),f(a.starts)),i.returnEnd?0:t.length}(r);if(s!==O)return s}if("illegal"===r.type&&""===i)return 1;if(S>1e5&&S>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return M+=i,i.length}var E=T(e);if(!E)throw console.error(g.replace("{}",e)),Error('Unknown language: "'+e+'"');!function(e){function n(n,t){return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+="|"),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");!function t(s,o){if(s.compiled)return;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let l=null;if("object"==typeof s.keywords&&(l=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,R(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&l)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");s.keywordPatternRe=n(s.lexemes||l||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),s.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(s.endRe=n(s.end)),s.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(s.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(s.illegalRe=n(s.illegal)),null==s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,s)})),s.starts&&t(s.starts,o),s.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(s)}(e)}(E);var _="",w=s||E,N={},k=new h.__emitter(h);!function(){for(var e=[],n=w;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>k.openNode(e))}();var M="",A=0,I=0,S=0,B=!1;try{for(w.matcher.considerAll();;){S++,B?B=!1:(w.matcher.lastIndex=I,w.matcher.considerAll());const e=w.matcher.exec(o);if(!e)break;const n=x(o.substring(I,e.index),e);I=e.index+n}return x(o.substr(I)),k.closeAllNodes(),k.finalize(),_=k.toHTML(),{relevance:A,value:_,language:e,illegal:!1,emitter:k,top:w}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(I-100,I+100),mode:n.mode},sofar:_,relevance:0,value:y(o),emitter:k};if(l)return{relevance:0,value:y(o),emitter:k,language:e,top:w,errorRaised:n};throw n}}function m(e,n){n=n||h.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new h.__emitter(h),value:y(e),illegal:!1,top:w};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(A).forEach((function(n){var a=b(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function v(e){return h.tabReplace||h.useBR?e.replace(c,(function(e,n){return h.useBR&&"\n"===e?"
":h.tabReplace?n.replace(/\t/g,h.tabReplace):""})):e}function x(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=h.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>f(e)||T(e))}(e);if(f(t))return;I("before:highlightBlock",{block:e,language:t}),h.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e;const r=n.textContent,a=t?p(t,r,!0):m(r),i=k(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=M(i,k(e),r)}a.value=v(a.value),I("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance})}function E(){if(!E.called){E.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,x)}}const w={disableAutodetect:!0,name:"Plain text"};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e){var n=T(e);return n&&!n.disableAutodetect}function I(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:p,highlightAuto:m,fixMarkup:v,highlightBlock:x,configure:function(e){h=N(h,e)},initHighlighting:E,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",E,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=w}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&r.aliases.forEach((function(n){s[n]=e}))},listLanguages:function(){return Object.keys(i)},getLanguage:T,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:A,inherit:N,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.0.0";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs); +hljs.registerLanguage("move",function(){"use strict";var e=e||{};function n(e){var n="move_to_sender borrow_global emit_event borrow_global_mut has move_from exists u8 u64 u128 vector address bool";return{name:"Move",aliases:["move"],keywords:{keyword:"abort acquires has as break continue copy drop key mut store define else false fun if invariant let loop module entry move native public const return spec struct true use while",literal:"true false",built_in:n},lexemes:e.IDENT_RE+"!?",illegal:"