Sui.

Допис

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

article banner.
harry phan.
Apr 24, 2025
Стаття

Крос-модульне управління дітьми за допомогою public_receiver

Це частина 3 серії «Об'єкти батьків-дитини в Sui Move».

Іноді ваші батьківські та дитячі типи визначаються в різних модулах або навіть різних пакетах. Наприклад, у вас може бути загальний об'єкт Warehouse, який може зберігати будь-які об'єкти Parcel. Модуль «Склад» хоче витягнути дочірню групу «Посилка», але тип «Посилка» визначається в іншому місці. У таких випадках ми використовуємо transfer: :public_receiver, який є крос-модульним двоюрідним братом отримання.

###отримувати проти публічний_отримати

Як ми бачили, transfer: :receiver можна викликати лише в модулі, який визначає T (або друга), оскільки він не вимагає T: store. Верифікатор Move байтового коду фактично гарантує, що під час будь-якого виклику для отримання тип T походить від поточного модуля. Це обмеження безпеки для об'єктів, що мають лише ключ.

transfer: :public_receiver — це варіант, який вимагає* T: ключ+сховище, але дозволяє отримувати за межами модуля T. Іншими словами, якщо тип об'єкта має можливість зберігання (тобто йому дозволено вільно існувати в глобальному сховищі), то будь-який модуль (заданий &mut UID батьківського користувача) може отримати його за допомогою public_receiver. Це ідеально підходить для випадків, коли батьківський модуль відрізняється від модуля дитини.

Навіщо потрібен магазин? Оскільки сховище позначає, що об'єкт можна безпечно зберігати та передати поза його визначальним модулем. Об'єкти, що містять лише ключі, можуть мати власні інваріанти, які оригінальний модуль хоче застосувати під час передачі та отримання; виключаючи ці з public_receiver, Sui змушує розробників обробляти їх у модулі (як ми побачимо з об'єктами, пов'язаними з душею). Якщо об'єкт має сховище, він є більш дозвільним, і Sui дозволяє загальній логіці передачі/прийому керувати ним зовні.

###Приклад: окремі батьківські та дочірні модулі

Проілюструємо простим сценарієм: Склад, який зберігає об'єкти Parcel. Тип посилки визначається у власному модулі, а Склад - в іншому. Ми покажемо, як Warehouse може отримати дочірню посилку за допомогою public_receiver.

module demo::parcel {  // Child module
    use sui::object::{Self, UID};
    use sui::tx_context::{Self, TxContext};

    /// A parcel object that can be stored in a Warehouse.
    /// It has both key and store, so it can be transferred across modules.
    struct Parcel has key, store { id: UID, contents: vector<u8> }

    public entry fun create_parcel(contents: vector<u8>, ctx: &mut TxContext): Parcel {
        Parcel { id: object::new(ctx), contents }
    }
}
module demo::warehouse {  // Parent module
    use sui::transfer::{Self, Receiving, public_receive};
    use demo::parcel::{Self, Parcel};
    use sui::object::{UID};
    use sui::tx_context::{Self, TxContext};

    struct Warehouse has key { id: UID, location: address }

    public entry fun create_warehouse(location: address, ctx: &mut TxContext): Warehouse {
        Warehouse { id: object::new(ctx), location }
    }

    /// Receive a Parcel that was sent to this Warehouse. 
    /// Returns the Parcel to the caller (transferred to caller's address).
    public entry fun withdraw_parcel(
        warehouse: &mut Warehouse, 
        parcel_ticket: Receiving<Parcel>, 
        ctx: &mut TxContext
    ): Parcel {
        // Using public_receive because Parcel is defined in another module and has store
        let parcel = public_receive<Parcel>(&mut warehouse.id, parcel_ticket) [oai_citation_attribution:27‡docs.sui.io](https://docs.sui.io/concepts/transfers/transfer-to-object#:~:text=%2F%2F%2F%20Given%20mutable%20,argument) [oai_citation_attribution:28‡github.com](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/transfer.move#:~:text=public%20fun%20public_receive,T%3E%29%3A%20T);
        // Transfer the parcel to the transaction sender (so the caller gets ownership)
        transfer::transfer(parcel, tx_context::sender(ctx));
        // We return nothing because we've transferred the Parcel out to the caller.
    }
}

Давайте розберемо те, що відбувається в _parcel:

  • Викликаємо public_receiver (&mut warehouse.id, parcel_ticket). Оскільки Parcel має можливість магазину, цей дзвінок дозволений, навіть якщо ми не перебуваємо в модулі посилок. Під капотом це виконує ту саму перевірку та вилучення, що і отримання, але це дозволено крос-модуль, оскільки магазин вказує, що це безпечно. https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/transfer.move#:~:text=public%20fun%20public_receive,T%3E%29%3A%20T
  • Потім негайно передаємо отриману посилку на адресу абонента (tx_context: :send (ctx)). Цей крок гарантує, що посилка покине склад і переходить до користувача, який ініціював виведення. Ми також могли просто повернути Parcel з функції, і Sui розглядає його як вихід, який належить адресі абонента (оскільки це вихід функції введення). Виконання явної передачі є більш детальним, але дає зрозуміти, що відбувається (і дозволяє нам робити будь-які перевірки перед випуском об'єкта).

Навіщо включати магазин у Parcel? Якщо у Parcel не вистачало можливості зберігання (тобто він мав лише ключ), виклик public_receiver у демо: :warehouse не компілюватиметься — Sui забезпечує те, що T має сховище для public_receiver. У цьому випадку ми будемо змушені отримати посилку за допомогою отримання в самому модулі посилки (або використовувати деякі стосунки з друзями), що ускладнює дизайн між модулями. Додавши магазин до Parcel, ми фактично говоримо «цей об'єкт можна вільно переміщати та отримувати зовнішніми модулями», що ми хочемо для загального шаблону контейнера.

Шаблон виклику функції: Щоб використовувати їх у транзакції, потік буде таким:

1.**Депозит (переказ на об'єкт) :**Телефонуйте переказ: :public_transfer (parcel_obj, @warehouse_id) для відправки Посилки на Склад. Це позначає власника посилки як склад. (Ми використовуємо public_transfer тут, оскільки він знаходиться поза модулем Parcel, а Parcel має магазин. Всередині модуля посилки також спрацює звичайна передача.) 2. **Вивести (отримати назад) :**Пізніше зателефонуйте _parcel (warehouse_obj, Прийом (ідентифікатор посилки,...)). Отримання можна отримати за допомогою SDK, посилаючись на ідентифікатор посилки та останню версію. Функція викличе public_receiver, а потім передасть вам посилку.

Після дзвінка _parcel власник посилки повертається на адресу (вашу), тому це знову звичайний об'єкт, що належить адресі. Склад більше не володіє ним.

Міркування між модулями: Зверніть увагу, що модуль Склад повинен знати про тип посилки (ми використовуємо demo: :parcel: :Parcel). Це тому, що ми явно вводимо Прийом як отримання. Якщо вам потрібен справді загальний контейнер, який міг би отримувати будь-який тип об'єкта, вам доведеться використовувати генерики або інший підхід (можливо, динамічні поля зі стиранням типів). Але для більшості випадків використання ви будете знати, яких типів дітей ви очікуєте.

Навіщо public_receiver замість того, щоб просто телефонувати Receive? Якщо ми спробуємо передати: :receiver (&mut warehouse.id, parcel_ticket) у модулі складу, перевірка переміщення відхилить його, оскільки Parcel не визначено в демо: :warehouse. Sui надає public_receiver як благословенний спосіб зробити це за допомогою додаткової перевірки здібностей (вимагає зберігання). Аналогічно, Sui має передачу проти public_transfer, freeze object проти public_freeze object тощо, за тим самим шаблоном: версії public_ призначені для використання поза визначальним модулем і вимагають зберігання.

Не забудьте дозвіл батьків: Навіть з public_receiver вам все одно потрібен &mut warehouse.id. Ми отримали його, тому що _parcel знаходиться в модулі складу і приймає &mut Warehouse. Таким чином, вилучити посилку може тільки той, хто міг би на це подзвонити (власник складу). Якщо модуль складу не надав таку функцію публічно, ніхто не міг би ззовні викликати public_receiver на її дочірніх програмах. Таким чином, крос-модуль не обходить батьківський контроль; він просто дозволяє батьківському коду працювати з дочірніми типами, які він не визначив.

Примітка щодо можливості зберігання: Надання сховища об'єктів робить його більш гнучким, але трохи менш обмеженим - будь-який модуль з батьківським посиланням може витягнути його за допомогою public_receiver. Якщо ви хочетеобмежитиспосіб отримання об'єкта (наприклад, застосувати власну логіку або запобігти легкому вилученню), ви можете навмисно зробити його лише для ключів. Ми побачимо приклад цього з предметами, пов'язаними з душею. У таких випадках ви можете реалізувати спеціальну функцію отримання замість того, щоб покладатися на public_receiver.

Підсумовуючи цю частину: public_receiver є вашим другом для керування дочірніми об'єктами, визначеними в інших модулах, якщо ці об'єкти мають можливість зберігання. Це дозволяє створювати крос-модульні системи (наприклад, наш склад/посилка), дотримуючись при цьому права власності та контролю доступу. Просто не забудьте включити магазин за дочірніми типами та використовувати public_transfer, коли надсилаєте їх батькові за межами їхнього модуля.

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

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

259Пости368Відповіді
Sui.X.Peera.

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

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

Кампанія винагородТравень
      Ми використовуємо файли cookie, щоб гарантувати вам найкращий досвід на нашому сайті.
      Детальніше