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
| Constante | Valor | Significado |
|---|---|---|
OP_MINT | 1 | nuevas unidades (createBatch) |
OP_TRANSFER | 2 | transferencia iniciada por el titular |
OP_BURN | 3 | redención, entrega, baja contable |
OP_FORCED_TRANSFER | 4 | ejecución forzosa |
OP_TRANSFORM | 5 | entradas (to = 0) y salidas (from = 0) de transform |
HOOK_CHECK | 1 | el hook requiere check |
HOOK_EXECUTED | 2 | el hook requiere onExecuted |
HOOK_ELIGIBILITY | 4 | el hook responde a canSend / canReceive |
STATUS_ACTIVE | 0 | valor por defecto |
STATUS_RETIRED | 255 | terminal |
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
ARexInvalidBatchcuandoquantity == 0,commodity == 0,unit == 0oexpiresAt != 0 && producedAt != 0 && expiresAt <= producedAt. producedAt == 0DEBE aceptarse (precosecha).- DEBE ejecutar la canalización con
OP_MINT; DEBE emitirBatchCreatedy despuésTransferSingle.
setBatchStatus
- DEBE estar restringido a un rol.
255es terminal. Lotes retirados: sin acuñaciones, sin transferencias (ARexBatchRetired), quemas permitidas.
transform
msg.senderDEBE serfromo estar aprobado para todos porfrom.- DEBE revertir con
ARexLengthMismatchante 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) = inputIdsy establecerissuer = msg.senderen las salidas. - DEBE ejecutar la canalización con
OP_TRANSFORMpara cada quema y cada acuñación, condata = abi.encode(processRef). - DEBE emitir
BatchCreatedpor cada salida, los eventos de ERC-1155 y unBatchTransformed. - 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
metadataHashDEBE ser elkeccak256del documento canónico alojado enuri. El documento DEBERÍA seguirstandard/schema/batch-metadata.schema.json.originRefNO 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:
canTransferDEBE ser falso cuando el contrato está en pausa, cuando el lote no existe o está retirado, cuando falla la elegibilidad, cuandoamount > balance − frozeno cuando algún hook CHECK rechazaOP_TRANSFER.- Las transferencias públicas NO DEBEN tener éxito cuando
canTransferes falso. forcedTransferDEBE estar restringido a un rol, DEBE omitir la elegibilidad, DEBE reducir la cantidad congelada para que nunca supere el saldo restante (emitiendoFrozen), DEBE ejecutar los hooks conOP_FORCED_TRANSFER, DEBE emitirForcedTransfery DEBERÍA funcionar durante la pausa.canSend/canReceiveson la conjunción de todos los hooksHOOK_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
| Propiedad | Valor |
|---|---|
| Base | OpenZeppelin 5.3.0 actualizable: ERC1155, ERC1155Supply, AccessControl, Pausable, UUPS |
| Almacenamiento | espacio de nombres ERC-7201 arex.storage.token, slot 0x8ed599b6be05dec05297c009a1279a71df771ad996fe924d0b5a47326ff60900 |
| Compilador | solc 0.8.22, viaIR, EVM paris; optimizer.runs = 1 para ARexToken (24,136 bytes desplegados), 200 en el resto |
| Roles | ISSUER, TRANSFORMER, STATUS, ENFORCER, HOOK_ADMIN, PAUSER, UPGRADER, DEFAULT_ADMIN; ATTEST_ADMIN en ARexAttestations |
| Reentrada | bloqueo en el almacenamiento alrededor de toda la secuencia de _update; ejercitado por la prueba ReentrantHook |
| Reenvío de datos | safeTransferFrom / safeBatchTransferFrom / createBatch guardan data para los hooks; transform pasa abi.encode(processRef) |
| Metadatos | uri(id) devuelve la URI del lote cuando está definida y, en caso contrario, la URI base |
| Pruebas | 46 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.