Skip to main contentA-REX Core

Whitepaper · 05

Especificación técnica

Resumen normativo de A-REX Core: conformidad, interfaces, semántica de las operaciones, el contrato de hook, atestaciones, almacenamiento y reglas de actualización. El texto vinculante es standard/AREX-CORE.md; este capítulo lo reformula para los lectores del whitepaper.

Conformidad

Un token conforme DEBE implementar ERC-1155, ERC-165, IERC7943MultiToken (0x41c4fbad), IARexBatch (0xe22639e0) y exponer su canalización mediante IARexHookManager.getHooks() (0x964f4861). DEBERÍA implementar IARexAttest (0xad603a6c) directamente o a través de un contrato complementario. Un hook conforme DEBE implementar IARexHook (0x332afcbb) y ERC-165. Todos los ids se reproducen mediante npm run interface-ids.

Constantes fijas

ConstanteValorSignificado
OP_MINT1nuevas unidades (createBatch)
OP_TRANSFER2transferencia iniciada por el titular
OP_BURN3redención, entrega, baja contable
OP_FORCED_TRANSFER4ejecución forzosa
OP_TRANSFORM5entradas (to = 0) y salidas (from = 0) de transform
HOOK_CHECK1el hook requiere check
HOOK_EXECUTED2el hook requiere onExecuted
HOOK_ELIGIBILITY4el hook responde a canSend / canReceive
STATUS_ACTIVE0valor por defecto
STATUS_RETIRED255terminal

Todo lo demás (códigos de producto básico, códigos de unidad, estados 1..254, códigos de rechazo, temas de atestación) es abierto. Las convenciones recomendadas figuran en ARexCodes.

Capa 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);
}

Semántica

createBatch

  • DEBE restringirse a cuentas autorizadas; la autorización NO DEBE derivarse del saldo de tokens.
  • DEBE revertir con ARexInvalidBatch cuando quantity == 0, commodity == 0, unit == 0 o expiresAt != 0 && producedAt != 0 && expiresAt <= producedAt.
  • producedAt == 0 DEBE aceptarse (precosecha).
  • DEBE ejecutar la canalización con OP_MINT; DEBE emitir BatchCreated y después TransferSingle.

setBatchStatus

  • DEBE estar restringido a un rol. 255 es terminal. Lotes retirados: sin acuñaciones, sin transferencias (ARexBatchRetired), quemas permitidas.

transform

  • msg.sender DEBE ser from o estar aprobado para todos por from.
  • DEBE revertir con ARexLengthMismatch ante longitudes incoherentes y DEBE revertir ante listas de entradas o de salidas vacías.
  • DEBE quemar las entradas, acuñar las salidas, establecer parentsOf(output) = inputIds y establecer issuer = msg.sender en las salidas.
  • DEBE ejecutar la canalización con OP_TRANSFORM para cada quema y cada acuñación, con data = abi.encode(processRef).
  • DEBE emitir BatchCreated por cada salida, los eventos de ERC-1155 y un BatchTransformed.
  • La conservación de la cantidad NO es obligatoria. La referencia distingue la reagrupación (mismo producto básico y misma unidad, Σin == Σout; cualquier titular) del procesamiento (TRANSFORMER_ROLE).

metadatos

  • metadataHash DEBE ser el keccak256 del documento canónico alojado en uri. El documento DEBERÍA seguir standard/schema/batch-metadata.schema.json.
  • originRef NO DEBE contener coordenadas en bruto ni identificadores de personas físicas.

Capa 0 — IERC7943MultiToken

Literal de ERC-7943 (forcedTransfer, setFrozenTokens, canSend, canReceive, getFrozenTokens, canTransfer; eventos ForcedTransfer, Frozen; errores ERC7943CannotSend, ERC7943CannotReceive, ERC7943CannotTransfer, ERC7943InsufficientUnfrozenBalance). A-REX añade estas aclaraciones:

  • canTransfer DEBE ser falso cuando el contrato está en pausa, cuando el lote no existe o está retirado, cuando falla la elegibilidad, cuando amount > balance − frozen o cuando algún hook CHECK rechaza OP_TRANSFER.
  • Las transferencias públicas NO DEBEN tener éxito cuando canTransfer es falso.
  • forcedTransfer DEBE estar restringido a un rol, DEBE omitir la elegibilidad, DEBE reducir la cantidad congelada para que nunca supere el saldo restante (emitiendo Frozen), DEBE ejecutar los hooks con OP_FORCED_TRANSFER, DEBE emitir ForcedTransfer y DEBERÍA funcionar durante la pausa.
  • canSend / canReceive son la conjunción de todos los hooks HOOK_ELIGIBILITY.
  • Un token DEBERÍA exponer transferCheck(from, to, id, amount) → bytes32.

Capa 2 — hooks

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;
}

Obligaciones del token: lista ordenada; verificar supportsInterface(0x332afcbb) y almacenar en caché los indicadores distintos de cero en el momento del registro; llamar a check por cada id y por cada hook CHECK y revertir con ARexHookRejected(hook, code) en el primer código distinto de cero antes de cualquier cambio de saldo; llamar a onExecuted por cada id y por cada hook EXECUTED después de los saldos; impedir la reentrada durante la secuencia; reenviar data; mantener públicas la lista y los indicadores.

Obligaciones del hook: indicadores constantes; check es una vista que devuelve códigos y nunca revierte por política; el token que llama es msg.sender; devolver 0 para OP_FORCED_TRANSFER salvo que la regla gobierne la ejecución forzosa; onExecuted nunca invoca funciones que modifiquen saldos.

Capa 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 requiere un emisor de confianza para el tema y un lote existente. isAttested es falso si la atestación está ausente, revocada o caducada, o si el emisor ya no es de confianza. Los emisores revocan sus propias atestaciones; un administrador PUEDE revocar cualquiera.

Códigos de rechazo (recomendados)

keccak256("AREX:" + NAME) para 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. Códigos personalizados: keccak256("NAMESPACE:NAME").

Datos de la implementación de referencia

PropiedadValor
BaseOpenZeppelin 5.3.0 actualizable: ERC1155, ERC1155Supply, AccessControl, Pausable, UUPS
Almacenamientoespacio de nombres ERC-7201 arex.storage.token, slot 0x8ed599b6be05dec05297c009a1279a71df771ad996fe924d0b5a47326ff60900
Compiladorsolc 0.8.22, viaIR, EVM paris; optimizer.runs = 1 para ARexToken (24,136 bytes desplegados), 200 en el resto
RolesISSUER, TRANSFORMER, STATUS, ENFORCER, HOOK_ADMIN, PAUSER, UPGRADER, DEFAULT_ADMIN; ATTEST_ADMIN en ARexAttestations
Reentradabloqueo en el almacenamiento alrededor de toda la secuencia de _update; ejercitado por la prueba ReentrantHook
Reenvío de datossafeTransferFrom / safeBatchTransferFrom / createBatch guardan data para los hooks; transform pasa abi.encode(processRef)
Metadatosuri(id) devuelve la URI del lote cuando está definida y, en caso contrario, la URI base
Pruebas46 en test/standard: ciclo de vida, linaje, congelación y transferencia forzosa, orden de los hooks, rechazo, datos y reentrada, atestaciones, ids de interfaz, autorización de actualizaciones, hooks de dominio, extensiones, invariantes aleatorias y la Guía de implementación ejecutada de principio a fin

Versionado del estándar

Cualquier cambio en una firma, un evento o un requisito obligatorio (DEBE) constituye una versión mayor con una nota de migración. Las recomendaciones aditivas (códigos, campos del esquema) son versiones menores. Los cambios solo se aceptan cuando dos implementadores independientes confirman su adopción. Las interfaces no se presentarán como ERC antes de que un despliegue en producción haya funcionado durante seis meses, existan tres implementaciones independientes de hooks y se publique una auditoría de terceros.