ERC-1155 в Solidity: несколько видов ресурсов
Индивидуальный LibraryPass подходит для номера с одним владельцем. Но библиотеке может понадобиться несколько взаимозаменяемых ресурсов: приглашения на разбор, лабораторные пропуски и учебные значки. Для каждого вида нужно своё количество у каждого адреса. ERC-1155 объединяет несколько идентификаторов токенов в одном экземпляре контракта.
В такой модели id обозначает вид ресурса, а не уникальную физическую единицу. Пять единиц id 17 у R могут сосуществовать с тремя единицами того же id у Q. Метода ownerOf(17) для единственного владельца здесь нет. Состояние читается по паре account–id через balanceOf.
Два разрешённых вида
Создадим отдельный LibraryResources. Администратор может выпускать только два определённых вида. Номер 17 используем как LAB_PASS, номер 18 как REVIEW_INVITE. Их совпадение с числом знакомого courseId является договором этого примера, а не автоматическим общим стандартом связи с CourseCatalog.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract LibraryResources is ERC1155, Ownable {
error InvalidIssue();
uint256 public constant LAB_PASS = 17;
uint256 public constant REVIEW_INVITE = 18;
constructor(address admin)
ERC1155("https://library.example/resources/{id}.json") Ownable(admin) {}
function issue(address to, uint256 id, uint256 amount) external onlyOwner {
if ((id != LAB_PASS && id != REVIEW_INVITE) || amount == 0)
revert InvalidIssue();
_mint(to, id, amount, "");
}
}
Это полный учебный исходник. Домен library.example обозначает пример и не является проверенным хранилищем. Контракт не устанавливается и не исполняется в текущей работе. Для реального ресурса адрес и договор хранения выбираются отдельно, как в уроке о метаданных.
У этой модели нет общего лимита выпуска. Администратор может создавать дополнительные количества разрешённых видов. Наличие числового id и применение известного стандарта не доказывают дефицит ресурса. Если бизнес-правило требует предел, он добавляется явным учётом и проверкой для каждого вида либо всего набора.
Чтение и передача
Предположим, A выдал R пять единиц id 17 и две единицы id 18. Тогда balanceOf(R,17) ожидаемо равен 5, а balanceOf(R,18) равен 2. Запрос id 19 для R вернёт его баланс данного вида, который сейчас нулевой. Отсутствие выпуска не означает отклонение такого публичного чтения.
R передаёт Q две единицы вида 17 через safeTransferFrom(R,Q,17,2,data). После ожидаемого успеха у R остаётся 3, у Q появляется 2, количество вида 18 у R не меняется. Административный owner коллекции и право issue также остаются прежними.
Аргумент data передаётся контрактному получателю в предусмотренный callback. В стандартном токене эти байты не обязаны иметь предметный смысл. Если принимающий сервис ожидает структуру заявки, он должен определить формат и проверять его самостоятельно. Передача произвольного data не превращает его в подтверждённое распоряжение администратора.
Общее разрешение оператору
В базовом ERC-1155 применяется setApprovalForAll. В отличие от показанного ERC-721, здесь нет отдельного стандартного approve для одного id и конкретного количества. Оператор R получает право управлять его ресурсами данной коллекции по установленному общему разрешению.
Поэтому экран с названием «разрешить два пропуска» будет вводить в заблуждение, если фактически подписывается setApprovalForAll(S,true). Само ограничение количества в локальном интерфейсе не ограничивает полномочие S в контракте. Для действительно узкого права требуется иная отдельно спроектированная модель.
При передаче Q операторское разрешение R не становится разрешением Q. Оно продолжает относиться к исходной паре R–S и к оставшимся либо будущим ресурсам R. Отмена происходит явным setApprovalForAll(S,false), если R хочет прекратить такое распоряжение.
Приём контрактом
Стандартные безопасные передачи проверяют приём контрактного получателя. Для одиночного вида вызывается соответствующий single hook, а для набора может потребоваться batch hook. Возвращаемое значение должно соответствовать ожидаемому селектору. Не любое успешное выполнение fallback считается согласием принять ресурс.
В закреплённой реализации состояние балансов обновляется до проверки приёма. Если callback отклоняет операцию, изменения откатываются. Если он принят, собственный код получателя мог выполняться внутри передачи. Приложение, добавляющее отдельное начисление или регистрацию покупки, должно учитывать эту границу исполнения.
Контракт, умеющий принять ERC-721, не автоматически умеет принять ERC-1155. У стандартов разные интерфейсы. Проверка названия кошелька либо наличия кода по адресу не заменяет поддержку нужного hook и доступного маршрута распоряжения принятыми ресурсами.
Метаданные вида
Шаблон URI содержит {id}. Для стандартной подстановки клиент использует шестнадцатеричное представление идентификатора с установленной длиной и формой, а не простую десятичную строку 17. Поэтому сервер, который приготовил только файл 17.json, может оказаться несовместимым с данным шаблоном.
Базовый uri(id) этой реализации возвращает одну и ту же строку шаблона. Он не подтверждает существование файла или успешный выпуск указанного id. Можно определить отдельный override для явных адресов описаний, но тогда следует объяснить новый договор, вместо предположения, что библиотека уже хранит разные строки за нас.
Также базовый контракт не предоставляет totalSupply(id) автоматически. Если приложению нужен текущий общий объём, применяется отдельное расширение или собственный согласованный учёт. Простое чтение баланса одного держателя не отвечает на вопрос о сумме всех держателей.
Для упражнения разберите выпуск id 19, нулевое количество, передачу двух единиц при балансе один и отправку известному контракту без приёмного hook. Затем составьте ожидаемые балансы обоих видов после последовательности допустимых передач. Мы фиксируем ожидаемые результаты по коду, не выдавая их за выполненные проверки.
Ресурс и право расходования
Выпущенный пропуск не расходуется автоматически после посещения урока. Наш контракт хранит количество и позволяет передачу; метод отметки использования отсутствует. Если библиотеке нужна однократная услуга, необходимо определить отдельное действие потребления, его субъекта и связь с серверной выдачей услуги. Скрытая запись в браузере не изменяет баланс токена.
Например, приложение может считать наличие хотя бы одной единицы видом постоянного разрешения. Тогда баланс два не означает два посещения по контракту. Другой продукт мог бы уменьшать количество при каждом использовании, но это потребовало бы явного burn либо escrow-маршрута. Не переносите смысл слова «пропуск» на код без такой договорённости.
Для каждого id сохраните его название и конкретную политику услуги в редакционной документации. Стандарт объединяет технический учёт разных видов, но не делает их предметные свойства одинаковыми.
Для самостоятельного уточнения: ERC1155.sol (5.4.0), eip-1155.
Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.