Whitepaper · 05
Техническая спецификация
Нормативное резюме A-REX Core: конформность, интерфейсы, семантика операций, контракт хука, аттестации, хранилище и правила обновления. Авторитетный текст — standard/AREX-CORE.md; эта глава пересказывает его для читателей whitepaper.
Конформность
Конформный токен ДОЛЖЕН реализовывать ERC-1155, ERC-165, IERC7943MultiToken (0x41c4fbad), IARexBatch (0xe22639e0) и предоставлять свой конвейер через IARexHookManager.getHooks() (0x964f4861). Ему СЛЕДУЕТ реализовывать IARexAttest (0xad603a6c) напрямую или через сопутствующий контракт. Конформный хук ДОЛЖЕН реализовывать IARexHook (0x332afcbb) и ERC-165. Все идентификаторы воспроизводятся командой npm run interface-ids.
Фиксированные константы
| Константа | Значение | Смысл |
|---|---|---|
OP_MINT | 1 | новые единицы (createBatch) |
OP_TRANSFER | 2 | перевод по инициативе держателя |
OP_BURN | 3 | выкуп, доставка, списание |
OP_FORCED_TRANSFER | 4 | принудительное исполнение |
OP_TRANSFORM | 5 | входы (to = 0) и выходы (from = 0) операции transform |
HOOK_CHECK | 1 | хуку нужен check |
HOOK_EXECUTED | 2 | хуку нужен onExecuted |
HOOK_ELIGIBILITY | 4 | хук отвечает на canSend / canReceive |
STATUS_ACTIVE | 0 | по умолчанию |
STATUS_RETIRED | 255 | терминальный |
Всё остальное — коды товаров, коды единиц измерения, статусы 1..254, коды отказа, темы аттестаций — открыто. Рекомендуемые соглашения приведены в ARexCodes.
Уровень 1 — IARexBatch
interface IARexBatch is IERC165 {
struct BatchInit { bytes32 commodity; bytes32 unit; uint64 producedAt; uint64 expiresAt; bytes32 originRef; bytes32 metadataHash; string uri; }
struct Batch { address issuer; uint64 createdAt; uint8 status; bytes32 commodity; bytes32 unit; uint64 producedAt; uint64 expiresAt; bytes32 originRef; bytes32 metadataHash; string uri; }
event BatchCreated(uint256 indexed id, address indexed issuer, bytes32 indexed commodity, bytes32 unit, uint256 quantity, address to);
event BatchStatusChanged(uint256 indexed id, uint8 previousStatus, uint8 newStatus, bytes32 reason);
event BatchMetadataUpdated(uint256 indexed id, bytes32 metadataHash, string uri);
event BatchTransformed(address indexed operator, uint256[] inputIds, uint256[] inputAmounts, uint256[] outputIds, uint256[] outputAmounts, bytes32 processRef);
error ARexBatchNotFound(uint256 id);
error ARexBatchRetired(uint256 id);
error ARexInvalidBatch(bytes32 reason);
error ARexInvalidStatusTransition(uint256 id, uint8 from, uint8 to);
error ARexLengthMismatch();
function createBatch(BatchInit calldata init, uint256 quantity, address to, bytes calldata data) external returns (uint256 id);
function batchOf(uint256 id) external view returns (Batch memory);
function batchExists(uint256 id) external view returns (bool);
function totalSupply(uint256 id) external view returns (uint256);
function setBatchStatus(uint256 id, uint8 status, bytes32 reason) external;
function setBatchMetadata(uint256 id, bytes32 metadataHash, string calldata uri) external;
function transform(address from, address to, uint256[] calldata inputIds, uint256[] calldata inputAmounts, BatchInit[] calldata outputs, uint256[] calldata outputAmounts, bytes32 processRef) external returns (uint256[] memory outputIds);
function parentsOf(uint256 id) external view returns (uint256[] memory);
}
Семантика
createBatch
- ДОЛЖЕН быть ограничен уполномоченными аккаунтами; полномочия НЕ ДОЛЖНЫ вытекать из баланса токенов.
- ДОЛЖЕН откатываться с
ARexInvalidBatch, когдаquantity == 0,commodity == 0,unit == 0илиexpiresAt != 0 && producedAt != 0 && expiresAt <= producedAt. producedAt == 0ДОЛЖЕН приниматься (до уборки урожая).- ДОЛЖЕН запускать конвейер с
OP_MINT; ДОЛЖЕН генерировать событияBatchCreated, затемTransferSingle.
setBatchStatus
- ДОЛЖЕН быть ограничен ролью.
255терминален. Погашенные партии: никакой эмиссии, никаких переводов (ARexBatchRetired), сжигание разрешено.
transform
msg.senderДОЛЖЕН бытьfromили иметь отfromодобрение на все токены.- ДОЛЖЕН откатываться с
ARexLengthMismatchпри несогласованных длинах и ДОЛЖЕН откатываться при пустых списках входов или выходов. - ДОЛЖЕН сжигать входы, эмитировать выходы, устанавливать
parentsOf(output) = inputIds, устанавливатьissuer = msg.senderу выходов. - ДОЛЖЕН запускать конвейер с
OP_TRANSFORMдля каждого сжигания и каждой эмиссии, сdata = abi.encode(processRef). - ДОЛЖЕН генерировать
BatchCreatedдля каждого выхода, события ERC-1155 и одноBatchTransformed. - Сохранение количества НЕ требуется. Эталонная реализация различает переформирование партий (тот же товар и единица измерения,
Σin == Σout; любой держатель) и переработку (TRANSFORMER_ROLE).
метаданные
metadataHashДОЛЖЕН бытьkeccak256канонического документа по адресуuri. Документ СЛЕДУЕТ строить в соответствии сstandard/schema/batch-metadata.schema.json.originRefНЕ ДОЛЖЕН содержать необработанные координаты или идентификаторы физических лиц.
Уровень 0 — IERC7943MultiToken
Дословно из ERC-7943 (forcedTransfer, setFrozenTokens, canSend, canReceive, getFrozenTokens, canTransfer; события ForcedTransfer, Frozen; ошибки ERC7943CannotSend, ERC7943CannotReceive, ERC7943CannotTransfer, ERC7943InsufficientUnfrozenBalance). A-REX добавляет следующие уточнения:
canTransferДОЛЖЕН возвращать false, когда контракт приостановлен, когда партия не существует или погашена, когда не пройден допуск, когдаamount > balance − frozenили когда любой хук CHECK отклоняетOP_TRANSFER.- Публичные переводы НЕ ДОЛЖНЫ завершаться успешно там, где
canTransferвозвращает false. forcedTransferДОЛЖЕН быть ограничен ролью, ДОЛЖЕН обходить допуск, ДОЛЖЕН уменьшать замороженное количество так, чтобы оно никогда не превышало оставшийся баланс (с генерациейFrozen), ДОЛЖЕН запускать хуки сOP_FORCED_TRANSFER, ДОЛЖЕН генерироватьForcedTransfer, и ему СЛЕДУЕТ работать при приостановке.canSend/canReceive— конъюнкция всех хуковHOOK_ELIGIBILITY.- Токену СЛЕДУЕТ предоставлять
transferCheck(from, to, id, amount) → bytes32.
Уровень 2 — хуки
interface IARexHook is IERC165 {
error ARexHookRejected(address hook, bytes32 code);
function hookFlags() external view returns (uint8);
function check(uint8 op, address operator, address from, address to, uint256 id, uint256 amount, bytes calldata data) external view returns (bytes32 code);
function onExecuted(uint8 op, address operator, address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
function canSend(address account) external view returns (bool);
function canReceive(address account) external view returns (bool);
}
interface IARexHookManager {
event HookRegistered(address indexed hook, uint8 flags, uint256 position);
event HookRemoved(address indexed hook);
error ARexHookAlreadyRegistered(address hook);
error ARexHookNotRegistered(address hook);
error ARexInvalidHook(address hook);
function getHooks() external view returns (address[] memory);
function hookFlagsOf(address hook) external view returns (uint8);
function registerHook(address hook) external;
function removeHook(address hook) external;
}
Обязанности токена: упорядоченный список; проверять supportsInterface(0x332afcbb) и кэшировать ненулевые флаги при регистрации; вызывать check для каждого id и каждого хука CHECK и откатываться с ARexHookRejected(hook, code) на первом ненулевом коде до любого изменения баланса; вызывать onExecuted для каждого id и каждого хука EXECUTED после изменения балансов; предотвращать повторный вход во время этой последовательности; передавать data дальше; держать список и флаги публичными.
Обязанности хука: константные флаги; check — view-функция, возвращающая коды и никогда не откатывающаяся из-за политики; вызывающий токен — это msg.sender; возвращать 0 для OP_FORCED_TRANSFER, если правило не регулирует принудительное исполнение; onExecuted никогда не вызывает функции, изменяющие баланс.
Уровень 3 — IARexAttest
interface IARexAttest is IERC165 {
struct Attestation { address issuer; bytes32 ref; uint64 issuedAt; uint64 expiresAt; bool revoked; }
event Attested(uint256 indexed id, bytes32 indexed topic, address indexed issuer, bytes32 ref, uint64 expiresAt);
event AttestationRevoked(uint256 indexed id, bytes32 indexed topic, address indexed issuer, bytes32 reason);
event TrustedIssuerUpdated(bytes32 indexed topic, address indexed issuer, bool trusted);
error ARexUntrustedIssuer(bytes32 topic, address issuer);
error ARexAttestationNotFound(uint256 id, bytes32 topic);
function attest(uint256 id, bytes32 topic, bytes32 ref, uint64 expiresAt) external;
function revokeAttestation(uint256 id, bytes32 topic, bytes32 reason) external;
function getAttestation(uint256 id, bytes32 topic) external view returns (Attestation memory);
function isAttested(uint256 id, bytes32 topic) external view returns (bool);
function isTrustedIssuer(bytes32 topic, address issuer) external view returns (bool);
}
attest требует эмитента, которому доверяют по данной теме, и существующую партию. isAttested ложен, если аттестация отсутствует, отозвана, истекла либо эмитент больше не является доверенным. Эмитенты отзывают собственные аттестации; администратор МОЖЕТ отозвать любую.
Коды отказа (рекомендуемые)
keccak256("AREX:" + NAME) для PAUSED, FROZEN, SENDER_INELIGIBLE, RECEIVER_INELIGIBLE, SANCTIONED, BATCH_NOT_FOUND, BATCH_RETIRED, BATCH_INACTIVE, BATCH_EXPIRED, BATCH_NOT_PRODUCED, LIMIT_EXCEEDED, DOCUMENT_MISSING, SEASON_CLOSED, IN_TRANSIT, ATTESTATION_MISSING. Пользовательские коды: keccak256("NAMESPACE:NAME").
Сведения об эталонной реализации
| Свойство | Значение |
|---|---|
| База | OpenZeppelin 5.3.0 upgradeable: ERC1155, ERC1155Supply, AccessControl, Pausable, UUPS |
| Хранилище | пространство имён ERC-7201 arex.storage.token, слот 0x8ed599b6be05dec05297c009a1279a71df771ad996fe924d0b5a47326ff60900 |
| Компилятор | solc 0.8.22, viaIR, EVM paris; optimizer.runs = 1 для ARexToken (24,136 байт после развёртывания), 200 для остальных контрактов |
| Роли | ISSUER, TRANSFORMER, STATUS, ENFORCER, HOOK_ADMIN, PAUSER, UPGRADER, DEFAULT_ADMIN; ATTEST_ADMIN в ARexAttestations |
| Реентерабельность | блокировка в хранилище вокруг всей последовательности _update; проверяется тестом ReentrantHook |
| Передача данных | safeTransferFrom / safeBatchTransferFrom / createBatch сохраняют data для хуков; transform передаёт abi.encode(processRef) |
| Метаданные | uri(id) возвращает URI партии, если он задан, иначе базовый URI |
| Тесты | 46 в test/standard: жизненный цикл, родословная, заморозка и принудительный перевод, порядок хуков, отказ, данные и реентерабельность, аттестации, идентификаторы интерфейсов, авторизация обновления, предметные хуки, расширения, рандомизированные инварианты и Руководство по реализации, исполняемое от начала до конца |
Версионирование стандарта
Любое изменение сигнатуры, события или требования уровня «ДОЛЖЕН» означает новую мажорную версию с примечанием о миграции. Аддитивные рекомендации (коды, поля схемы) — минорные. Изменения принимаются только тогда, когда два независимых разработчика реализаций подтверждают внедрение. Интерфейсы не будут поданы как ERC, пока одно промышленное развёртывание не проработает шесть месяцев, не появятся три независимые реализации хуков и не будет опубликован аудит третьей стороной.