Skip to main contentA-REX Core

Whitepaper · 07

Хуки и эталонные модули

Как функциональность добавляется к конформному токену без изменения стандарта: контракт хука, эталонные хуки, сопутствующие расширения и способы написать, протестировать и зарегистрировать собственный хук.

Модули — это демонстрации, а не стандарт

Отношение между A-REX Core и его модулями такое же, как между ERC-3643 и модулями комплаенса T-REX: интерфейс, через который подключаются модули, стандартизирован; модули — нет. Конформный токен без хуков является допустимым токеном A-REX. Хук, написанный кем угодно, совместим с любым конформным токеном. Именно это позволяет экосистеме расти без изменения стандарта.

graph LR
    T["ARexToken<br/>конформный токен"] -->|"check / onExecuted / canSend / canReceive"| H1[EligibilityHook]
    T --> H2[ExpiryHook]
    T --> H3[AttestationHook]
    T --> H4["Пользовательский хук"]
    H3 --> A[ARexAttestations]

Контракт хука

interface IARexHook is IERC165 {
    function hookFlags() external view returns (uint8);   // CHECK=1 · EXECUTED=2 · ELIGIBILITY=4
    function check(uint8 op, address operator, address from, address to, uint256 id, uint256 amount, bytes calldata data)
        external view returns (bytes32 code);            // 0 = allowed
    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);
}
Обратный вызовКогдаДолжен
checkдо изменения балансов, для каждого затронутого id, хуки в порядке регистрации, остановка на первом ненулевом значениибыть view-функцией; никогда не откатываться из-за политики
onExecutedпосле изменения балансов, для каждого id, по порядкуне вызывать функции токена, изменяющие баланс (токен заблокирован)
canSend / canReceiveпри каждом переводе и для view-функций ERC-7943 токенабыть view-функциями

op принимает одно из значений OP_MINT (1), OP_TRANSFER (2), OP_BURN (3), OP_FORCED_TRANSFER (4), OP_TRANSFORM (5). В преобразовании входы приходят с to == 0, а выходы — с from == 0. data — это данные перевода ERC-1155, abi.encode(processRef) для преобразований и пусто для принудительных переводов.

Вызывающий токен — это msg.sender. Хук, которому нужна партия, читает IARexBatch(msg.sender).batchOf(id).

Эталонные хуки

EligibilityHook — разрешение, блокировка, санкции

Демонстрирует HOOK_ELIGIBILITY. Ведёт необязательный список разрешённых адресов, список заблокированных адресов и санкционный список; canSend/canReceive возвращают их конъюнкцию; check возвращает SANCTIONED, SENDER_INELIGIBLE или RECEIVER_INELIGIBLE, чтобы интерфейсы могли объяснить отказ. Принудительные переводы разрешены всегда: изъятие товара со счёта, попавшего под санкции, и есть смысл принудительного исполнения. В промышленной эксплуатации списки наполняются из реестра идентичности (ONCHAINID, EAS, верифицируемые учётные данные) или санкционного оракула; хуку безразлично, из какого.

ExpiryHook — скоропортящесть без отдельного модуля

Не хранит состояния. Читает expiresAt и producedAt из партии. Блокирует эмиссию и переводы просроченных партий (BATCH_EXPIRED), но не сжигание (списание) и не принудительные переводы (принудительное исполнение). Опционально блокирует переводы ещё не произведённых партий (BATCH_NOT_PRODUCED): платформа, финансирующая до уборки урожая, устанавливает флаг так, чтобы разрешать их; спотовая торговая площадка — так, чтобы блокировать. Этот единственный хук покрывает блокирующую часть скоропортящести; кривые порчи становятся вспомогательной view-функцией или офчейн-оценкой.

AttestationHook — регуляторные ограничения по товарам

Сопоставляет код товара с обязательной темой аттестации. Перед переводом читает партию, находит требование, запрашивает у реестра аттестаций isAttested(id, topic) и в противном случае возвращает ATTESTATION_MISSING. Правила «кофе нужна ссылка на должную осмотрительность по EUDR», «партиям с маркировкой органической продукции нужен действующий сертификат органической продукции», «скоту нужен санитарный сертификат» — это по одному вызову setRequirement каждое.

PerishabilityHook — модели порчи

Конфигурация порчи по коду товара с переопределением для отдельной партии; модели NONE, LINEAR (до нижнего порога к моменту истечения срока), EXPONENTIAL (период полураспада) и STEP (ступенчатое снижение через каждый интервал). View-функции valueBps, secondsRemaining, isExpired. Блокирует эмиссию и переводы просроченных партий (BATCH_EXPIRED) и переводы партий, остаточная стоимость которых равна нулю (PERISH:NO_VALUE); никогда не блокирует сжигание или принудительное исполнение.

SeasonalHook — окна сбора урожая и торговли

Абсолютные окна [start, end] по товару и операции (не более 16 на ключ). Сочетание товара и операции без окон не ограничено. Типичное использование: OP_MINT ограничивается окном сбора урожая, OP_TRANSFER — окном торговли; в остальных случаях возвращается SEASON_CLOSED.

DeliveryHook — хранение и расчёты

Отправитель планирует доставку amount единиц получателю через перевозчика; единицы блокируются (счётчик O(1), поэтому check никогда не перебирает историю). Перевозчик и датчики фиксируют забор груза, транзит и показания температуры (флаг нарушения); получатель подтверждает доставку; споры передаются арбитру. Расчёт — это сам перевод ERC-1155: отправитель → получатель, точное количество, с data = abi.encode(deliveryId); check разрешает его только тогда, когда доставка находится в состоянии DELIVERED, а onExecuted переводит её в SETTLED и снимает блокировку. Свободные (неблокированные) единицы продолжают перемещаться.

TraceabilityHook — журнал событий в форме EPCIS

Наблюдает за каждой операцией токена и классифицирует её по бизнес-шагам CBV (commissioning, shipping, destroying, transforming) и состояниям (active, in_transit, destroyed, seized); RECORDER_ROLE записывает критические события отслеживания, не связанные с токеном (охлаждение, упаковка, приёмка), с readPoint и bizLocation. Два режима: только генерация событий (около +17 k газа на перевод; индексатор восстанавливает журнал) или сохранение (около +184 k; getEvents отвечает ончейн, постранично).

Сопутствующие расширения

Три задачи никогда не требовали оформления в виде правил перевода; это обычные контракты, которые читают или записывают аттестации:

РасширениеЧто делает
CommodityPriceOracleрепортёры передают цены по товарам; раунды нумеруются и финализируются по кворуму с медианой либо вручную по достижении минимума; latestAnswer, isStale
CarbonImmobilizationWrapperфиксирует кредиты, иммобилизованные в реестре (реестр, диапазон серийных номеров, винтаж, тонны, доказательство), связывает тонны с партиями, отражает погашение и высвобождение, записывает аттестацию AREX:CARBON:IMMOBILIZED; никогда не эмитирует кредиты
ParametricInsuranceпул ERC-20; полис на партию с темой-триггером; страховое требование выплачивается, когда триггер аттестован до истечения срока

Написание собственного хука

Хук ниже — это contracts/examples/SeasonHook.sol; он компилируется в CI, а test/standard/ImplementationGuide.test.js проверяет его на токене. Для промышленного использования предназначен SeasonalHook, поддерживающий несколько окон на товар и на операцию.

contract SeasonHook is ARexHookBase, Ownable {
    mapping(bytes32 => uint64[2]) public window; // commodity → [open, close]


    constructor(address owner_) Ownable(owner_) {}


    function setWindow(bytes32 commodity, uint64 open, uint64 close) external onlyOwner {
        window[commodity] = [open, close];
    }


    function hookFlags() public pure override returns (uint8) {
        return ARexCodes.HOOK_CHECK;
    }


    function check(uint8 op, address, address, address, uint256 id, uint256, bytes calldata)
        external
        view
        override
        returns (bytes32)
    {
        if (op != ARexCodes.OP_TRANSFER) return ARexCodes.OK; // never block mints, burns or enforcement
        IARexBatch.Batch memory b = IARexBatch(msg.sender).batchOf(id);
        uint64[2] memory w = window[b.commodity];
        if (w[1] == 0) return ARexCodes.OK; // no window configured
        return (block.timestamp >= w[0] && block.timestamp <= w[1]) ? ARexCodes.OK : ARexCodes.SEASON_CLOSED;
    }
}

Практические правила:

  1. Возвращать OK для OP_FORCED_TRANSFER, если хук сам не регулирует принудительное исполнение.
  2. Не блокировать сжигание без явного намерения: сжигание — это доставки и списания.
  3. Держать check дешёвым и ограниченным; он выполняется при каждом переводе каждого id. Никогда не перебирать историю.
  4. Использовать пространства имён для кодов: ARexCodes.code("MYNS", "NAME").
  5. Сохранять флаги константными; токен их кэширует.
  6. Изменения состояния размещать в onExecuted, но никогда в check.

Регистрация и порядок

await token.registerHook(sanctionsHook);   // evaluated first
await token.registerHook(expiryHook);
await token.registerHook(attestationHook); // evaluated last
await token.getHooks();                    // ordered list
await token.removeHook(expiryHook);        // order of the rest preserved

Дешёвые правила с высокой долей отказов следует ставить первыми. HOOK_ADMIN_ROLE следует размещать за мультиподписью или таймлоком: тот, кто контролирует конвейер, контролирует, какие переводы возможны.

Тестирование

test/standard/helpers.js развёртывает эталонный стек. RecordingHook записывает каждый обратный вызов и может получить указание отклонять с любым кодом; ReentrantHook пытается повторно войти в токен из onExecuted и подтверждает работу блокировки. DomainHooks.test.js и Extensions.test.js покрывают перенесённые хуки и сопутствующие контракты; Invariants.test.js выполняет случайную последовательность операций с зафиксированным seed и проверяет сохранение предложения, терминальность статуса и то, что canTransfer предсказывает каждый исход.

Взаимодействие с комплаенсом ERC-3643

Модуль комплаенса ERC-3643 предоставляет moduleCheck(from, to, value, compliance). Хук, который вызывает его для OP_TRANSFER и сопоставляет false с кодом, делает ограничения по странам, лимиты баланса и временные блокировки, написанные для токенов-ценных бумаг, пригодными для повторного использования на токенах партий. Обратное — предоставление хука A-REX токену ERC-3643 — столь же механично. Chainlink Automated Compliance Engine интегрируется по тому же шаблону: его движок политик становится одним хуком.