Skip to main contentA-REX Core

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_MINT1новые единицы (createBatch)
OP_TRANSFER2перевод по инициативе держателя
OP_BURN3выкуп, доставка, списание
OP_FORCED_TRANSFER4принудительное исполнение
OP_TRANSFORM5входы (to = 0) и выходы (from = 0) операции transform
HOOK_CHECK1хуку нужен check
HOOK_EXECUTED2хуку нужен onExecuted
HOOK_ELIGIBILITY4хук отвечает на canSend / canReceive
STATUS_ACTIVE0по умолчанию
STATUS_RETIRED255терминальный

Всё остальное — коды товаров, коды единиц измерения, статусы 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, пока одно промышленное развёртывание не проработает шесть месяцев, не появятся три независимые реализации хуков и не будет опубликован аудит третьей стороной.