From d7529c0b65ee582e5742cff27420ad42547c0a0c Mon Sep 17 00:00:00 2001 From: thounyy Date: Mon, 6 Jan 2025 19:22:40 +0100 Subject: [PATCH 1/4] docs: update conventions to latest Move features --- .../sui-move-concepts/conventions.mdx | 652 +++++++++--------- 1 file changed, 308 insertions(+), 344 deletions(-) diff --git a/docs/content/concepts/sui-move-concepts/conventions.mdx b/docs/content/concepts/sui-move-concepts/conventions.mdx index d97f0f7773fe1..96be4dc752f40 100644 --- a/docs/content/concepts/sui-move-concepts/conventions.mdx +++ b/docs/content/concepts/sui-move-concepts/conventions.mdx @@ -1,494 +1,458 @@ --- -title: Conventions -description: Recommended Move 2024 best practices for the Sui blockchain. +title: Move Conventions +description: Recommended Move 2024 best practices for Sui development. --- -The following recommendations are based on 2024 Move. +## Preamble -## Add section titles +This guide outlines recommended conventions and best practices for writing Move smart contracts on Sui. Following these guidelines helps create more maintainable, secure, and composable code that aligns with the ecosystem's standards. -Use titles in code comments to create sections for your Move code files. Structure your titles using `===` on either side of the title. +### Who is this for? -```move -module conventions::comments { - // === Imports === - - // === Errors === +This guide is intended for: +- Move developers building on Sui +- Teams establishing coding standards +- Anyone reviewing or maintaining Move codebases - // === Constants === +### How to use this guide - // === Structs === +The conventions are organized into several key areas: +- **Organization Principles**: How to structure your packages, modules, and code +- **Naming Conventions**: Standard conventions for naming different components +- **Code Structure**: Best practices and patterns for writing clean, maintainable code +- **Documentation**: How to document your code effectively - // === Method Aliases === +While these conventions are recommendations rather than strict rules, they represent patterns that have proven effective across many Sui projects. They help create consistency across the ecosystem and make code easier to understand and maintain. - // === Public-Mutative Functions === +## Organization Principles - // === Public-View Functions === +### Package - // === Admin Functions === +A Sui package consists of: +- a `sources` directory containing the Move code that will be uploaded to the blockchain +- a `Move.toml` manifest file where the dependencies are declared as well as other information about the package +- a `Move.lock` file that is automatically generated by the Sui Move toolchain to lock the versions of the dependencies and track the different published and upgraded versions of the package that exist on the different networks - // === Public-Package Functions === +For this reason, the `Move.lock` file should always be part of the package (don't add it to the `.gitignore` file). Use the [automated address management](https://docs.sui.io/concepts/sui-move-concepts/packages/automated-address-management) instead of the old `published-at` field in the manifest file. - // === Private Functions === +Optionally, you can add a tests directory to contain the tests for the package and an examples directory to provide use cases for the package, both won't be uploaded on-chain. - // === Test Functions === -} +``` +sources/ + my_module.move + another_module.move + ... +tests/ + my_module_tests.move + ... +examples/ + using_my_module.move +Move.lock +Move.toml ``` -## CRUD functions names +In your package manifest, the package name should be in PascalCase: `name = "MyPackage"`, ideally the named address representing the package should be the same as the package name, but in snake_case: `my_package = 0x0`. -These are the available CRUD functions: +### Modules -- `add`: Adds a value. -- `new`: Creates an object. -- `drop`: Drops a struct. -- `empty`: Creates a struct. -- `remove`: Removes a value. -- `exists_`: Checks if a key exists. -- `contains`: Checks if a collection contains a value. -- `destroy_empty`: Destroys an object or data structure that has values with the **drop** ability. -- `to_object_name`: Transforms an Object X to Object Y. -- `from_object_name`: Transforms an Object Y to Object X. -- `property_name`: Returns an immutable reference or a copy. -- `property_name_mut`: Returns a mutable reference. +Modules are the main building blocks of your Move code. They are used to organize and encapsulate related functionality. Design your modules around one object or data structure. A variant structure should have its own module to avoid complexity and bugs. -## Potato structs - -Do not use 'potato' in the name of structs. The lack of abilities define it as a potato pattern. +Module declarations don't need to use brackets anymore and the compiler provides default use statements for widely used modules, so you don't need to declare all of them. ```move -module conventions::request { - // ✅ Right - struct Request {} +module conventions::wallet; - // ❌ Wrong - struct RequestPotato {} +public struct Wallet has key, store { + id: UID, + amount: u64 +} + + +module conventions::claw_back_wallet; + +public struct Wallet has key { + id: UID, + amount: u64 } ``` -## Read functions +### Body -Be mindful of the dot syntax when naming functions. Avoid using the object name on function names. +Structure your code using comments to create sections for your Move code files. Structure your titles using `===` on either side of the title. ```move -module conventions::profile { +module conventions::comments; - struct Profile { - age: u64 - } +// === Imports === - // ✅ Right - public fun age(self: &Profile): u64 { - self.age - } +// === Errors === - // ❌ Wrong - public fun profile_age(self: &Profile): u64 { - self.age - } -} +// === Constants === -module conventions::defi { +// === Structs === - use conventions::profile::{Self, Profile}; +// === Events === - public fun get_tokens(profile: &Profile) { +// === Method Aliases === - // ✅ Right - let name = profile.age(); +// === Public Functions === - // ❌ Wrong - let name2 = profile.profile_age(); - } -} +// === View Functions === + +// === Admin Functions === + +// === Package Functions === + +// === Private Functions === + +// === Test Functions === ``` -## Empty function +Here, "Public Functions" are the functions modifying state, "View Functions" are often on-chain getters or off-chain helpers. The latter are not necessary because data is easily readable by querying objects. `init` function should be the first function in the module if it exists. -Name the functions that create data structures as `empty`. +Try to sort your functions by their purpose and according to the user flow to improve readability, you can also use explicit function names like `admin_set_fees` to make it clear what the function does. -```move -module conventions::collection { +Ideally, test functions should only consists of `[test_only]` helpers for the actual tests that should be located in the tests directory. - struct Collection has copy, drop, store { - bits: vector - } +Imports can be grouped by dependency, for example: - public fun empty(): Collection { - Collection { - bits: vector[] - } - } -} +```move +use std::string::String; +use sui::{ + coin::Coin, + balance, + table::Table +}; +use my_dep::battle::{Battle, Score}; ``` -## New function +## Naming Conventions + +### Constants -Name the functions that create objects as `new`. +Constants should be in uppercase and use snake case. Errors are specific constants that use PascalCase and start with an E. Make them descriptive. ```move -module conventions::object { +module conventions::constants; - use sui::object::{Self, UID}; - use sui::tx_context::TxContext; +const MAX_NAME_LENGTH: u64 = 64; +// this is right +const EInvalidName: u64 = 0; +// this is wrong +const E_INVALID_NAME: u64 = 0; +``` - struct Object has key, store { - id: UID - } +### Structs - public fun new(ctx:&mut TxContext): Object { - Object { - id: object::new(ctx) - } - } -} -``` +To anticipate for future features, struct must be declared with the `public` modifier. Abilities must always be declared in this order: `key`, `copy`, `drop`, `store`. Do not use 'potato' in the name of structs. The lack of abilities define it as a potato pattern. -## Shared objects +Structs support positional fields that can be used for simple wrappers, dynamic fields keys or as tuples. -Library modules that share objects should provide two functions: one to create the object and another to share it. It allows the caller to access its UID and run custom functionality before sharing it. +Some the structs will be used to emit events, these structs should be named with the `Event` suffix. ```move -module conventions::profile { +module conventions::request; - use sui::object::{Self, UID}; - use sui::tx_context::TxContext; - use sui::transfer::share_object; +// dynamic field keys +public struct ReceiptKey(ID) has copy, drop, store; - struct Profile has key { - id: UID - } +// dynamic field +public struct Receipt has key, store { + id: UID, + data: Data +} - public fun new(ctx:&mut TxContext): Profile { - Profile { - id: object::new(ctx) - } - } +// right naming +public struct Request {} - public fun share(profile: Profile) { - share_object(profile); - } -} +// wrong naming +public struct RequestPotato {} ``` -## Reference functions +### CRUD functions names -Name the functions that return a reference as `_mut` or ``, replacing with `` the actual name of the property. +The functions below follow standard CRUD (Create, Read, Update, Delete) naming conventions: -```move -module conventions::profile { +- `new`: Creates an empty object. +- `empty`: Creates an empty struct. +- `create`: Creates an initialized object or struct. +- `add`: Adds a value. +- `remove`: Removes a value. +- `exists`: Checks if a key exists. +- `contains`: Checks if a collection contains a value. +- `borrow`: Returns an immutable reference of a struct or object. +- `borrow_mut`: Returns a mutable reference of a struct or object. +- `property_name`: Returns an immutable reference or a copy of a field. +- `property_name_mut`: Returns a mutable reference of a field. +- `drop`: Drops a struct. +- `destroy`: Destroys an object or data structure that has values with the **drop** ability. +- `destroy_empty`: Destroys an empty object or data structure that has values with the **drop** ability. +- `to_name`: Transforms an Type X to Type Y. +- `from_name`: Transforms an Type Y to Type X. - use std::string::String; +### Generics - use sui::object::UID; +Generics can be declared using single letter names or full names. By convention, developers use T and U for generic types, but you can use a more descriptive name if it is not confusing with other types. Always prioritize readability. - struct Profile has key { - id: UID, - name: String, - age: u8 - } +```move +module conventions::generics; - // profile.name() - public fun name(self: &Profile): &String { - &self.name - } +// single letter name +public struct Receipt has store { ... } - // profile.age_mut() - public fun age_mut(self: &mut Profile): &mut u8 { - &mut self.age - } -} +// full name +public struct Receipt has store { ... } ``` -## Separation of concerns +## Code Structure + +### Shared objects -Design your modules around one object or data structure. A variant structure should have its own module to avoid complexity and bugs. +Library modules that share objects should provide two functions: one to instantiate and return the object, and another one to share it. It allows the caller to pass it to other functions and run custom functionality before sharing it. ```move -module conventions::wallet { +module conventions::shop; - use sui::object::UID; +public struct Shop has key { + id: UID +} - struct Wallet has key, store { - id: UID, - amount: u64 +public fun new(ctx: &mut TxContext): Shop { + Shop { + id: object::new(ctx) } } -module conventions::claw_back_wallet { - - use sui::object::UID; - - struct Wallet has key { - id: UID, - amount: u64 - } +public fun share(shop: Shop) { + transfer::share_object(shop); } ``` -## Errors +### Pure functions -Use PascalCase for errors, start with an E and be descriptive. +Keep your functions pure to maintain composability. Do not use `transfer::transfer` or `transfer::public_transfer` inside core functions, except in specific cases where the object is not transferable and shouldn't be modified. ```move -module conventions::errors { - // ✅ Right - const ENameHasMaxLengthOf64Chars: u64 = 0; +module conventions::amm; - // ❌ Wrong - const INVALID_NAME: u64 = 0; -} -``` - -## Struct property comments +use sui::coin::Coin; -Describe the properties of your structs. +public struct Pool has key { + id: UID +} -```move -module conventions::profile { +// right -> returns the excess coins even if they have zero value. +public fun add_liquidity(pool: &mut Pool, coin_x: Coin, coin_y: Coin): (Coin, Coin, Coin) { + // Implementation omitted. + abort(0) +} - use std::string::String; +// right but not recommended +public fun add_liquidity_and_transfer(pool: &mut Pool, coin_x: Coin, coin_y: Coin, recipient: address) { + let (lp_coin, coin_x, coin_y) = add_liquidity(pool, coin_x, coin_y); + transfer::public_transfer(lp_coin, recipient); + transfer::public_transfer(coin_x, recipient); + transfer::public_transfer(coin_y, recipient); +} - use sui::object::UID; +// wrong +public fun impure_add_liquidity(pool: &mut Pool, coin_x: Coin, coin_y: Coin, ctx: &mut TxContext): Coin { + let (lp_coin, coin_x, coin_y) = add_liquidity(pool, coin_x, coin_y); + transfer::public_transfer(coin_x, tx_context::sender(ctx)); + transfer::public_transfer(coin_y, tx_context::sender(ctx)); - struct Profile has key, store { - id: UID, - /// The age of the user - age: u8, - /// The first name of the user - name: String - } + lp_coin } ``` -## Destroy functions +### Coin argument -Provide functions to delete objects. Destroy empty objects with the function `destroy_empty`. Use the function `drop` for objects that have types that can be dropped. +Pass the `Coin` object by value with the exact right amount directly to improve transaction readability from the frontend. ```move -module conventions::wallet { +module conventions::amm; - use sui::object::{Self, UID}; - use sui::balance::{Self, Balance}; - use sui::sui::SUI; +use sui::coin::Coin; - struct Wallet has key, store { - id: UID, - value: Value - } +public struct Pool has key { + id: UID +} - // Value has drop - public fun drop(self: Wallet) { - let Wallet { id, value: _ } = self; - object::delete(id); - } +// right +public fun swap(coin_in: Coin): Coin { + // Implementation omitted. + abort(0) +} - // Value doesn't have drop - // Throws if the `wallet.value` is not empty. - public fun destroy_empty(self: Wallet>) { - let Wallet { id, value } = self; - object::delete(id); - balance::destroy_zero(value); - } +// wrong +public fun exchange(coin_in: &mut Coin, value: u64): Coin { + // Implementation omitted. + abort(0) } ``` -## Pure functions +### Access control -Keep your functions pure to maintain composability. Do not use `transfer::transfer` or `transfer::public_transfer` inside core functions. +To maintain composability, use capability objects instead of arrays of addresses for access control. ```move -module conventions::amm { - - use sui::transfer; - use sui::coin::Coin; - use sui::object::UID; - use sui::tx_context::{Self, TxContext}; - - struct Pool has key { - id: UID - } +module conventions::access_control; - // ✅ Right - // Return the excess coins even if they have zero value. - public fun add_liquidity(pool: &mut Pool, coin_x: Coin, coin_y: Coin): (Coin, Coin, Coin) { - // Implementation omitted. - abort(0) - } - - // ✅ Right - public fun add_liquidity_and_transfer(pool: &mut Pool, coin_x: Coin, coin_y: Coin, recipient: address) { - let (lp_coin, coin_x, coin_y) = add_liquidity(pool, coin_x, coin_y); - transfer::public_transfer(lp_coin, recipient); - transfer::public_transfer(coin_x, recipient); - transfer::public_transfer(coin_y, recipient); - } +use sui::sui::SUI; +use sui::balance::Balance; +use sui::coin::{Self, Coin}; +use sui::table::{Self, Table}; - // ❌ Wrong - public fun impure_add_liquidity(pool: &mut Pool, coin_x: Coin, coin_y: Coin, ctx: &mut TxContext): Coin { - let (lp_coin, coin_x, coin_y) = add_liquidity(pool, coin_x, coin_y); - transfer::public_transfer(coin_x, tx_context::sender(ctx)); - transfer::public_transfer(coin_y, tx_context::sender(ctx)); - - lp_coin - } +public struct Account has key, store { + id: UID, + balance: u64 } -``` - -## Coin argument -Pass the `Coin` object by value with the right amount directly because it's better for transaction readability from the frontend. +public struct State has key { + id: UID, + // field not necessary as the state lives in the Account objects + accounts: Table, + balance: Balance +} -```move -module conventions::amm { +// right -> with this function, another protocol can hold the `Account` on behalf of a user. +public fun withdraw(state: &mut State, account: &mut Account, ctx: &mut TxContext): Coin { + let authorized_balance = account.balance; - use sui::coin::Coin; - use sui::object::UID; + account.balance = 0; - struct Pool has key { - id: UID - } + coin::take(&mut state.balance, authorized_balance, ctx) +} - // ✅ Right - public fun swap(coin_in: Coin): Coin { - // Implementation omitted. - abort(0) - } +// wrong -> this is less composable. +public fun wrong_withdraw(state: &mut State, ctx: &mut TxContext): Coin { + let sender = tx_context::sender(ctx); - // ❌ Wrong - public fun exchange(coin_in: &mut Coin, value: u64): Coin { - // Implementation omitted. - abort(0) - } + let authorized_balance = table::borrow_mut(&mut state.accounts, sender); + let value = *authorized_balance; + *authorized_balance = 0; + coin::take(&mut state.balance, value, ctx) } ``` -## Access control +### Data storage in owned vs shared objects -To maintain composability, use capabilities instead of addresses for access control. +If your dApp data has a one to one relationship, it's best to use owned objects. ```move -module conventions::access_control { - - use sui::sui::SUI; - use sui::object::UID; - use sui::balance::Balance; - use sui::coin::{Self, Coin}; - use sui::table::{Self, Table}; - use sui::tx_context::{Self, TxContext}; - - struct Account has key, store { - id: UID, - balance: u64 - } +module conventions::vesting_wallet; - struct State has key { - id: UID, - accounts: Table, - balance: Balance - } +use sui::sui::SUI; +use sui::coin::Coin; +use sui::table::Table; +use sui::balance::Balance; - // ✅ Right - // With this function, another protocol can hold the `Account` on behalf of a user. - public fun withdraw(state: &mut State, account: &mut Account, ctx: &mut TxContext): Coin { - let authorized_balance = account.balance; +public struct OwnedWallet has key { + id: UID, + balance: Balance +} - account.balance = 0; +public struct SharedWallet has key { + id: UID, + balance: Balance, + accounts: Table +} - coin::take(&mut state.balance, authorized_balance, ctx) - } - // ❌ Wrong - // This is less composable. - public fun wrong_withdraw(state: &mut State, ctx: &mut TxContext): Coin { - let sender = tx_context::sender(ctx); +// A vesting wallet releases a certain amount of coin over a period of time. +// If the entire balance belongs to one user and the wallet has no additional functionalities, it is best to store it in an owned object. +public fun new(deposit: Coin, ctx: &mut TxContext): OwnedWallet { + // Implementation omitted. + abort(0) +} - let authorized_balance = table::borrow_mut(&mut state.accounts, sender); - let value = *authorized_balance; - *authorized_balance = 0; - coin::take(&mut state.balance, value, ctx) - } +// If you wish to add extra functionality to a vesting wallet, it is best to share the object. +// For example, if you wish the issuer of the wallet to be able to cancel the contract in the future. +public fun new_shared(deposit: Coin, ctx: &mut TxContext) { + // Implementation omitted. + // shares the `SharedWallet`. + abort(0) } ``` -## Data storage in owned vs shared objects +### Admin capability -If your dApp data has a one to one relationship, it's best to use owned objects. +In admin-gated functions, the first parameter should be the capability. It helps the autocomplete with user types. ```move -module conventions::vesting_wallet { - - use sui::sui::SUI; - use sui::coin::Coin; - use sui::object::UID; - use sui::table::Table; - use sui::balance::Balance; - use sui::tx_context::TxContext; - - struct OwnedWallet has key { - id: UID, - balance: Balance - } +module conventions::social_network; - struct SharedWallet has key { - id: UID, - balance: Balance, - accounts: Table - } +use std::string::String; - /* - * A vesting wallet releases a certain amount of coin over a period of time. - * If the entire balance belongs to one user and the wallet has no additional functionalities, it is best to store it in an owned object. - */ - public fun new(deposit: Coin, ctx: &mut TxContext): OwnedWallet { - // Implementation omitted. - abort(0) - } +public struct Account has key { + id: UID, + name: String +} - /* - * If you wish to add extra functionality to a vesting wallet, it is best to share the object. - * For example, if you wish the issuer of the wallet to be able to cancel the contract in the future. - */ - public fun new_shared(deposit: Coin, ctx: &mut TxContext) { - // Implementation omitted. - // It shares the `SharedWallet`. - abort(0) - } +public struct Admin has key { + id: UID, +} + +// right -> cap.update(&mut account, b"jose".to_string()); +public fun update(_: &Admin, account: &mut Account, new_name: String) { + // Implementation omitted. + abort(0) +} + +// wrong -> account.update(&cap, b"jose".to_string()); +public fun set(account: &mut Account, _: &Admin, new_name: String) { + // Implementation omitted. + abort(0) } ``` -## Admin capability +## Documentation -In admin-gated functions, the first parameter should be the capability. It helps the autocomplete with user types. +There is nothing more pleasant than a well-written and well-documented codebase. If some support the fact that clean code is self-documenting, I would say that well-documented code is self-explanatory. -```move -module conventions::social_network { +### Comments - use std::string::String; +Document your code by explaining functions and structs in simple terms using the `///` syntax (doc comment). If you want to add technical insights for developers that might use your code, use the `//` syntax (regular comment). - use sui::object::UID; +Field comments should be used to describe the properties of your structs. In complex functions, you can also describe the parameters and return values. - struct Account has key { - id: UID, - name: String - } +```move +module conventions::hero; - struct Admin has key { - id: UID, - } +use std::string::String; +use sui::kiosk::{Kiosk, KioskOwnerCap}; - // ✅ Right - // cap.update(&mut account, b"jose"); - public fun update(_: &Admin, account: &mut Account, new_name: String) { - // Implementation omitted. - abort(0) - } +public struct Hero has key, store { + id: UID, + // power of the nft + power: u64 +} - // ❌ Wrong - // account.update(&cap, b"jose"); - public fun set(account: &mut Account, _: &Admin, new_name: String) { - // Implementation omitted. - abort(0) +/// creates and returns a new Hero object +public fun new(ctx: &mut TxContext): Hero { + Hero { + id: object::new(ctx), + power: 0 } } + +// should be initialized before being shared +public fun initialize_hero(hero: &mut Hero) { + hero.power = 100; +} + +public fun start_battle( + self: &mut Kiosk, // user kiosk + cap: &KioskOwnerCap, // user kiosk owner cap + _policy: &TransferPolicy, // transfer policy for the game + hero_id: ID, // hero to use + battle_id: String // id of the battle to start +) { + // Implementation omitted. + abort(0) +} ``` + +### README + +The README file should be located in the root of the package and should be named `README.md`. It should contain a description of the package, the purpose of the package, and how to use it. \ No newline at end of file From 82765644f676c6ade10f053672dd16350fc1ace6 Mon Sep 17 00:00:00 2001 From: Thouny <92447129+thounyy@users.noreply.github.com> Date: Tue, 7 Jan 2025 09:41:59 +0100 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: ronny-mysten <118224482+ronny-mysten@users.noreply.github.com> --- .../sui-move-concepts/conventions.mdx | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/docs/content/concepts/sui-move-concepts/conventions.mdx b/docs/content/concepts/sui-move-concepts/conventions.mdx index 96be4dc752f40..44bbcfc79b09f 100644 --- a/docs/content/concepts/sui-move-concepts/conventions.mdx +++ b/docs/content/concepts/sui-move-concepts/conventions.mdx @@ -3,39 +3,38 @@ title: Move Conventions description: Recommended Move 2024 best practices for Sui development. --- -## Preamble -This guide outlines recommended conventions and best practices for writing Move smart contracts on Sui. Following these guidelines helps create more maintainable, secure, and composable code that aligns with the ecosystem's standards. +This guide outlines recommended conventions and best practices for writing Move smart contracts on Sui. Following these guidelines helps create more maintainable, secure, and composable code that aligns with ecosystem standards. -### Who is this for? +## Who is this for? This guide is intended for: - Move developers building on Sui - Teams establishing coding standards - Anyone reviewing or maintaining Move codebases -### How to use this guide +## How to use this guide The conventions are organized into several key areas: -- **Organization Principles**: How to structure your packages, modules, and code +- **Organization principles:** How to structure your packages, modules, and code - **Naming Conventions**: Standard conventions for naming different components - **Code Structure**: Best practices and patterns for writing clean, maintainable code - **Documentation**: How to document your code effectively While these conventions are recommendations rather than strict rules, they represent patterns that have proven effective across many Sui projects. They help create consistency across the ecosystem and make code easier to understand and maintain. -## Organization Principles +## Organization principles ### Package A Sui package consists of: -- a `sources` directory containing the Move code that will be uploaded to the blockchain -- a `Move.toml` manifest file where the dependencies are declared as well as other information about the package -- a `Move.lock` file that is automatically generated by the Sui Move toolchain to lock the versions of the dependencies and track the different published and upgraded versions of the package that exist on the different networks +- a `sources` directory containing the Move code to be uploaded to the blockchain +- a `Move.toml` manifest file that declares dependencies and other information about the package +- a `Move.lock` file that the Sui Move toolchain automatically generates to lock the versions of the dependencies and track the different published and upgraded versions of the package that exist on the different networks For this reason, the `Move.lock` file should always be part of the package (don't add it to the `.gitignore` file). Use the [automated address management](https://docs.sui.io/concepts/sui-move-concepts/packages/automated-address-management) instead of the old `published-at` field in the manifest file. -Optionally, you can add a tests directory to contain the tests for the package and an examples directory to provide use cases for the package, both won't be uploaded on-chain. +Optionally, you can add a `tests` directory to contain the tests for the package and an `examples` directory to provide use cases for the package. Neither directory is uploaded on chain when you publish the package. ``` sources/ @@ -51,13 +50,13 @@ Move.lock Move.toml ``` -In your package manifest, the package name should be in PascalCase: `name = "MyPackage"`, ideally the named address representing the package should be the same as the package name, but in snake_case: `my_package = 0x0`. +In your package manifest, the package name should be in PascalCase: `name = "MyPackage"`. Ideally, the named address representing the package should be the same as the package name, but in snake_case: `my_package = 0x0`. ### Modules Modules are the main building blocks of your Move code. They are used to organize and encapsulate related functionality. Design your modules around one object or data structure. A variant structure should have its own module to avoid complexity and bugs. -Module declarations don't need to use brackets anymore and the compiler provides default use statements for widely used modules, so you don't need to declare all of them. +Module declarations don't need to use brackets anymore and the compiler provides default `use` statements for widely used modules, so you don't need to declare all of them. ```move module conventions::wallet; @@ -108,13 +107,13 @@ module conventions::comments; // === Test Functions === ``` -Here, "Public Functions" are the functions modifying state, "View Functions" are often on-chain getters or off-chain helpers. The latter are not necessary because data is easily readable by querying objects. `init` function should be the first function in the module if it exists. +Here, "public functions" are the functions modifying state, "view functions" are often on-chain getters or off-chain helpers. The latter are not necessary because you can query objects to read their data. The `init` function should be the first function in the module, if it exists. -Try to sort your functions by their purpose and according to the user flow to improve readability, you can also use explicit function names like `admin_set_fees` to make it clear what the function does. +Try to sort your functions by their purpose and according to the user flow to improve readability. You can also use explicit function names like `admin_set_fees` to make it clear what the function does. -Ideally, test functions should only consists of `[test_only]` helpers for the actual tests that should be located in the tests directory. +Ideally, test functions should only consist of `[test_only]` helpers for the actual tests that are located in the `tests` directory. -Imports can be grouped by dependency, for example: +Group imports by dependency, for example: ```move use std::string::String; @@ -126,11 +125,11 @@ use sui::{ use my_dep::battle::{Battle, Score}; ``` -## Naming Conventions +## Naming conventions ### Constants -Constants should be in uppercase and use snake case. Errors are specific constants that use PascalCase and start with an E. Make them descriptive. +Constants should be uppercase and formatted as snake case. Errors are specific constants that use PascalCase and start with an E. Make them descriptive. ```move module conventions::constants; @@ -144,11 +143,15 @@ const E_INVALID_NAME: u64 = 0; ### Structs -To anticipate for future features, struct must be declared with the `public` modifier. Abilities must always be declared in this order: `key`, `copy`, `drop`, `store`. Do not use 'potato' in the name of structs. The lack of abilities define it as a potato pattern. +To account for future features, declare structs with the `public` modifier. -Structs support positional fields that can be used for simple wrappers, dynamic fields keys or as tuples. +Always declare struct abilities in this order: `key`, `copy`, `drop`, `store`. -Some the structs will be used to emit events, these structs should be named with the `Event` suffix. +Do not use 'potato' in the name of structs. The lack of abilities define it as a potato pattern. + +Structs support positional fields that can be used for simple wrappers, dynamic field keys, or as tuples. + +Use the `Event` suffix to name structs that emit events. ```move module conventions::request; @@ -169,9 +172,9 @@ public struct Request {} public struct RequestPotato {} ``` -### CRUD functions names +### CRUD function names -The functions below follow standard CRUD (Create, Read, Update, Delete) naming conventions: +The following functions follow standard CRUD (Create, Read, Update, Delete) naming conventions: - `new`: Creates an empty object. - `empty`: Creates an empty struct. @@ -187,12 +190,12 @@ The functions below follow standard CRUD (Create, Read, Update, Delete) naming c - `drop`: Drops a struct. - `destroy`: Destroys an object or data structure that has values with the **drop** ability. - `destroy_empty`: Destroys an empty object or data structure that has values with the **drop** ability. -- `to_name`: Transforms an Type X to Type Y. -- `from_name`: Transforms an Type Y to Type X. +- `to_name`: Transforms a Type X to Type Y. +- `from_name`: Transforms a Type Y to Type X. ### Generics -Generics can be declared using single letter names or full names. By convention, developers use T and U for generic types, but you can use a more descriptive name if it is not confusing with other types. Always prioritize readability. +Declare generics using single letter names or full names. By convention, developers use `T` and `U` for generic types, but you can use a more descriptive name if it is not confusing with other types. Always prioritize readability. ```move module conventions::generics; @@ -408,13 +411,13 @@ public fun set(account: &mut Account, _: &Admin, new_name: String) { ## Documentation -There is nothing more pleasant than a well-written and well-documented codebase. If some support the fact that clean code is self-documenting, I would say that well-documented code is self-explanatory. +There is nothing more pleasant than a well-written and well-documented codebase. If some support the idea that clean code is self-documenting, well-documented code is self-explanatory. ### Comments Document your code by explaining functions and structs in simple terms using the `///` syntax (doc comment). If you want to add technical insights for developers that might use your code, use the `//` syntax (regular comment). -Field comments should be used to describe the properties of your structs. In complex functions, you can also describe the parameters and return values. +Use field comments to describe the properties of your structs. In complex functions, you can also describe the parameters and return values. ```move module conventions::hero; @@ -428,7 +431,7 @@ public struct Hero has key, store { power: u64 } -/// creates and returns a new Hero object +/// Creates and returns a new Hero object public fun new(ctx: &mut TxContext): Hero { Hero { id: object::new(ctx), @@ -455,4 +458,4 @@ public fun start_battle( ### README -The README file should be located in the root of the package and should be named `README.md`. It should contain a description of the package, the purpose of the package, and how to use it. \ No newline at end of file +Create a `README.md` file in the root of the package. Include a description of the package, the purpose of the package, and how to use it. \ No newline at end of file From e620e323808ff0cd6467c46b34bbd315d06290cc Mon Sep 17 00:00:00 2001 From: thounyy Date: Tue, 7 Jan 2025 09:49:09 +0100 Subject: [PATCH 3/4] chore: unstack titles --- .../concepts/sui-move-concepts/conventions.mdx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/content/concepts/sui-move-concepts/conventions.mdx b/docs/content/concepts/sui-move-concepts/conventions.mdx index 44bbcfc79b09f..e6eb88bfcfe28 100644 --- a/docs/content/concepts/sui-move-concepts/conventions.mdx +++ b/docs/content/concepts/sui-move-concepts/conventions.mdx @@ -127,6 +127,8 @@ use my_dep::battle::{Battle, Score}; ## Naming conventions +Adhering to naming conventions in your code helps readability and ultimately makes your codebase easier to maintain. The following sections outline the key naming conventions to follow when writing Move code. + ### Constants Constants should be uppercase and formatted as snake case. Errors are specific constants that use PascalCase and start with an E. Make them descriptive. @@ -134,10 +136,13 @@ Constants should be uppercase and formatted as snake case. Errors are specific c ```move module conventions::constants; +// correct non-error constant const MAX_NAME_LENGTH: u64 = 64; -// this is right + +// correct error constant const EInvalidName: u64 = 0; -// this is wrong + +// wrong error constant const E_INVALID_NAME: u64 = 0; ``` @@ -166,7 +171,7 @@ public struct Receipt has key, store { } // right naming -public struct Request {} +public struct Request(); // wrong naming public struct RequestPotato {} @@ -209,6 +214,8 @@ public struct Receipt has store { ... } ## Code Structure +The following section covers common patterns and best practices specific to Move development on Sui, including object ownership models and function design principles. + ### Shared objects Library modules that share objects should provide two functions: one to instantiate and return the object, and another one to share it. It allows the caller to pass it to other functions and run custom functionality before sharing it. From 4964c03c1787a2477cf2a1944790eb1693e19eb4 Mon Sep 17 00:00:00 2001 From: thounyy Date: Tue, 7 Jan 2025 12:43:17 +0100 Subject: [PATCH 4/4] chore: shorten the intro based on @juzybits recommendation --- .../sui-move-concepts/conventions.mdx | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/docs/content/concepts/sui-move-concepts/conventions.mdx b/docs/content/concepts/sui-move-concepts/conventions.mdx index e6eb88bfcfe28..1589f2cf22ab3 100644 --- a/docs/content/concepts/sui-move-concepts/conventions.mdx +++ b/docs/content/concepts/sui-move-concepts/conventions.mdx @@ -3,24 +3,8 @@ title: Move Conventions description: Recommended Move 2024 best practices for Sui development. --- - This guide outlines recommended conventions and best practices for writing Move smart contracts on Sui. Following these guidelines helps create more maintainable, secure, and composable code that aligns with ecosystem standards. -## Who is this for? - -This guide is intended for: -- Move developers building on Sui -- Teams establishing coding standards -- Anyone reviewing or maintaining Move codebases - -## How to use this guide - -The conventions are organized into several key areas: -- **Organization principles:** How to structure your packages, modules, and code -- **Naming Conventions**: Standard conventions for naming different components -- **Code Structure**: Best practices and patterns for writing clean, maintainable code -- **Documentation**: How to document your code effectively - While these conventions are recommendations rather than strict rules, they represent patterns that have proven effective across many Sui projects. They help create consistency across the ecosystem and make code easier to understand and maintain. ## Organization principles @@ -148,8 +132,6 @@ const E_INVALID_NAME: u64 = 0; ### Structs -To account for future features, declare structs with the `public` modifier. - Always declare struct abilities in this order: `key`, `copy`, `drop`, `store`. Do not use 'potato' in the name of structs. The lack of abilities define it as a potato pattern. @@ -418,7 +400,7 @@ public fun set(account: &mut Account, _: &Admin, new_name: String) { ## Documentation -There is nothing more pleasant than a well-written and well-documented codebase. If some support the idea that clean code is self-documenting, well-documented code is self-explanatory. +There is nothing more pleasant than a well-written and well-documented codebase. While some argue that clean code is self-documenting, well-documented code is self-explanatory. ### Comments