[{"data":1,"prerenderedAt":20},["ShallowReactive",2],{"blog-detail:sui-objects-security-principles-and-best-practices":3},{"category":4,"article":5},null,{"slug":6,"category":7,"categoryLabel":8,"subcategoryLabel":9,"authorName":10,"title":11,"excerpt":12,"contentHtml":13,"coverUrl":4,"seoTitle":11,"seoDescription":12,"seoImageUrl":14,"publishedAt":15,"updatedAt":16,"readTime":17,"cover":4,"hasCover":18,"coverAlt":19},"sui-objects-security-principles-and-best-practices","research","Research","Industry Research","MoveBit","Sui Objects-Security Principles and Best Practices","Sui Move introduced Object, which introduced some changes in the security model of Move type, but potentially also brought new risks. This article will analyze the security model of Sui Move and provide some considerations for developers to avoid security issues","\u003Ch2 id=\"Introduction\">\u003Cstrong>Introduction\u003C\u002Fstrong>\u003C\u002Fh2>\n\u003Cp>Sui Move introduced Object, which introduced some changes in the security model of Move type, but potentially also brought new risks. This article will analyze the security model of Sui Move and provide some considerations for developers to avoid security issues\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"\u002Fmedia\u002Fmovebit\u002F2023\u002F07\u002F15abfefd8e7665cacb51f4cf2476a5ee.jpg\" alt=\"SO\">\u003C\u002Fp>\n\u003Ch2 id=\"The-Security-Model-of-Move\">\u003Cstrong>The Security Model of Move:\u003C\u002Fstrong>\u003C\u002Fh2>\n\u003Cp>As we know, Sui Move is a “dialect” of the Move programming language, so before we delve into the unique security model of Sui Move, it is best to first understand the security model of the original Move language to better comprehend the succeeding content. The security model of Sui Move mainly includes the following aspects:\u003C\u002Fp>\n\u003Ch3 id=\"Struct-Visibility\">\u003Cstrong>Struct Visibility\u003C\u002Fstrong>\u003C\u002Fh3>\n\u003Cp>In Move, the structure is a custom type defined using the \u003Ccode>struct\u003C\u002Fcode> keyword.\u003C\u002Fp>\n\u003Cp>Unlike C\u002FC++, structures in Move are module private, in the sense that their members\u002Ffields can only be accessed in the module that defines them, even though the structs are always public. This design essentially made every structs “opaque” objects to the outside worlds and other modules can only operate on them with methods that the author has defined. It helps prevent coupling between modules, maintains modularity and independence of the code, and makes it easier to achieve information isolation and functional separation between modules.\u003C\u002Fp>\n\u003Cp>In addition, there is another feature of structures in Move, which is that they can be used to define resource types. Resource type is a special type in Move used to represent ownership of resources.\u003C\u002Fp>\n\u003Cp>By defining resources as structure types, it is possible to make the behavior of the object types more clear and controllable, and the state of the resource can be accessed and modified through the members of the structure. This design enables Move to better support the development of blockchain applications because resource types can be used to represent important data such as digital assets and contracts on the blockchain.\u003C\u002Fp>\n\u003Cp>Here’s a specific example: Let’s assume that on the blockchain, there is a type of digital asset called Token, which is used to represent a digital asset. This Token type can be defined as a structure with members for the Token’s value and owner, which is the address of the token’s owner. In Move, this structure could be defined in the following way:\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\">module Token {\n    struct Token {\n        id: guid::UID,\n        value: u64,\n        ...\n    }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>In the above code, the \u003Ccode>Token\u003C\u002Fcode> structure is defined as a module-private type that can only be accessed and modified within the \u003Ccode>Token\u003C\u002Fcode> module. This means that the members of the \u003Ccode>Token\u003C\u002Fcode> structure cannot be directly accessed in other modules.\u003C\u002Fp>\n\u003Cp>This design ensures the privacy of the token type, preventing third parties from directly accessing and modifying the token’s value and other information.\u003C\u002Fp>\n\u003Ch3 id=\"Ability-of-Struct\">\u003Cstrong>Ability of Struct\u003C\u002Fstrong>\u003C\u002Fh3>\n\u003Cp>Move’s type system introduces the concept of Abilities to represent the behavior of types. In Move, each type has one or more Abilities that define whether the values of that type can be copied, moved, used as keys, or stored.\u003C\u002Fp>\n\u003Cp>Here are some specific details about several Abilities in Move:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\n\u003Cp>\u003Ccode>copy\u003C\u002Fcode>: The value of this type can be copied. Usually, basic types (such as integers and booleans) have \u003Ccode>copy\u003C\u002Fcode> ability, while more complex types (such as structures and resources) need to be considered on a case-by-case basis.\u003C\u002Fp>\n\u003Cp>  For example, in the above example, Token is an asset type that should not be duplicated, so it should not have \u003Ccode>copy\u003C\u002Fcode> ability. However, for some structures that are used solely to represent data, such as a u64 id wrapper struct, it can have \u003Ccode>copy\u003C\u002Fcode> ability.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Ccode>drop\u003C\u002Fcode>: The value of this type can be automatically destroyed at the end of the scope. The \u003Ccode>drop\u003C\u002Fcode> ability is usually used for resource types. Types with \u003Ccode>drop\u003C\u002Fcode> ability will have their values automatically destroyed at the end of their scope.\u003C\u002Fp>\n\u003Cp>  For types without \u003Ccode>drop\u003C\u002Fcode> ability, their values will not be automatically destroyed at the end of their scope, thus causing a compilation error. The module that owns the type should provide a manual destruction method or the resource can be moved elsewhere. In the case of Token, it should not be automatically destroyed, so it should not have \u003Ccode>drop\u003C\u002Fcode> Ability.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Ccode>key\u003C\u002Fcode>: In the original Move, the values in the global storage must have \u003Ccode>key\u003C\u002Fcode> ability, indicating that the value can be used as the key for global storage.\u003C\u002Fp>\n\u003Cp>  Formally, the global storage in Move is a mapping of address A x type T -&gt; value V, where type T must have \u003Ccode>key\u003C\u002Fcode> ability. However, in Sui Move, there have been some improvements to the use of global storage, so the role of Key ability is slightly different, read the sections below for detailed information.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Ccode>store\u003C\u002Fcode>: Refers to the ability of a type’s value to be stored ( for example, in another struct).\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"The-Security-Model-of-Sui-Object\">\u003Cstrong>The Security Model of Sui Object\u003C\u002Fstrong>\u003C\u002Fh2>\n\u003Cp>In original Move, there’s a global storage where you can store and access structs with \u003Ccode>key\u003C\u002Fcode> ability with its type and an address as index, via builtin functions \u003Ccode>move_to&lt;T&gt;(addr: address, value: T),  move_from&lt;T&gt;(addr: address) -&gt; T, etc\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>In contrast, in Sui Move, we no longer have these concepts.\u003C\u002Fp>\n\u003Cp>As mentioned earlier, the \u003Ccode>key\u003C\u002Fcode> ability in the original Move is used to indicate that a type’s value can be used as the \u003Ccode>key\u003C\u002Fcode> for global storage. Because in Sui Move, there isn’t a global storage. So the role of the \u003Ccode>key\u003C\u002Fcode> ability has changed. Simply put, in Sui Move, the \u003Ccode>key\u003C\u002Fcode> ability indicates that values of this type can be held in global storage and requires that the first field of the type is a \u003Ccode>id: sui::object::UID\u003C\u002Fcode>, which holds the object’s key in storage. This is done because Sui Move extends the bytecode verifier in Move, which is explained in more detail below.\u003C\u002Fp>\n\u003Ch3 id=\"Extensions-of-the-verifier-in-Move\">\u003Cstrong>Extensions of the verifier in Move\u003C\u002Fstrong>\u003C\u002Fh3>\n\u003Cp>The extensions of the verifier in Move primarily include the following types of checks：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\n\u003Cp>\u003Cstrong>Check of structs with key ability\u003C\u002Fstrong>:\u003Cbr>As mentioned above, if a struct has \u003Ccode>key\u003C\u002Fcode> ability, it is considered a Sui Object. The Move verifier will check if the first field of all structs with key ability is named “id” and its type is \u003Ccode>sui::object::UID\u003C\u002Fcode>.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Check of global storage\u003C\u002Fstrong>:\u003Cbr>Check of global storage: As mentioned earlier, the original Move global storage model is not applicable in Sui Move. Therefore, the Move verifier will check whether all modules have used global storage. If used, an error will be reported.”\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>ID leak check\u003C\u002Fstrong>: ID leak verifier ensures that only “fresh” IDs (e.g. generated by \u003Ccode>sui::object::new\u003C\u002Fcode>) are used to fill an object’s \u003Ccode>id: UID\u003C\u002Fcode> field. Attempting to re-use an existing object’s field when creating a fresh object counts as an “ID Leak”, and will fail this verification pass. Since unpacking is the only bytecode that can extract the ID field from a Sui object. The flow of this value can be traced from this instruction to ensure that it never leaks outside the function. There are four possible ways in which it may occur:\u003C\u002Fp>\n\u003Cp>  a. The ID is returned;\u003C\u002Fp>\n\u003Cp>  b. Written to a mutable reference;\u003C\u002Fp>\n\u003Cp>  c. Added to a vector;\u003C\u002Fp>\n\u003Cp>  d. Passed as an argument to a function.\u003C\u002Fp>\n\u003Cp> These cases will be checked by the verifier, and an error will be reported if ID leak is found.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Generic parameter privacy check\u003C\u002Fstrong>: There are two categories of transfer functions in Sui: private ones such as \u003Ccode>sui::transfer::transfer\u003C\u002Fcode> and their public variants (i.e., \u003Ccode>sui::transfer::public_transfer\u003C\u002Fcode>). For the private ones, the parameter T must be defined in the same module where the call initiation occurs, and the compiler requires T to have \u003Ccode>key\u003C\u002Fcode> ability. For the public ones, T can be defined in different modules and must have the \u003Ccode>key &amp; store\u003C\u002Fcode> abilities.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Entry check\u003C\u002Fstrong>: Check the validity of module initialization and transaction entry points.\u003C\u002Fp>\n\u003Cp>  ► For module initialization functions:\u003C\u002Fp>\n\u003Cp>  ☐ The existence of the function is optional. In other words, a module can have no initialization function.\u003C\u002Fp>\n\u003Cp>  ☐ If there is one, the function must have the name \u003Ccode>init\u003C\u002Fcode> and must be private.\u003C\u002Fp>\n\u003Cp>  ☐ The function can have up to two parameters: an optional one-time witness parameter and a required \u003Ccode>TxContext\u003C\u002Fcode> reference parameter.\u003C\u002Fp>\n\u003Cp>  ►  For transaction entry points:\u003C\u002Fp>\n\u003Cp>  ☐ The function must have an \u003Ccode>entry\u003C\u002Fcode> attribute.\u003C\u002Fp>\n\u003Cp>  ☐ It can have a \u003Ccode>TxContext\u003C\u002Fcode> reference parameter, and it must be the last parameter.\u003C\u002Fp>\n\u003Cp>  ☐ The function cannot have any return values.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>One-time witness check\u003C\u002Fstrong>: If a module defines a struct type with the same name as the module name in uppercase, this struct type is considered a one-time witness. The struct type must meet the following conditions:\u003C\u002Fp>\n\u003Cp>  ☐ Its ability can only be \u003Ccode>drop\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>  ☐ It cannot be generic, that is, it does not involve type parameters.\u003C\u002Fp>\n\u003Cp>  ☐ Its unique instance is passed as a parameter to the module initialization function.\u003C\u002Fp>\n\u003Cp>  ☐ It is not instantiated anywhere in its defining module.\u003C\u002Fp>\n\u003Cp>  ☐ At source level, the compiler will generate a boolean field for empty struct definition. But at the bytecode level, the verifier cannot presume that it is nonempty. So it checks that the struct has only one field of any named Boolean type.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"The-Ownership-Model-of-Sui-Object\">\u003Cstrong>The Ownership Model of Sui Object\u003C\u002Fstrong>\u003C\u002Fh2>\n\u003Cp>All Sui Objects have an owner, and there are four ownership relationships:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\n\u003Cp>\u003Cstrong>Owned by an address\u003C\u002Fstrong>: When an object is created, an address can be specified, and the object will be transferred to that address. When using an object as a parameter, regardless of whether the object is passed by value or by mutable or immutable reference, the signatures of all addresses need to be available. This ensures that only the object owner can modify the object.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Owned by an object\u003C\u002Fstrong>: The owner of a Sui Object can also be another object. However, please note that this is different from a struct containing another struct. For more detailed information, please refer to the \u003Ca target=\"_blank\" rel=\"noopener\" href=\"https:\u002F\u002Fdocs.sui.io\u002Flearn\u002Fobjects#owned-by-another-object\" title=\"Move ecosystem security,blockchain audit services\">Sui official documentation\u003C\u002Fa>.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Immutable Object\u003C\u002Fstrong>: Immutable objects have no owner and cannot be modified. Therefore, only immutable references can be used to pass them as parameters. A common example is the Sui Move package, which cannot be modified after publication.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Shared Object\u003C\u002Fstrong>:  Unlike exclusive (address or object) objects, shared objects can be accessed by anyone on the network.\u003C\u002Fp>\n\u003Cp>  The extended features and accessibility of such objects need to ensure their security by designing corresponding check mechanisms. Therefore, developers need to carefully consider the design of such objects. You can refer to the use cases in \u003Ca target=\"_blank\" rel=\"noopener\" href=\"https:\u002F\u002Fexamples.sui.io\u002Fbasics\u002Fshared-object.html\" title=\"crypto audit company,movement ecosystem,blockchain audit companies\">Sui Examples\u003C\u002Fa> to understand how to use shared objects.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"Common-Patterns-to-Watch-out-when-Programming-in-Sui-Move\">\u003Cstrong>Common Patterns to Watch out when Programming in Sui Move\u003C\u002Fstrong>\u003C\u002Fh2>\n\u003Cp>Whether you are building DeFi or other types of protocols on Sui, it is essential to conduct rigorous security audits and comprehensive code testing during the development process or before launching on the Mainnet. The following are some common patterns for developers to keep in mind.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\n\u003Ch3 id=\"Avoid-giving-excessive-abilities-to-structs\">\u003Cstrong>Avoid giving excessive abilities to structs\u003C\u002Fstrong>\u003C\u002Fh3>\n\u003Cp>  When creating custom structs using Move, it is advisable to avoid using overly generic abilities. In other words, we should consider the object’s usage scenario in advance and assign it “minimal” abilities to prevent unexpected operations.\u003C\u002Fp>\n\u003Cp>  Excessive abilities to event struct\u003C\u002Fp>\n\u003Cp>  According to the parameter ability requirements in sui::event, only \u003Ccode>copy\u003C\u002Fcode> and \u003Ccode>drop\u003C\u002Fcode> abilities are necessary.\u003C\u002Fp>\n  \u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\">module sui::event {\n    public native fun emit&lt;T: copy + drop&gt;(event: T);\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>  Let’s take a look at the following struct, which is used as a parameter in events to store some key information. Therefore, the \u003Ccode>store\u003C\u002Fcode> ability is redundant in this context.\u003C\u002Fp>\n  \u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\">  \u002F\u002F\u002F Emitted when a new pool is created\n  struct PoolCreated has copy, store, drop {\n      \u002F\u002F\u002F object ID of the newly created pool\n      pool_id: ID,\n      token_x_name: TypeName,\n      token_y_name: TypeName,\n  }\n\n\u002F\u002F\u002F Emit the PoolCreated event\nevent::emit(PoolCreated { … });\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>  \u003Cstrong>Suggestion\u003C\u002Fstrong>: By removing the \u003Ccode>store\u003C\u002Fcode> ability, we can bring the struct in line with best practices.\u003C\u002Fp>\n  \u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\">\u002F\u002F\u002F Emitted when a new pool is created\nstruct PoolCreated has copy, drop {\n    \u002F\u002F\u002F object ID of the newly created pool\n    pool_id: ID,\n    token_x_name: TypeName,\n    token_y_name: TypeName,\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>  Excessive abilities to flashloan struct\u003C\u002Fp>\n\u003Cp>  The following code snippet is from a smart contract implementing a flash loan feature. The \u003Ccode>loan\u003C\u002Fcode> method allows users to borrow a large amount of assets without collateral, while the \u003Ccode>repay\u003C\u002Fcode> method allows users to repay the loan. The implementation principle of flash loans in Sui involves defining a struct with no abilities. This struct object is created in one method and destroyed in another method, and this process must be completed within the same transaction.\u003C\u002Fp>\n\u003Cp>  In the code below, we can see that the \u003Ccode>Receipt\u003C\u002Fcode> struct is mistakenly given the abilities of \u003Ccode>store\u003C\u002Fcode> and \u003Ccode>drop\u003C\u002Fcode>. This error allows the repay method to be bypassed, and as a result, the assets borrowed by the user may never be returned.\u003C\u002Fp>\n  \u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\">\u002F\u002F\u002F A shared object offering flash loans to any buyer willing to pay `fee`.\nstruct FlashLender&lt;phantom T&gt; has key {\n    id: UID,\n    \u002F\u002F\u002F Coins available to be lent to prospective borrowers\n    to_lend: Balance&lt;T&gt;,\n    \u002F\u002F\u002F Number of `Coin&lt;T&gt;`'s that will be charged for the loan.\n    \u002F\u002F\u002F In practice, this would probably be a percentage, but\n    \u002F\u002F\u002F we use a flat fee here for simplicity.\n    fee: u64,\n}\n\n\nstruct Receipt&lt;phantom T&gt; has store, drop {\n    \u002F\u002F\u002F ID of the flash lender object the debt holder borrowed from\n    flash_lender_id: ID,\n    \u002F\u002F\u002F Total amount of funds the borrower must repay: amount borrowed + the fee\n    repay_amount: u64\n}\n\n\n\u002F\u002F\u002F Request a loan of `amount` from `lender`. The returned `Receipt&lt;T&gt;` \"hot potato\" ensures\n\u002F\u002F\u002F that the borrower will call `repay(lender, ...)` later on in this tx.\n\u002F\u002F\u002F Aborts if `amount` is greater that the amount that `lender` has available for lending.\npublic fun loan&lt;T&gt;(\n    self: &amp;mut FlashLender&lt;T&gt;, amount: u64, ctx: &amp;mut TxContext\n): (Coin&lt;T&gt;, Receipt&lt;T&gt;) {\n\nlet to_lend = &amp;mut self.to_lend;\n    assert!(balance::value(to_lend) &gt;= amount, ELoanTooLarge);\n    let loan = coin::take(to_lend, amount, ctx);\n    let repay_amount = amount + self.fee;\n    let receipt = Receipt { flash_lender_id: object::id(self), repay_amount };\n\n\n    (loan, receipt)\n}\n\n\n\u002F\u002F\u002F Repay the loan recorded by `receipt` to `lender` with `payment`.\n\u002F\u002F\u002F Aborts if the repayment amount is incorrect or `lender` is not the `FlashLender`\n\u002F\u002F\u002F that issued the original loan.\npublic fun repay&lt;T&gt;(self: &amp;mut FlashLender&lt;T&gt;, payment: Coin&lt;T&gt;, receipt: Receipt&lt;T&gt;) {\n    let Receipt { flash_lender_id, repay_amount } = receipt;\n    assert!(object::id(self) == flash_lender_id, ERepayToWrongLender);\n    assert!(coin::value(&amp;payment) == repay_amount, EInvalidRepaymentAmount);\n\n\n    coin::put(&amp;mut self.to_lend, payment)\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>  Solution: Remove the \u003Ccode>store\u003C\u002Fcode> and \u003Ccode>drop\u003C\u002Fcode> ability from the struct \u003Ccode>Receipt\u003C\u002Fcode>.\u003C\u002Fp>\n  \u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\">struct Receipt&lt;phantom T&gt; {\n    \u002F\u002F\u002F ID of the flash lender object the debt holder borrowed from\n    flash_lender_id: ID,\n    \u002F\u002F\u002F Total amount of funds the borrower must repay: amount borrowed + the fee\n    repay_amount: u64\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n\u003Cli>\n\u003Ch3 id=\"The-metadata-in-Coin-should-be-frozen\">\u003Cstrong>The metadata in Coin should be frozen\u003C\u002Fstrong>\u003C\u002Fh3>\n\u003Cp>  In Sui, except for certain NFT types that aim to implement creative functionalities, the metadata of all coins, should be frozen. If the metadata is not frozen, it can lead to confusion for users if the administrator modifies that data.\u003C\u002Fp>\n\u003Cp>  The following code creates a T coin by calling the Sui framework function \u003Ccode>coin::create_currency()\u003C\u002Fcode>. The second return value of this function is of type \u003Ccode>coin::CoinMetadata&lt;T&gt;\u003C\u002Fcode>, which stores the metadata of the coin. In this code, the metadata of the T coin is not frozen, allowing the person with the \u003Ccode>treasury cap\u003C\u002Fcode> to freely modify information such as the name, symbol, and other details.\u003C\u002Fp>\n  \u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\"> fun init(witness: T, ctx: &amp;mut TxContext) {\n    let (treasury_cap, metadata) = coin::create_currency&lt;T&gt;(witness,\n        9,\n        b\"Token\",\n        b\"Token\",\n        b\"It is the native token for xxx\",\n        option::none(),\n        ctx);\n    transfer::public_transfer(treasury_cap, tx_context::sender(ctx))\n    transfer::public_share_object(metadata);\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>  \u003Cstrong>Suggestion\u003C\u002Fstrong>: The fix for this issue is quite simple. You just need to Freeze it instead of sharing.\u003C\u002Fp>\n  \u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\">transfer::public_freeze_object(metadata);\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n\u003Cli>\n\u003Ch3 id=\"Verify-the-proper-usage-of-generics\">\u003Cstrong>Verify the proper usage of generics\u003C\u002Fstrong>\u003C\u002Fh3>\n\u003Cp>  In terms of public functions in Move, generic types are the most common type parameter. Although the bytecode verifier performs certain security checks, We still need to be cautious about the pitfalls that generics can introduce. These pitfalls can potentially lead to significant losses if not handled properly.\u003C\u002Fp>\n\u003Cp>  Here is a code snippet from a gameFi platform that represents the purchasing of packs. Its main functionality is to allow users with signed information to use a certain quantity of coins to buy packs.  In the signature verification process, there is no check on the correlation between the incoming COIN type and the order. This means that any type of Coin can be used for the operation. Users have the option to purchase Packs using low-value tokens, which may result in significant losses for the project.\u003C\u002Fp>\n  \u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\">public entry fun buy_pack&lt;COIN&gt;(game: &amp;mut Game, paid: Coin&lt;COIN&gt;, pack_id: u8, timestamp: u64, signature: vector&lt;u8&gt;, ctx: &amp;mut TxContext) {\n   let token_amount = coin::value(&amp;paid);\n   let player = tx_context::sender(ctx);\n   let message = PackMessage {\n       prefix: BUY_PACK_PREFIX,\n       token_amount: token_amount,\n       pack_id: pack_id,\n       game: object::id_address(game),\n       owner: player,\n       timestamp: timestamp,\n   };\n   let message_bytes = bcs::to_bytes(&amp;message);\n   let recovered_address = verifier::ecrecover_to_eth_address(signature, message_bytes);\n   assert!(game.verifier == recovered_address, ESigFail);\n }\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>  \u003Cstrong>Solution\u003C\u002Fstrong>: One suggestion is including the token type in the signature verification.\u003C\u002Fp>\n\u003Cp>  Here is a simple example of the fixed code:\u003C\u002Fp>\n  \u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\"> let message = PackMessage {\n     prefix: BUY_PACK_PREFIX,\n     token_amount: token_amount,\n  coin_type: type_name::get&lt;COIN&gt;(), \u002F\u002F include the coin type\n     pack_id: pack_id,\n     game: object::id_address(game),\n     owner: player,\n     timestamp: timestamp,\n};\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n\u003Cli>\n\u003Ch3 id=\"Avoid-using-non-global-variables-as-parameters-in-entry-functions\">\u003Cstrong>Avoid using non-global variables as parameters in entry functions\u003C\u002Fstrong>\u003C\u002Fh3>\n\u003Cp>  In Sui Move, for global object-type parameters in entry functions, the code does not use \u003Ccode>borrow_global_mut\u003C\u002Fcode> to obtain global resources. Instead, when making a Sui Move function call from CLI or SDK, the caller passes in the object ID, and Sui automatically retrieves the object from the global storage. This approach enhances development convenience, but it can potentially lead to certain functionalities becoming inaccessible or unavailable.\u003C\u002Fp>\n\u003Cp>  For example, in the \u003Ccode>get_value\u003C\u002Fcode> function below, it accepts a \u003Ccode>Balance\u003C\u002Fcode> type parameter, this code compiles successfully. However, after deployment, this function cannot be called properly through the CLI or SDK, resulting in the inability to use the business function.\u003C\u002Fp>\n  \u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\"> struct Balance has store {\n     value: u64\n}\npublic entry fun get_value(balance : &amp;Balance): u64 {\n     balance.value\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>  Solution: Modify \u003Ccode>Balance\u003C\u002Fcode> struct, add an \u003Ccode>id\u003C\u002Fcode> field of \u003Ccode>UID\u003C\u002Fcode> type, make it to be a Sui Move Object Struct, and add a new function to create \u003Ccode>Balance\u003C\u002Fcode> objects.\u003C\u002Fp>\n  \u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\"> struct Balance has store {\n     id: UID,\n     value: u64\n }\npublic entry fun new(val : u64, ctx: &amp;mut TxContext){\n     transfer::transfer(Balance {\n         id: object::new(ctx),\n         value: val\n   }, tx_context::sender(ctx));\n }\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n\u003Cli>\n\u003Ch3 id=\"Proper-usage-of-Share-Object\">\u003Cstrong>Proper usage of Share Object\u003C\u002Fstrong>\u003C\u002Fh3>\n\u003Cp>  Shared object is an object that is shared using the \u003Ccode>sui::transfer::share_object\u003C\u002Fcode> or \u003Ccode>sui::transfer::public_share_object\u003C\u002Fcode> function and is accessible to everyone. By sharing objects, multiple accounts can read from and modify the object without duplicating it. This sharing mechanism is useful when implementing complex protocols and contracts.\u003C\u002Fp>\n\u003Cp>  It is crucial to use Share Objects correctly; otherwise, it can lead to disastrous consequences.\u003C\u002Fp>\n\u003Cp>  Let’s take a look at the code snippet below, the \u003Ccode>AdminCap\u003C\u002Fcode>struct represents an administrative capability and contains a single field \u003Ccode>id\u003C\u002Fcode> of type \u003Ccode>UID\u003C\u002Fcode>.  With the \u003Ccode>AdminCap\u003C\u002Fcode>, users can invoke the \u003Ccode>set_pool_fee\u003C\u002Fcode> to update the the fee of the pool. Improper sharing of \u003Ccode>AdminCap\u003C\u002Fcode> allows anyone with access to the \u003Ccode>AdminCap\u003C\u002Fcode> object to modify the pool fee, which is unintended and can have adverse effects.\u003C\u002Fp>\n  \u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\"> struct AdminCap has key {\n     id: UID\n }\n\n\nfun init(ctx: &amp;mut TxContext) {\n     let admin_cap = AdminCap {\n         id: object::new(ctx)\n     };\n     transfer::share_object(admin_cap);\n }\n\n\npublic entry fun set_pool_fee(\n     _: &amp;AdminCap,\n     pool: &amp;Pool,\n     new_fee: u64) {\n     pool.fee = fee;\n     event::emit(PoolFeeUpdate {\n         new_fee: fee\n     });\n }\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>  \u003Cstrong>Solution\u003C\u002Fstrong>: Transfer the \u003Ccode>AdminCap\u003C\u002Fcode> to the creator instead of sharing it.\u003C\u002Fp>\n  \u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\">fun init(ctx: &amp;mut TxContext) {\n    let admin_cap = AdminCap {\n        id: object::new(ctx)\n    };\n    transfer::public_transfer(admin_cap, tx_context::sender(ctx));\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n\u003Cli>\n\u003Ch3 id=\"Validate-the-consistency-of-multiple-related-parameters\">\u003Cstrong>Validate the consistency of multiple related parameters\u003C\u002Fstrong>\u003C\u002Fh3>\n\u003Cp>  For complex smart contracts, we may use multiple shared objects to store different sets of data, with users accessing the corresponding objects as needed. In some cases, there may be certain relationships or dependencies between these objects. It is crucial to validate these relationships when using the objects to avoid data inconsistency and potential vulnerabilities to attacks.\u003C\u002Fp>\n\u003Cp>  Here is a partial code snippet of a Launchpad contract. The \u003Ccode>create_launchpad\u003C\u002Fcode> function is used to create a new Launchpad along with its corresponding whitelist. The \u003Ccode>invest\u003C\u002Fcode> function allows whitelisted users to purchase TR tokens using Coin T1. However, in the \u003Ccode>invest\u003C\u002Fcode> function, there is no validation to check if the provided whitelist is consistent with the Launchpad. This can result in users from other Launchpads’ whitelists participating in this Launchpad as well.\u003C\u002Fp>\n  \u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\">public entry fun create_launchpad&lt;TI, TR&gt;(\n    _: &amp;mut AdminCap,\n    launchpadID: String,\n    target_amount: u64,\n    tokens_to_sell: u64,\n    receiver: address,\n    ctx: &amp;mut TxContext\n    ) {\n        \u002F\u002F Create launchpad and corresponding whitelist\n        let launchpad = new&lt;TI, TR&gt;(\nlaunchpadID,\n            target_amount,\n            tokens_to_sell,\n            receiver,\n            ctx\n        );\n        let whitelist = Whitelist {\n            id: object::new(ctx),\n            launchpad_id: launchpadID,\n            allowed_addresses: vector::empty&lt;address&gt;()\n        }\n\n\n        \u002F\u002F Make objects shared to be accessed for everyone\n        transfer::public_share_object(launchpad);\n        transfer::public_share_object(whitelist);\n }\n\n\npublic entry fun invest&lt;TI, TR&gt;(\n    launchpad: &amp;mut Launchpad&lt;TI, TR&gt;,\n    whitelist: &amp;Whitelist,\n    coin: &amp;mut coin::Coin&lt;TI&gt;,\n    amount: u64,\n    ctx: &amp;mut TxContext\n ) {\n    \u002F\u002F Verify if sender is present in the whitelist\n    assert!(whitelist::contains(whitelist, tx_context::sender(ctx)), ENotInWhitelist);\n\n\n    let coins_to_invest = coin::split(coin, amount, ctx);\n    let invest_balance = coin::into_balance(coins_to_invest);\n    balance::join(&amp;mut launchpad.investment_vault, invest_balance);\n\n\n    let invest_cert = InvestCertificate{\n        id: object::new(ctx),\n        launchpadID: get_id(launchpad),\n        deposit: amount\n    };\n    launchpad.invested_amount = launchpad.invested_amount + amount;\n    launchpad.investors_count = launchpad.investors_count + 1;\n    transfer::public_transfer(invest_cert, tx_context::sender(ctx));\n\n\n    event::emit(InvestedInLaunchpad{\nlaunchpad_id: get_id(launchpad),\n        investor: tx_context::sender(ctx),\n        amount: amount\n    });\n }\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>  \u003Cstrong>Solution\u003C\u002Fstrong>: Add consistency validation to ensure the provided whitelist matches the Launchpad.\u003C\u002Fp>\n\u003Cpre class=\"line-numbers language-Rust\" data-language=\"Rust\">\u003Ccode class=\"language-Rust\">public entry fun invest&lt;TI, TR&gt;(\n     launchpad: &amp;mut Launchpad&lt;TI, TR&gt;,\n     whitelist: &amp;Whitelist,\n     coin: &amp;mut coin::Coin&lt;TI&gt;,\n     amount: u64,\n     ctx: &amp;mut TxContext\n ) {\n     \u002F\u002F Verify if sender is present in the whitelist\n     assert!(whitelist::contains(whitelist, tx_context::sender(ctx)), ENotInWhitelist);\n\n      \u002F\u002FVerify if the whitelist matches the launchpad.\n      assert!(whitelist.launchpad_id == launchpad.id, EInconsistent)\n   …\n  }\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"Conclusion\">\u003Cstrong>Conclusion\u003C\u002Fstrong>\u003C\u002Fh2>\n\u003Cp>The introduction of Objects in Sui Move has resulted in significant changes to the original Move programming method. However, by understanding the Sui Move security model and mastering the operation essentials of Sui Objects, developers can better utilize these features while avoiding security issues.\u003C\u002Fp>\n\u003Ch2 id=\"About-MoveBit\">\u003Cstrong>About MoveBit\u003C\u002Fstrong>\u003C\u002Fh2>\n\u003Cp>MoveBit is a blockchain security company focused on the Move Ecosystem by pioneering the use of cutting-edge Formal Verification. The team consists of security professionals from academia and enterprise with 10 years of security experience. they were one of the earliest contributors to the Move ecosystem, working with Move developers to set the standard for secure Move applications and make the Move ecosystem the most secure Web3 destination.\u003C\u002Fp>\n\u003Cp>Twitter: \u003Ca target=\"_blank\" rel=\"noopener\" href=\"https:\u002F\u002Ftwitter.com\u002FMoveBit_\">https:\u002F\u002Ftwitter.com\u002FMoveBit_\u003C\u002Fa>\u003C\u002Fp>\n\u003Cp>Medium: \u003Ca target=\"_blank\" rel=\"noopener\" href=\"https:\u002F\u002Fmovebit.medium.com\u002F\">https:\u002F\u002Fmovebit.medium.com\u002F\u003C\u002Fa>\u003C\u002Fp>\n\u003Cp>Github: \u003Ca target=\"_blank\" rel=\"noopener\" href=\"https:\u002F\u002Fgithub.com\u002Fmovebit\">https:\u002F\u002Fgithub.com\u002Fmovebit\u003C\u002Fa>\u003C\u002Fp>\n\u003Cp>Discord: \u003Ca target=\"_blank\" rel=\"noopener\" href=\"https:\u002F\u002Fdiscord.com\u002Finvite\u002F7wM8VU9Gyj\">https:\u002F\u002Fdiscord.com\u002Finvite\u002F7wM8VU9Gyj\u003C\u002Fa>\u003C\u002Fp>","\u002Fmedia\u002Fmovebit\u002F2022\u002F09\u002F2b36c3a7da22b02f60e79524b16e5664.png","2023-07-07T03:15:00Z","2026-08-11T18:05:56Z","19 min read",false,"Sui Objects-Security Principles and Best Practices cover image",1792635794991]