Sui.

Допис

Діліться своїми знаннями.

Owen.
Owen1185
Jun 11, 2025
Питання та відповіді експертів

Як оновити ключ продавця в ObjectTable, коли він змінюється в структурі?

Привіт усім, я тільки починаю писати смарт-контракти і працюю над своїм першим проектом. Я хотів би допомогти з проблемою, на якій я застряг.

Поки що я створив Merchantструктуру, яка виглядає так: -id: унікальний ідентифікатор (UID) -owner: адреса продавця -key: Рядок, що використовується як унікальний ключ -balance: u64, що представляє їх баланс

Я також зробив структуру MerchantRegistryдля управління всіма продавцями: -id: інший UID -merchant_to_address: ObjectTable<address, Merchant>відображення адрес для продавців -merchant_to_key: ObjectTable<String, Merchant>відображення ключів для продавців

Я хочу мати можливість шукати продавця або за їхньоюадресу, або за їхнімключа.

Коли користувач оновлює свій ключ Merchantвсередині структури, зміна автоматично не оновлює ключ у merchant_to_keyтаблиці. Це означає, що старий ключ все ще вказує на купця, який ламає речі.

Я намагався видалити запис з таблиці та вставити його назад за допомогою нового ключа, але я постійно стикаюся з такими помилками, як:

«Не можна ігнорувати значення без можливості падіння»

Я впевнений, що це помилка початківців, але мені ніде не вдалося знайти чіткого пояснення чи рішення. Чи є правильний спосіб обробки оновлення ключа як у структурі, так і в таблиці пошуку?

  • Sui
  • Move
5
6
Поділитися
Коментарі
.

Відповіді

6
HaGiang.
Jul 23 2025, 15:44

У Sui Move об'єкти за замовчуванням є «ресурсами», тобто їх не можна дублювати або викинути випадково. Це забезпечується за допомогою типової системи, де структури або мають здатність падати (тобто їх можна відкинути, якщо вони виходять за рамки), або ні.

Ваша структура Merchant, ймовірно, не має можливості скидання, оскільки вона містить UID. UID є унікальними ідентифікаторами для об'єктів на Sui, і вони призначені для запобігання випадковому видаленню або дублювання цих об'єктів. Якщо об'єкт містить UID, його зазвичай не можна скинути.

Коли ви намагаєтеся видалити запис з ObjectTable, функція remove повертає значення (у цьому випадку об'єкт Merchant). Якщо ви не присвоїте це повернуте значення змінній або не передаєте його іншій функції, яка її споживає, Move передбачає, що ви намагаєтеся «ігнорувати» або «скинути» його. Оскільки ваша структура Merchant не має можливості скидання, ця дія заборонена, що призводить до помилки «Не можна ігнорувати значення без можливості скидання».

Основна проблема полягає в тому, що зміна поля всередині об'єкта (наприклад, merchant.key = new_key;) не автоматично оновлює ключі у вашій ObjectTable. Об'єктна таблиця зберігає об'єкт на основі ключа, за допомогою якого він був доданий. Отже, якщо внутрішній ключ змінюється, таблиця все одно знає лише про старий ключ.

module your_address::your_module_name {

    use sui::object::{Self, UID};
    use sui::tx_context::{Self, TxContext};
    use sui::string::{String, utf8};
    use sui::object_table::{Self, ObjectTable}; // Import ObjectTable

    // Define your Merchant struct
    public struct Merchant has key, store {
        id: UID,
        owner: address,
        key: String, // This is the key you want to update
        balance: u64,
    }

    // Define your MerchantRegistry struct
    public struct MerchantRegistry has key, store {
        id: UID,
        merchant_to_address: ObjectTable<address, Merchant>, // Mapping addresses to merchants
        merchant_to_key: ObjectTable<String, Merchant>,     // Mapping keys to merchants
    }

    // You'll likely have an `init` function to create the registry
    fun init(ctx: &mut TxContext) {
        let registry = MerchantRegistry {
            id: object::new(ctx),
            merchant_to_address: object_table::new(ctx),
            merchant_to_key: object_table::new(ctx),
        };
        // Transfer ownership of the registry, or make it shared if applicable
        sui::transfer::transfer(registry, tx_context::sender(ctx));
    }

    // Function to add a new merchant (for context)
    public entry fun add_merchant(
        registry: &mut MerchantRegistry,
        merchant_owner: address,
        merchant_key: vector<u8>, // Using vector<u8> for input, convert to String
        ctx: &mut TxContext
    ) {
        let merchant = Merchant {
            id: object::new(ctx),
            owner: merchant_owner,
            key: utf8(merchant_key),
            balance: 0,
        };

        // Add to both tables
        object_table::add(&mut registry.merchant_to_address, merchant_owner, object::share_object(merchant));     
        // You'll need to decide on a primary storage and potentially store references or IDs in other tables.
        // For simplicity, let's assume `merchant_to_key` is the primary and `merchant_to_address` stores the object ID.
        // If `Merchant` is a shared object, you can store it in multiple tables.
        // Let's adjust the `Merchant` struct for clarity in this example.

        // For simplicity and to avoid the "same object in multiple places" issue,
        // it's often better to have one "primary" table that owns the object,
        // and other tables store only the object's ID or a reference.
        // If you truly need the Merchant object directly in both tables, the Merchant itself needs to be a SHARED object.
        // Let's revise the example to make Merchant a shared object.

        // REVISED MERCHANT STRUCT (if you want it in both tables directly):
        // public struct Merchant has key { // No 'store' ability if it's shared directly
        //     id: UID,
        //     owner: address,
        //     key: String,
        //     balance: u64,
        // }
        // public entry fun add_merchant(...) {
        //     let merchant = Merchant { ... };
        //     object::share_object(merchant); // Make it shared
        //     object_table::add(&mut registry.merchant_to_address, merchant_owner, merchant);
        //     object_table::add(&mut registry.merchant_to_key, utf8(merchant_key), merchant);
        // }
        // This makes `Merchant` a shared object, which has different implications for ownership and mutability.

        //***************Let's stick to the original assumption that Merchant is not shared,
        //***************and therefore only one table directly owns the `Merchant` object.
        //***************If you want to look up by address AND key, and only one table can own the object,
        //***************then one table (e.g., merchant_to_address) stores the `Merchant` object,
        //***************and the other (merchant_to_key) stores the `ID` of the Merchant.

        // For this scenario, let's assume `merchant_to_address` owns the `Merchant` objects,
        // and `merchant_to_key` will map a String key to the `ID` of the Merchant.

        // New Merchant struct:
        // public struct Merchant has key, store {
        //     id: UID,
        //     owner: address,
        //     key: String, // Stored inside the object
        //     balance: u64,
        // }

        // Updated MerchantRegistry:
        // public struct MerchantRegistry has key, store {
        //     id: UID,
        //     merchant_to_address: ObjectTable<address, Merchant>, // Owners the Merchant object
        //     merchant_to_key: ObjectTable<String, object::ID>,    // Maps key to Merchant's ID
        // }

        //****************Let's assume for the sake of the user's original question*****************
        //****************that `ObjectTable<String, Merchant>` is intended to hold****************
        //****************the actual Merchant object, which implies it's the primary****************
        //****************owner or the `Merchant` is shared.
        //****************If `Merchant` is not shared, only ONE `ObjectTable` can hold it.
        //****************The error "Cannot ignore values without drop ability" confirms
        //****************that the `Merchant` object itself is being handled.

        // Assuming merchant_to_key is the primary table where the Merchant object resides:
        object_table::add(&mut registry.merchant_to_key, utf8(merchant_key), merchant);
        // If you still need lookup by address, you'd store the ID in merchant_to_address:
        // object_table::add(&mut registry.merchant_to_address, merchant_owner, object::id(&merchant));
        // This creates a circular dependency in a single transaction if you try to get ID *after* adding.
        // Best practice is one table owning the object, others holding IDs.

        // For the sake of directly answering the user's original question about `merchant_to_key`:
    }


    /// Entry function to update a merchant's key in the ObjectTable.
    public entry fun update_merchant_key(
        registry: &mut MerchantRegistry,
        old_key: vector<u8>, // Input as vector<u8> for String conversion
        new_key: vector<u8>, // Input as vector<u8> for String conversion
        ctx: &mut TxContext // TxContext is often needed for object::new or other operations
    ) {
        // Convert input vectors to Sui Strings
        let old_key_str = utf8(old_key);
        let new_key_str = utf8(new_key);

        // 1. Remove the merchant using the old key.
        // This returns the 'Merchant' object. Since Merchant does not have 'drop',
        // we *must* bind it to a variable.
        let mut merchant = object_table::remove(&mut registry.merchant_to_key, old_key_str);

        // 2. Update the 'key' field inside the retrieved Merchant struct.
        merchant.key = new_key_str;

        // 3. Re-insert the updated Merchant object into the table with the new key.
        // This will create a new entry in the ObjectTable.
        object_table::add(&mut registry.merchant_to_key, new_key_str, merchant);

        // If you also had a `merchant_to_address` table mapping to `object::ID`,
        // and the `owner` of the merchant might also change, you'd need to update that as well.
        // For example:
        // let old_address = merchant.owner; // Assuming merchant.owner is still the original owner
        // if (old_address != new_owner_address) { // If owner changes
        //     let merchant_id = object_table::remove(&mut registry.merchant_to_address, old_address);
        //     // merchant.owner = new_owner_address; // Update owner in the struct if needed
        //     object_table::add(&mut registry.merchant_to_address, new_owner_address, merchant_id);
        // }
    }

    // You might also want a function to get a merchant by key for testing
    public fun get_merchant_by_key(
        registry: &MerchantRegistry,
        key: vector<u8>
    ): &Merchant {
        object_table::borrow(&registry.merchant_to_key, utf8(key))
    }
}
4
Коментарі
.
MoonBags.
Jul 23 2025, 15:30

У Sui Move ви не можете просто змінити ключ об'єкта в таблиці або безпосередньо видалити/замінити об'єкти без належної обробки. Коли ви змінюєте поле ключа всередині структури Merchant, таблиця merchant_to_key автоматично не оновлюється. Це залишає ваш старий ключ все ще вказує на об'єкт, тоді як новий ключ взагалі не має асоціації.

Помилка «Не можна ігнорувати значення без можливості скидання» виникає через те, що ваша структура Merchant, ймовірно, не має можливості скидання (що є загальним для структур, що містять UID). Це означає, що ви не можете просто видалити об'єкт з таблиці та відкинути його; ви повинні явно зберігати та повторно використовувати повернутий об'єкт.

3
Коментарі
.
0xduckmove.
Jul 23 2025, 15:32

Щоб правильно оновити ключ продавця, потрібно вручну видалити старий запис, оновити структуру Merchant новим ключем, а потім знову вставити його в таблицю. Ось як це можна зробити в рамках транзакції:

public entry fun update_merchant_key(
    registry: &mut MerchantRegistry,
    old_key: String,
    new_key: String
) {
    // 1. Remove the merchant using the old key and store the returned object.
    let merchant = object_table::remove(&mut registry.merchant_to_key, old_key);

    // 2. Update the key field inside the struct.
    merchant.key = new_key;

    // 3. Re-insert the merchant into the table with the updated (new) key.
    object_table::add(&mut registry.merchant_to_key, new_key, merchant);
}```

Цей підхід гарантує безпечне витягування об'єкта Merchant, зміни його ключа, а потім вставляєте його назад у ObjectTable з новим ключем. 
Цей метод дотримується правил власності Move та запобігає випадковій втраті або «витіку» ресурсів, що має вирішальне значення при роботі з типами, які не мають можливості скидання.

3
Коментарі
.
harry phan.
Jul 23 2025, 15:45

Щоб оновити ключ продавця в ObjectTable, потрібно вручну:

видалити об'єкт Merchant за допомогою old_key.

Оновіть поле ключа всередині структури Merchant, яку ви щойно отримували, і додайте модифікований об'єкт Merchant назад до ObjectTable за допомогою new_key.

public entry fun update_merchant_key(
    registry: &mut MerchantRegistry,
    old_key: String,
    new_key: String
) {
    let mut merchant = object_table::remove(&mut registry.merchant_to_key, old_key); // 1. Remove and capture
    merchant.key = new_key; // 2. Update the struct
    object_table::add(&mut registry.merchant_to_key, new_key, merchant); // 3. Re-insert
}
2
Коментарі
.
Meaning.Sui.
Jul 23 2025, 15:52

Якщо ви намагаєтеся оновити ключ продавця в ObjectTable<String, Merchant> і стикаєтеся з такими помилками, як «Не можна ігнорувати значення без можливості скидання», проблема полягає в тому, що в Sui Move ви не можете просто видаляти або замінити об'єкти в сховищі без явної обробки прав власності та типів, які не мають можливості скидання. Коли ви змінюєте ключ у структурі Merchant, таблиця merchant_to_key автоматично не оновлюється, тому старий ключ все ще вказує на об'єкт, а новий ключ взагалі не пов'язаний.

2
Коментарі
.
24p30p.
24p30p1865
Jul 9 2025, 05:43

Якщо ви намагаєтеся оновити дані продавця keyв ObjectTable<String, Merchant>a і стикаєтеся з помилками на кшталт «Не можна ігнорувати значення без можливості скидання», проблема полягає в тому, що в Sui Move ви не можете просто видалити або замінити об'єкти в сховищі без явної обробки прав власності та типів, яким не виста dropчає можливості. Коли ви змінюєте ключ Merchantвсередині структури, це автоматично не оновлює табли merchant_to_keyцю, тому старий ключ все ще вказує на об'єкт, а новий ключ взагалі не пов'язаний.

Щоб виправити це, вам потрібновручну видалити старий запис, оновити структуру, а потімзнову вставити її за допомогою нового ключа. Але оскільки структура не Merchantмає такої dropможливості, ви не можете просто видалити її та ігнорувати результат - вам доведеться зберігати та повторно використовувати повернутий об'єкт. Ось як ви можете зробити це належним чином всередині транзакції:

public entry fun update_merchant_key(
    registry: &mut MerchantRegistry,
    old_key: String,
    new_key: String
) {
    // Remove the merchant using the old key and store the returned object
    let merchant = object_table::remove(&mut registry.merchant_to_key, old_key);

    // Update the key field inside the struct
    merchant.key = new_key;

    // Re-insert into the table with the updated key
    object_table::add(&mut registry.merchant_to_key, new_key, merchant);
}

Таким чином, ви безпечно витягуєте продавця, змінюєте його та вставляєте назад за допомогою нового ключа, дотримуючись правил власності Move.

Пам'ятайте, це необхідно, оскільки Move призначений для запобігання випадковому падінню або витіканню ресурсів. Якщо ваша Merchantструктура не має здатності drop(яка є звичайною, коли вона містить aUID), ви завжди повинні обробляти її явно.

Щоб отримати більш чітке уявлення про те, як ObjectTableпрацює та як безпечно обробляти право власності, перегляньте офіційні документи тут: https://docs.sui.io/build/move/object-tables.

0
Коментарі
.

Ви знаєте відповідь?

Будь ласка, увійдіть та поділіться нею.

Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.

609Пости1280Відповіді
Sui.X.Peera.

Зароби свою частку з 1000 Sui

Заробляй бали репутації та отримуй винагороди за допомогу в розвитку спільноти Sui.

Кампанія винагородЛипень