Пост
Поделитесь своими знаниями.
+15
Как ограничения возможностей взаимодействуют с динамическими полями в гетерогенных коллекциях?
Я создаю торговую площадку, которая будет работать с несколькими типами ресурсов с разными требованиями к возможностям, и я задал несколько фундаментальных вопросов о системе типов Move. Я хочу хранить разные типы активов в одной коллекции, но у них разные возможности:
- Обычные NFT:
key + store
(можно передавать другим лицам) key
Только токены Soulbound (не подлежат передаче)- Настраиваемые активы с ограничениями на перевод
public struct Marketplace has key {
id: UID,
listings: Bag, // Want to store different asset types here
}
// This works for transferable assets
public fun list_transferable<T: key + store>(
marketplace: &mut Marketplace,
asset: T,
price: u64
) { /* ... */ }
// But how to handle soulbound assets?
public fun list_soulbound<T: key>( // No store ability
marketplace: &mut Marketplace,
asset_ref: &T, // Can only take reference
price: u64
) { /* How do I store metadata about this? */ }
Ключевые вопросы:
- Требования к возможностям:
dynamic_field::add<K, V>()``V
всегда ли при использовании требуетсяstore
время компиляции? Могут ли типы оболочек обойти эту проблему? - Гетерогенное хранилище: можно ли в одном пакете хранить объекты с разными наборами способностей
key + store + copy``key + store
и по-разному обрабатывать их во время выполнения? - Безопасность типов: поскольку в динамических полях происходит стирание типов, как обеспечить безопасность типов при извлечени�� значений? Какова схема хранения метаданных типов?
- Паттерн Witness: как ограничения способностей работают с фантомными типами? Могу ли я хранить информацию о
Asset<Type1>``Asset<Type2>
типах в той же коллекции и извлекать информацию о типах позже?
Создание системы, в которой NFT, токены soulbound и активы с ограниченным доступом требуют функциональности торговой площадки, но с другой семантикой передачи данных.
Я испробовал типы оболочек, несколько коллекций для каждого набора способностей, хранилище метаданных отдельных типов. В каждом из них есть компромисс между безопасностью типов, стоимостью газа и сложностью.
- Sui
- Architecture
Ответы
5Вы сталкиваетесь с ядром системы типов и возможностей Move и совершенно правы, задавая эти вопросы при создании гетерогенной торговой площадки.
Во-первых, об ограничениях возможностей: любое значение, которое вы храните с помощью dynamic_field: :add<K, V> (), должно быть доступно для сохранения на момент компиляции. В Move существует строгое правило: значения без сохранения просто невозможно сохранить в хранилище, будь то в динамических полях, таблицах или аналогичных конструкциях. Попытка обернуть тип, не являющийся хранилищем, в структуру тоже не сработает — здесь Move действует строго. Если хотя бы в одном поле нет хранилища, вся оболочка также потеряет хранилище. Так что, к сожалению, здесь нет обходного пути с помощью обертки. Ресурсы Soulbound, содержащие только ключ (и не имеющие хранилища), исключаются из любой коллекции, записываемой в глобальное хранилище, например в сумку или динамическое поле. Это хорошо описано в документации по возможностям Move.
Что касается хранения разных типов активов в одной коллекции: если все они используют key + store, то да, их можно хранить в одной сумке, но под капотом Move не существует реального полиморфизма. Тип должен быть известен во время доступа, а пакет должен быть однородным с точки зрения компилятора. В связи с этим возникает следующий вопрос: как обеспечить безопасность типов в динамических полях? Поскольку приложение Move стирает текст в этих коллекциях, оно не запоминает, какой именно шрифт находится в них. Вам необходимо предоставить сопоставление. Один из распространенных вариантов заключается в том, что каждый ресурс объединяют в структуру, включающую такие метаданные, как перечисление u8 или TypeName, поэтому при извлечении объекта вы проверяете метаданные и преобразуете или обрабатываете их соответствующим образом. Волшебного RTTI не существует; вы его подделываете, соблюдая дисциплину обработки данных.
Теперь о фантомных типах и паттернах-свидетелях: они полезны для обеспечения безопасности во время компиляции, но не позволяют проводить самоанализ во время выполнения.
При проектировании торговой площадки это означает, что вы, скорее всего, рассматриваете гибридную стратегию. Для обычных NFT и других передаваемых активов вы можете свободно использовать динамические поля объектов или пакеты, если они соответствуют значению key + store. Для ресурсов, связанных только с ключевыми данными, лучше всего хранить ссылки и связанные с ними метаданные в параллельном пакете метаданных или использовать собственный тип списка, в котором не хранится сам ресурс, а фиксируется его существование с помощью идентификатора и дополнительных данных. Это сложнее, но позволяет правильно применять семантику переноса данных.
Ссылки
1. store
Всегда ли нужны значения в динамических полях?
dynamic_field::add
Если вы хотитесохранитьзначение непосредственно в динамическом поле (например, viastore
), оно должно удовлетворять всем возможностям, необходимым для типа поля, в том числе. В V
противном случае store
вы не сможете напрямую поместить его в динамическое поле.
Но вот в чем фишка:
Выможете упаковатьтакие типы в другую структуру
store
, у которой есть, даже если внутренний тип этого не делает.
Пример ###:
struct AssetWrapper<T: key> has store {
inner: T,
}
Несмотря на то, T
что оно естьkey
, его AssetWrapper<T>
можно хранить, потому что оно есть само по себеstore
.
Теперь вы можете сделать следующее:
dynamic_field::add(&mut object, key, AssetWrapper { inner: soulbound_token });
Это позволяет хранить как передаваемые, так и непередаваемые активы под одной и той же абстракцией.
2. Можно ли в одной сумке хранить предметы с разными наборами способностей?
Краткий ответ:да, используя обертки и методы стирания текста. Но есть и компромиссы.
В Move вы не сможете напрямую составить список нескольких типов бетона, если они не упакованы в обычный контейнер.
Хорошим подходом является использование такой обертки:
struct AnyAsset has store {
// Maybe include metadata or witness type info
data: Vector<u8>, // serialized representation?
type_info: TypeInfo, // Type metadata
}
Или, что еще лучше, используйте кодировку перечислений в вариантном стиле (если она поддерживается вашим диалектом Move):
enum AssetType has store {
Transferable(NFT),
Soulbound(SoulboundToken),
Restricted(RestrictedAsset),
}
Однако это требует исчерпывающего сопоставления и плохо масштабируется между модулями.
Итак, опять же:обертывание, динамические поля и метаданные— более гибкое решение.
3. Как обеспечить безопасность типов при извлечении?
Move выполняетстирание шрифтовв динамических полях. Таким образом, как только вы извлекаете значение из динамического поля, вы просто получаете значение определенного типа, V
но не знаете, какое значение V
находится во время выполнения.
Чтобы безопасно решить эту проблему, вам необходимо:
- Храните метаданныетипрядом со значением.
- Используйте шаблон-свидетельиливстраивание тегов**, чтобы повторно подтвердить информацию о типе при получении.
Шаблон ###: метаданные типа хранилища
struct AssetWithMetadata<T: key> has store {
value: T,
type_info: TypeInfo,
}
Затем сохраните AssetWithMetadata<NFT>
или AssetWithMetadata<SoulboundToken>
в динамическом поле.
При извлечении данных сверьте значение с type_info
ожидаемым типом, прежде чем делать какие-либо небезопасные действия.
4. Паттерны свидетелей и типы фантомов
Вы вполне можете использовать фантомные типы в качестве свидетелей для кодирования поведения, например передаваемости.
Например:
struct Transferable; // marker type
struct NonTransferable; // marker type
struct Asset<T: drop + copy + store + key, Policy: phantom> has key, store {
id: UID,
policy: PhantomData<Policy>,
}
Затем:
type NFT = Asset<_, Transferable>;
type Soulbound = Asset<_, NonTransferable>;
Затем вы можете написать логику, например:
public fun transfer<T>(asset: Asset<T, Transferable>) {
// allowed
}
public fun transfer<T>(_asset: Asset<T, NonTransferable>) {
// disallowed
abort();
}
Это позволяет обеспечить соблюдение правил передачи данных во время компиляции.
Но для хранения обоих типов в одной коллекции вам все равно понадобится оболочка:
struct AnyAsset has store {
data: Vector<u8>,
type_info: TypeInfo,
}
type_info
И динамически обрабатывайте десериализацию на основе.
Вот как можно создать торговую площадку Sui Move, поддерживающую как передаваемые (NFT, ключ + хранилище), так и активы, привязанные к душе (только ключи), сохраняя при этом безопасность типов и возможности обработки.
Требования к возможностям работы с динамическими полями
Да, во время компиляции в V должно быть хранилище для dynamic_field: :add<K, V> () и аналогичных коллекций.
- хранилище необходимо для хранения любого значения в глобальном хранилище, включая динамические поля и таблицы.
- Типы-оболочки не могут обойти эту проблему: если вы упаковываете тип без сохранения в структуру, в оболочке также не может быть хранилища, потому что для хранения структуры должно быть хранилище во всех полях (source).
Разнородное хранилище в пакете
- В сумке (или в любой динамической полевой коллекции) можно хранить только те типы, которые соответствуют требуемым возможностям.
- Для полей динамических объектов значение должно содержать ключ + store.
- Для динамических полей значение должно быть сохранено.
- Нельзя хранить текст, содержащий только ключ (например, токен soulbound), в сумке, требующей хранения.
- Это фундаментальное ограничение системы типов Move (source).
Безопасность типов и стирание типов
- Динамические поля стираются во время выполнения. Вы должны знать тип, который хотите получить во время доступа.
- Шаблон безопасности типов:
- Храните метаданные типа (например, перечисление или имя типа) вместе со значением.
- Используйте структуру-оболочку, включающую информацию как об активе, так и о его типе.
- При извлечении проверьте метаданные перед преобразованием.
- Пример шаблона:
- Сохраните список {asset_type: u8,...} и используйте asset_type, чтобы определить, как обращаться с активом.
Паттерны свидетелей и типы фантомов
- Фантомные типы и шаблон-свидетель помогают во время компиляции, а не во время выполнения.
- Вы можете использовать Asset
для разных типов, но вам все равно нужно убедиться, что все T обладают необходимыми способностями для коллекции. - Во время выполнения нельзя извлекать информацию о типе из фантомного типа; необходимо хранить явные метаданные (source).
- Дизайн торговой площадки для смешанных типов активов
-
Переводимые активы (NFT): используйте ключ +, храните их в динамических полях объектов или сумках.
-
Токены Soulbound (только ключ): их нельзя хранить в динамических полях или сумках, требующих хранения. Их следует обрабатывать отдельно, например, сохраняя только метаданные или ссылки (а не сам объект).
-
Настраиваемые ресурсы с ограничениями: вы можете хранить их до тех пор, пока в них есть хранилище ключей +. Если нет, вам нужен отдельный путь обработки.
-
Все значения в динамических полях или пакетах должны храниться (и иметь ключ для полей динамических объектов).
-
В этих коллекциях нельзя хранить типы, используя только ключ.
-
Для обеспечения безопасности типов во время выполнения требуются четкие метаданные.
-
Фантомные типы/шаблоны-свидетели помогают во время компиляции, а не во время выполнения.
Спасибо Xavier.eth за этот вдумчивый и технический вопрос. Вот мой рассказ об ограничениях способностей и динамических полях в Sui Move:
V
✅Да, любое значение (dynamic_field::add<K, V>()
), используемое в поле, store
должно содержать способность.
Это строго соблюдается при проверках Move во время компиляции. Даже вставка несохраненного значения в структуру не поможет — если в каком-либо поле отсутствуетstore
, вся структура также потеряет его.
key + store
✅Хранение разнородных ресурсов с разными возможностями возможно, но только при условии, что все ресурсы в динамическом поле/пакетах должны удовлетворять одному и тому же набору возможностей(обычно).
🔒В таких коллекциях нельзя прямиком хранить активы, связанные с душой и обладающие единственными key
способностями. Вместо этого вы можете:
- Хранитьтолько метаданные(идентификатор, тип enum и т. д.)
dynamic_field::add<AssetID, Metadata>
- Используйте параллельное отображение, как для типов, связанных с душой. - Ведите реестр, основанный на ссылках, в котором отслеживаются списки активов, связанных с soulbound, без сохранения всего объекта.
🧩Фантомные типыпомогаюттолько во время компиляции. Для реальной логики поиска вам по-прежнему понадобятся явные метаданные типа времени выполнения.
🚀Предлагаемая гибридная архитектура:
Bag<AssetID, TransferableAsset>
- для активов с store
Map<AssetID, SoulboundMetadata>
для типов, доступных только под ключAssetWrapper
структура с типомtype_id
илиu8 asset_type
для идентификации типа во время выполнения
Еще раз спасибо! Я с нетерпением жду развития этого рынка!
— г-н Рифат Хоссен
Если вы создаете торговую площадку, поддерживающую несколько типов активов с разными возможностями в Sui Move, ключевая проблема заключается в том, какограничения возможностейMove взаимодействуют с динамическими полямии разнородными данными. store``dynamic_field::add<K, V>()``V
Невозможно напрямую хранить значения, не имея такой store
возможности, поэтому да. При использовании значения должны быть доступны на момент компиляции. store
Это означает, что вы не сможете напрямую хранить что-то вроде токена soulbound, если оно не упаковано в тип, в котором оно есть. Чтобы обойти эту проблему, вы можете использовать структуры-оболочки, которые содержат метаданные об активе или идентификационный номер, но при этом удовлетворяют требуемым возможностям. Например, SoulboundListing<T: key>
оболочка может содержать только ссылку или метаданные, а не весь объект. В гетерогенном хранилище Bag``Table
можно хранить или хранить только один тип в каждом экземпляре, но вы можете сделать это, объединив ресурсы в пользовательский тип, похожий на перечисления, или используя фантомные типы, подобные признакам, для моделирования информации о типах. Нативного полиморфизма вы не получите, но вы можете отслеживать типы активов с помощью ручной маркировки или использовать паттернwitness, в котором фантомные типы и дискриминанты времени выполнения помогают моделировать разделение на уровне типов. Asset<T>``T1
Если вы используете T2
для каждого типа ресурсов, а оба типа — Asset<T1>``Asset<T2>
фантомные, то да, вы можете хранить их и в общем контейнере, удалив их в общую оболочку. Затем вам нужно будет хранить информацию о типе отдельно (например, в type_id
поле), чтобы она соответствовала друг другу при чтении. Это баланс междубезопасностью типа,эффективностью использования газаисложностью проекта. С точки зрения безопасности лучше всего использовать отдельные коллекции для каждого типа активов, но типы оберток и индексированные метаданные более масштабируемы, если вам нужна гибкость. Выбор зависит от того, какое значение для вашего варианта использования имеет стоимость бензина или простота разработки.
Знаете ответ?
Пожалуйста, войдите в систему и поделитесь им.
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.
Заработай свою долю из 1000 Sui
Зарабатывай очки репутации и получай награды за помощь в развитии сообщества Sui.
- Почему BCS требует точного порядка полей для десериализации, когда структуры Move содержат именованные поля?53
- «Ошибки проверки нескольких источников» в публикациях модуля Sui Move — автоматическое устранение ошибок43
- Сбой транзакции Sui: объекты, зарезервированные для другой транзакции25
- Как ограничения возможностей взаимодействуют с динамическими полями в гетерогенных коллекциях?05