Sui.

Пост

Поделитесь своими знаниями.

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

Межмодульное управление детьми с помощью public_receive

Это третья часть серии «Объекты для родителей и детей в режиме Sui Move».

Иногда ваши родительские и дочерние типы определяются в разных модулях или даже в разных пакетах. Например, у вас может быть обычный объект Warehouse, в котором можно хранить любые объекты Parcel. Модуль Warehouse хочет удалить дочерний объект Parcel, но тип участка определен в другом месте. В таких случаях мы используем transfer: :public_receive, который является межмодульным двоюродным братом receive.

###получать и public_receive

Как мы уже видели, transfer: :receive можно вызвать только в модуле, который определяет T (или имя друга), поскольку для этого не требуется T: store. Верификатор байт-кода Move фактически гарантирует, что в любом получаемом вызове используется тип T из текущего модуля. Это ограничение безопасности для объектов, содержащих только ключи.

transfer: :public_receive — это вариант, который требует хранилища T: key +, но позволяет получать данные за пределами модуля T. Другими словами, если тип объекта можно хранить (то есть ему разрешено свободно существовать в глобальном хранилище), то любой модуль (с идентификатором родительского объекта &mut) может получить его с помощью public_receive. Это идеально подходит для случаев, когда родительский модуль отличается от дочернего модуля.

Зачем нужен магазин? Потому что хранилище отмечает, что объект можно безопасно сохранить и передать за пределы определяющего модуля. Объекты, содержащие только ключи, могут иметь собственные инварианты, которые исходный модуль хочет использовать при передаче и получении. Исключая объекты из public_receive, Sui заставляет разработчиков обрабатывать их в модуле (как мы увидим в случае с объектами, привязанными к душе). Если у объекта есть хранилище, оно более разрешительно, а Sui позволяет управлять им извне с помощью общей логики передачи/получения.

###Пример: отдельные родительские и дочерние модули

Давайте проиллюстрируем простой сценарий: склад, в котором хранятся объекты Parcel. Тип участка определяется в отдельном модуле, а склад — в другом. Мы покажем, как Хранилище может получить дочернюю посылку с помощью public_receive.

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.
    }
}

Давайте разберемся, что происходит в withdraw_parcel:

  • Мы называем public_receive (&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: :sender (ctx)). Этот шаг гарантирует, что посылка покинет склад и отправится пользователю, инициировавшему вывод средств. Кроме того, мы могли бы просто вернуть посылку из этой функции, и Суй воспримет ее как выходные данные, принадлежащие адресу вызывающего абонента (поскольку это выходные данные функции ввода). Выполнение явной передачи более подробное задание, но оно позволяет понять, что происходит (и позволяет нам выполнить любые проверки перед выпуском объекта).

Зачем включать магазин в посылку? Если в Parcel отсутствовала возможность хранения (то есть у нее был только ключ), вызов public_receive в demo: :warehouse не скомпилировался — Sui заставляет T оставить в памяти public_receive. В этом случае нам придется забрать посылку с помощью команды receive в самом модуле обработки посылок (или использовать дружеские отношения), что усложнит межмодульное проектирование. Добавляя хранилище в Parcel, мы фактически говорим, что «этот объект можно свободно перемещать и принимать внешними модулями». Именно это и нужно для стандартного шаблона контейнера.

Шаблон вызова функций: Чтобы использовать их в транзакции, необходимо выполнить следующую последовательность действий:

1.**Депозит (перевод на объект) :**Позвоните в transfer: :public_transfer (parcel_obj, @warehouse_id), чтобы отправить посылку на склад. При этом владелец посылки считается складом. (Здесь мы используем public_transfer, потому что оно находится за пределами модуля Parcel и в Parcel есть магазин. В модуле посылки также подойдет простой перевод.) 2. **Выведите деньги (получите обратно) :**Позже вызовите команду withdraw_parcel (warehouse_obj, «Получение» (parcel_id,...)). Справку о получении можно получить с помощью SDK, указав идентификатор посылки и ее последнюю версию. Функция вызовет public_receive, а затем передаст вам посылку.

После вызова withdraw_parcel владелец посылки возвращается на адрес (ваш), так что это снова обычный объект, принадлежащий адресу. Склад больше не владеет им.

Межмодульные аспекты: Обратите внимание, что модулю Warehouse необходимо знать тип посылки (мы используем demo: :parcel: :Parcel). Это связано с тем, что мы явно указываем «Получение» как «Получение». Если вам нужен действительно универсальный контейнер, в который можно было бы поместить объекты любого типа, вам придется использовать дженерики или другой подход (возможно, динамические поля со стиранием типов). Но в большинстве случаев вы знаете, каких типов детей вы ожидаете.

Почему public_receive вместо простого вызова receive? Если мы попробуем transfer: :receive (&mut warehouse.id, parcel_ticket) в модуле склада, верификатор Move отклонит запрос, так как в demo: :warehouse не определена посылка. Sui предоставляет public_receive в качестве удачного способа сделать это, добавив дополнительную проверку способностей (требующую сохранения). Аналогичным образом, Sui использует transfer против public_transfer, freeze_object против public_freeze_object и т. д., следуя той же схеме: версии public_ предназначены для использования вне определяющего модуля и требуют хранения.

Не забудьте разрешение родителя: Даже если вы используете public_receive, вам все равно нужен этот &mut warehouse.id. Мы его получили, потому что withdraw_parcel находится в модуле Warehouse и принимает &mut Warehouse. Таким образом, забрать посылку может только тот, кто так позвонит (владелец склада). Если складской модуль не предоставлял такую функцию публично, никто также не мог бы вызвать public_receive извне для своих дочерних элементов. Таким образом, кросс-модуль не обходит родительский контроль; он просто позволяет родительскому коду работать с дочерними типами типов, которые он не определил.

Примечание о возможности хранения: Предоставление хранилища объектов делает его более гибким, но немного менее ограниченным — любой модуль, имеющий ссылку на родительский объект, может удалить его с помощью public_receive. Если вы хотитеограничитьспособ извлечения объекта (например, ввести собственную логику или запретить простое извлечение), вы можете намеренно сделать его доступным только по ключу. Пример этого мы рассмотрим на примере объектов, привязанных к душе. В таких случаях вы можете реализовать собственную функцию получения, а не полагаться на public_receive.

Подводя итог этому, можно сказать, что public_receive — ваш друг в управлении дочерними объектами, определенными в других модулях, при условии, что эти объекты можно хранить. Он позволяет создавать кроссмодульные системы (например, наш склад/посылок), сохраняя при этом право собственности и контроль доступа. Просто не забудьте включить хранилище дочерних типов и использовать 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, чтобы гарантировать вам лучший опыт на нашем сайте.
      Подробнее