Допис
Діліться своїми знаннями.
Логіка прив'язки душі та шаблон отримання повернення
#Логіка прив'язки душі та схема отримання повернення
Тепер для спеціального шаблону дизайну, який використовує механізм батьків-дочірня:об'єкти, пов'язані з душею. Об'єкт, пов'язаний з душею, призначений для прив'язки до конкретного власника і не підлягає постійній передачі. Однак, можливо, ви захочете * тимчасово* надати комусь доступ до нього в рамках однієї транзакції - наприклад, для виконання якоїсь операції з ним - але переконайтеся, що він повернеться вам до кінця цієї транзакції. Це досягається за допомогою того, що ми називаємо шаблоном повернення квитанції**, варіантом візерунка «гаряча картопля».
###Візерунок гарячої картоплі в Sui
Візерунок «гаряча картопля» названий на честь дитячої гри - якщо вам вручили гарячу картоплю 🌡️🥔, ви не можете її тримати; ви повинні швидко передати її. У терміні Move гаряча картопля зазвичай являє собою об'єкт або ресурс, який мус бути використаний або переданий в тій самій транзакції, інакше транзакція не вдається. Це часто реалізується шляхом надання об'єкту можливості без падіння (тому ви не можете просто ігнорувати його або скинути) та розробляючи логіку таким чином, що єдиний спосіб позбутися від нього - виконати необхідну дію (наприклад, повернути позику).
Поширеним випадком використання єфлеш-позика: ви позичаєте деякі монети в транзакції (отримуєте об'єкт позики, плюс токен «боргу»). Якщо ви не погасите до кінця транзакції, ви не можете завершити, оскільки ви все ще тримаєте той токен боргу, який у вас немає дозволу скинути, що фактично змушує вас повернути позику або перервати.
###Приклад об'єкта, пов'язаного з душею
Припустимо, у нас є об'єкт SoulBound, який ми завжди хочемо залишати з його початковою адресою власника. Ми можемо примусити, що якщо хтось «позичить» його (як дочірній об'єкт у якогось батька), він повинен повернути його в тій же транзакції. Як? За допомогою квитанції про повернення.
Нижче наведено спрощену версію, натхненну прикладом документації Суї про предмети, пов'язані з душею
module demo::soul_bound {
use sui::transfer::{Self, Receiving};
use sui::object::UID;
use sui::transfer;
const EWrongObject: u64 = 0;
/// A soul-bound object that cannot be permanently transferred.
/// It has only `key` ability (no `store`), to tighten transfer/receive rules [oai_citation_attribution:40‡docs.sui.io](https://docs.sui.io/concepts/transfers/transfer-to-object#:~:text=%2F%2F%2F%20This%20object%20has%20,id%3A%20UID%2C).
public struct SoulBound has key {
id: UID,
data: u64
}
/// A receipt that proves a SoulBound object must be returned.
/// No abilities: cannot be copied, dropped, or stored (implicitly).
public struct ReturnReceipt {
/// The object ID of the soul-bound object that must be returned.
object_id: UID,
/// The address (or object ID) it must be returned to (the original owner).
return_to: address
}
/// Allows the owner of `parent` to retrieve their SoulBound child.
/// Returns the SoulBound object *and* a ReturnReceipt that compels return.
public fun take_soul_bound(parent: &mut UID, sb_ticket: Receiving<SoulBound>): (SoulBound, ReturnReceipt) {
let sb = transfer::receive(parent, sb_ticket); // receive SoulBound (only this module can do it, since SoulBound has no store) [oai_citation_attribution:41‡docs.sui.io](https://docs.sui.io/concepts/transfers/transfer-to-object#:~:text=%2F%2F%2F%20,to_address%28%29%2C%20object_id%3A%20object%3A%3Aid%28%26soul_bound%29%2C)
let receipt = ReturnReceipt {
object_id: sb.id,
return_to: parent.to_address()
};
(sb, receipt)
}
/// Return a SoulBound object using the ReturnReceipt.
/// This must be called in the same transaction after take_soul_bound.
public fun return_soul_bound(sb: SoulBound, receipt: ReturnReceipt) {
// Verify the receipt matches this SoulBound object
assert!(sb.id == receipt.object_id, EWrongObject);
// Send the SoulBound object back to the original owner address
transfer::transfer(sb, receipt.return_to);
// (ReturnReceipt is consumed/destroyed here as function param)
}
}
У цій конструкції:
- SoulBound - об'єкт лише для ключів. Не надаючи йому зберігання, ми перешкоджаємо використанню на ньому public_transfer або public_receiver, тобто єдиний спосіб перенести або отримати його - це через функції визначеного модуля (гарантуючи використання нашої власної логіки).
- take_soul_bound (аналогічно «get_object» у документах) - це функція, яку власник викликав би, щоб вилучити свій об'єкт, пов'язаний з душею, з якогось батька. Він викликає transfer: :receiver, щоб отримати об'єкт SoulBound. Оскільки SoulBound не має магазину, цей виклик дозволений лише тут у своєму модулі (що добре). Потім він створює структуру returnReceipt, що містить ідентифікатор об'єкта, зв'язаного з душею, та адресу, до якої потрібно повернутися (адреса батька). Повертаємообооб'єкт, і квитанцію.
- ReturnReceipt не має можливостей скидання, зберігання або копіювання (ми не оголосили жодного, тому він розглядається як ресурс, який не можна скинути або дублювати). Це означає, що транзакціямусзробити щось із нею; інакше це залишиться ресурсом, і віртуальна машина не дозволить транзакції завершитися успішно. По суті, ReturnReceipt — це наш жетон гарячої картоплі 🔥🥔.
- Єдине дійсне, що можна зробити з returnReceipt - це викликати return_soul_bound (або подібну призначену функцію) в тій самій транзакції. У return_soul_bound ми перевіряємо, чи ідентифікатор об'єкта SoulBound відповідає запису квитанції (щоб запобігти спробі повернути інший об'єкт з неправильним квитанцією). Потім викликаємо transfer: :transfer (sb, receipt.return_to), який відправляє об'єкт SoulBound назад на адресу в квитанції (початковий власник). Це ефективно відновлює пов'язаний з душею об'єкт на законне місце. ReturnReceipt використовується як аргумент (таким чином знищується).
- Якщо користувач спробує завершити транзакцію, не викликаючи return_soul_bound, він все одно буде тримати ReturnReceipt (з виводу take_soul_bound). Оскільки у ReturnReceipt відсутній drop, Move VM відмовиться завершити транзакцію (у Move ви не можете просто відкинути ресурс, він повинен бути використаний або десь зберігатися). Транзакція буде перервана або вважатиметься недійсною, тобто вся операція відколюється назад.Результат: ви не можете просто втекти з об'єктом, пов'язаним з душею; якщо ви не повернете його, tx не вдається, і він залишається у батька.
З моменту виклику take_soul_bound до моменту виклику return_soul_bound в транзакції, у вас є доступне значення об'єкта SoulBound - імовірно, для виконання деяких дозволених операцій з ним (можливо прочитати його або використовувати за потреби). Але виповинніповернути його до того, як транзакція закінчиться, завдяки стягненню квитанцій.
Цей шаблон часто описується як «прив'язаний до душі = не може залишити свого власника», але точніше, він може залишити в межах однієї транзакції як дочірній об'єкт, з гарантією, що він повернеться. Це як перевірити бібліотечну книгу - ви повинні повернути її до того, як бібліотека закриється на день 😅.
Чому б просто ніколи не передавати його взагалі? Існують сценарії, коли тимчасове перенесення об'єкта на інший об'єкт (або контекст) корисно. Одним із прикладів є контекстспільного об'єкта: можливо, елемент SoulBound утримується під спільним об'єктом під час певної операції і повинен бути видалений початковим власником. Інша полягає в тому, щоб дозволити контрольовану композицію - ви можете дозволити деякому модулю працювати над вашим об'єктом, передаючи його об'єкту цього модуля, а потім поверніть його.
Зробивши SoulBound лише для ключів, ми переконалися, що жоден зовнішній public_receiver не може отримати його без участі нашого модуля. Використовуючи квитанцію, ми змушуємо дотримуватися політики повернення. Коментар у прикладі коду Sui навіть зазначає, що ReturnReceipt запобігає обміну - якщо в одній транзакції було вилучено два об'єкти, пов'язані з душею, кожна квитанція містить певний ідентифікатор об'єкта, тому ви не можете змішати їх і повернути неправильний, щоб обдурити.
Узагальнення ідеї: Шаблон ReturnReceipt можна використовувати, коли ви хочете змусити повернути об'єкт. Флеш-позики використовують подібну концепцію (токен позики та токен боргу, який потрібно погасити). Щоразу, коли у вас є інваріантний «об'єкт X повинен повернутися на адресу Y до кінця tx», ви можете створити ресурс квитанції, щоб підтримувати його.
###Швидкий підсумок:
-Об'єкти, пов'язані з душею: лише ключі, отримані лише за логікою їх модуля. -ReturnReceipt: фіктивний ресурс, який повертається поряд з об'єктом, щоб гарантувати його повернення. Він не випадає, тому користувач повинен викликати функцію повернення або не вдасться. -Гаряча картопля: Якщо ви тримаєте повернення квитанції, вам краще обробити її (повернути об'єкт) до того, як «музика припиниться» (транзакція закінчиться).
- Якщо об'єкт не буде повернуто, транзакція не буде успішно виконана - ваші зміни відкочуються, ефективно застосовуючи правило, пов'язаного з душею.
І з цим ми висвітлили основні концепції стосунків батьків-дитина в Sui Move!
##5. Структура проекту та стратегія тестування (готова до GitHub)
Щоб закріпити ці концепції, ви можете створити проект Sui Move та пограти з прикладами. Ось запропонований макет проекту, який включає модулі для наведених вище прикладів. Ви можете компілювати та запускати одиничні тести або використовувати Sui CLI для взаємодії з ними.
parent_child_demo/
├── Move.toml
├── sources/
│ ├── toy_box.move (Parent & child in same module example)
│ ├── parcel.move (Child module for Parcel)
│ ├── warehouse.move (Parent module for Warehouse, uses public_receive)
│ └── soul_bound.move (SoulBound and ReturnReceipt module)
└── tests/
└── parent_child_test.move (Integration tests for the modules)
Move.toml: Обов'язково включіть фреймворк Sui та будь-які адреси для вашого пакету. Наприклад:
[package]
name = "parent_child_demo"
version = "0.0.1"
dependencies = [
{ name = "Sui", git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" }
]
[addresses]
demo = "0x0"
- (Використовуйте правильну адресу замість 0x0, якщо цього вимагає Sui, або названу адресу, яку ви призначатимете під час публікації. ) *
У каталозі джерел:
-toy_box.moveможе містити модуль приклад: :toy_box з частини 2, з визначеннями Toy та Box та функцією take_toy. -parcel.moveтаwarehouse.moveбудуть містити модулі з Частини 3. -soul_bound.moveбуде містити модуль з частини 4.
Кожен модуль повинен використовувати sui::... у міру необхідності для передачі, object, tx_context тощо, як показано в прикладах. Потім tests/parent_child_test.move може імпортувати ці модулі та імітувати сценарії:
module demo::parent_child_test {
use 0xYourAddr::toy_box;
use 0xYourAddr::warehouse;
use 0xYourAddr::parcel;
use 0xYourAddr::soul_bound;
use sui::tx_context::TxContext;
#[test]
fun test_toy_withdraw() {
let ctx = TxContext::new(); // pseudo, context is usually provided
let my_box = toy_box::Box { id: object::new(&mut ctx) };
let my_toy = toy_box::Toy { id: object::new(&mut ctx), name: b"Ball".to_vec() };
// Transfer toy into the box
transfer::transfer(my_toy, my_box.id);
// Now simulate receiving it back
let ticket = transfer::Receiving<toy_box::Toy>{ /*...*/ }; // In real test, use transfer::make_receiver or a transaction call
let returned_toy = toy_box::take_toy(&mut my_box, ticket);
assert!(returned_toy.name == b"Ball".to_vec());
}
// Additional tests for warehouse/parcel and soul_bound can be written similarly.
}
Наведене вище є концептуальною ілюстрацією. На практиці тестовий контекст Sui може відрізнятися — вам може знадобитися використовувати transfer: :public_transfer у тестах під час передачі об'єкту (якщо він знаходиться за межами модуля), а також використовувати контекст отримання з транзакції. Sui надає передачу: :make_receiver (функція лише для тестування), яка може сформувати Receiving із заданим ідентифікатором
Запуск тестів: Ви можете запустити test sui move, який виконає функції # [test]. Або розгорніть пакет у локальну мережу Sui та викликайте функції введення через CLI або SDK, щоб спостерігати за поведінкою:
- Створіть посилку, створіть склад, зателефонуйте transfer: :public_transfer через CLI, щоб помістити посилку на склад, потім зателефонуйте _parcel.
- Створіть об'єкт SoulBound (можливо, зробивши запис цікавим для створення), перенесіть його на якийсь батьківський (або спільний об'єкт), потім в одній транзакції викликайте take_soul_bound та пропустіть return_soul_bound, щоб побачити, що транзакція провалилася (очікувано), проти включення виклику return, щоб побачити успіх.
Кожна частина цього проекту стосується однієї з тем:
- toy_box.move: базовий батько-дитина та отримання.
- parcel.move & warehouse.move: крос-модульні дочірні дані та public_receiver.
- soul_bound.move: прив'язана до душі з поверненою квитанцією.
Експериментуючи з ними, ви поглибите своє розуміння об'єктної моделі Суї. Код структурований таким чином, щоб бути навчальним і не повинен використовуватися як є для виробництва без оглядів безпеки, але він забезпечує надійну відправну точку.
Висновок: Функціональність батьківсько-дочірнього об'єкта Sui Move потужна. Це дозволяє створювати складні структури даних в ланцюжку (наприклад, інвентаризації, гаманці, колекції) з тонкозернистим контролем доступу. Поєднання transfer: :receive/public_receiver та системи типу Move гарантує, що лише авторизований код може отримати дочірні об'єкти, а шаблони, такі як returnReceipt, дозволяють застосовувати правила тимчасового володіння (прив'язка душі, флеш-позики тощо). Ми посипали кілька цікавих аналогій та емоджі, але зрештою, це надійні інструменти для будівельників. Тепер йдіть вперед і побудуйте якусь магію вкладених об'єктів! 🚀🔥
Коли об'єкт передається на інший об'єкт замість адреси облікового запису, вони утворюютьбатько-дитинські стосунки. Ви можете думати про це так:
-Батьки= об'єкт контейнера -Дитина= річ, поміщена всередині
Переміщення не має значення, якщо ви переходите до облікового запису чи ідентифікатора об'єкта. Це просто * рухається*.
public struct Parent has key {
id: UID,
name: String,
}
public struct Child has key {
id: UID,
description: String,
}
##Створення батьківських та дочірніх об'єктів
Батько повинен бути змінним і повинен існувати. Ви не можете засипати речі в незмінну коробку!
- Sui
- Architecture
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.
Зароби свою частку з 1000 Sui
Заробляй бали репутації та отримуй винагороди за допомогу в розвитку спільноти Sui.

- ... SUIBigSneh+1396
- ... SUISuiLover+1333
- ... SUI0xduckmove+1207
- ... SUIThorfin+1202
- ... SUIOwen+970
- ... SUIharry phan+847
- ... SUItheking+742
- Чому BCS вимагає точного порядку полів для десеріалізації, коли структури Move мають названі поля?53
- Помилки перевірки кількох джерел» у публікаціях модуля Sui Move - автоматичне вирішення помилок43
- Невдала операція Sui: об'єкти, зарезервовані для іншої транзакції25
- Як обмеження здібностей взаємодіють з динамічними полями в гетерогенних колекціях?05