Livre blanc · 05
Spécification technique
Résumé normatif d'A-REX Core : conformité, interfaces, sémantique des opérations, contrat des hooks, attestations, règles de stockage et de mise à niveau. Le texte faisant foi est standard/AREX-CORE.md ; ce chapitre le reformule à l'intention des lecteurs du livre blanc.
Conformité
Un jeton conforme DOIT implémenter ERC-1155, ERC-165, IERC7943MultiToken (0x41c4fbad), IARexBatch (0xe22639e0) et exposer son pipeline via IARexHookManager.getHooks() (0x964f4861). Il DEVRAIT implémenter IARexAttest (0xad603a6c) directement ou par l'intermédiaire d'un contrat compagnon. Un hook conforme DOIT implémenter IARexHook (0x332afcbb) et ERC-165. Tous les identifiants sont reproduits par npm run interface-ids.
Constantes fixes
| Constante | Valeur | Signification |
|---|---|---|
OP_MINT | 1 | nouvelles unités (createBatch) |
OP_TRANSFER | 2 | transfert initié par le détenteur |
OP_BURN | 3 | rachat, livraison, passage en pertes |
OP_FORCED_TRANSFER | 4 | mise en application |
OP_TRANSFORM | 5 | entrées (to = 0) et sorties (from = 0) de transform |
HOOK_CHECK | 1 | le hook demande check |
HOOK_EXECUTED | 2 | le hook demande onExecuted |
HOOK_ELIGIBILITY | 4 | le hook répond à canSend / canReceive |
STATUS_ACTIVE | 0 | par défaut |
STATUS_RETIRED | 255 | terminal |
Tout le reste — codes de denrée, codes d'unité, statuts 1..254, codes de rejet, thèmes d'attestation — est ouvert. Les conventions recommandées figurent dans ARexCodes.
Couche 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);
}
Sémantique
createBatch
- DOIT être réservé aux comptes autorisés ; l'autorisation NE DOIT PAS découler du solde de jetons.
- DOIT annuler la transaction avec
ARexInvalidBatchlorsquequantity == 0,commodity == 0,unit == 0, ouexpiresAt != 0 && producedAt != 0 && expiresAt <= producedAt. producedAt == 0DOIT être accepté (avant récolte).- DOIT exécuter le pipeline avec
OP_MINT; DOIT émettreBatchCreatedpuisTransferSingle.
setBatchStatus
- DOIT être réservé à un rôle.
255est terminal. Lots retirés : aucune émission, aucun transfert (ARexBatchRetired), destructions autorisées.
transform
msg.senderDOIT êtrefromou avoir reçu defromune approbation globale.- DOIT annuler la transaction avec
ARexLengthMismatchen cas de longueurs incohérentes et DOIT l'annuler en présence de listes d'entrées ou de sorties vides. - DOIT détruire les entrées, émettre les sorties, définir
parentsOf(output) = inputIds, définirissuer = msg.sendersur les sorties. - DOIT exécuter le pipeline avec
OP_TRANSFORMpour chaque destruction et chaque émission, avecdata = abi.encode(processRef). - DOIT émettre
BatchCreatedpar sortie, les événements ERC-1155 et unBatchTransformed. - La conservation des quantités n'est PAS requise. La référence distingue la réorganisation de lots (même denrée et même unité,
Σin == Σout; n'importe quel détenteur) du traitement (TRANSFORMER_ROLE).
metadata
metadataHashDOIT être lekeccak256du document canonique situé àuri. Le document DEVRAIT suivrestandard/schema/batch-metadata.schema.json.originRefNE DOIT PAS contenir de coordonnées brutes ni d'identifiants de personnes physiques.
Couche 0 — IERC7943MultiToken
Repris tel quel d'ERC-7943 (forcedTransfer, setFrozenTokens, canSend, canReceive, getFrozenTokens, canTransfer ; événements ForcedTransfer, Frozen ; erreurs ERC7943CannotSend, ERC7943CannotReceive, ERC7943CannotTransfer, ERC7943InsufficientUnfrozenBalance). A-REX ajoute ces précisions :
canTransferDOIT valoir faux lorsque le jeton est en pause, lorsque le lot n'existe pas ou est retiré, lorsque l'éligibilité échoue, lorsqueamount > balance − frozen, ou lorsqu'un hook CHECK rejetteOP_TRANSFER.- Les transferts publics NE DOIVENT PAS réussir lorsque
canTransfervaut faux. forcedTransferDOIT être réservé à un rôle, DOIT contourner l'éligibilité, DOIT réduire le montant gelé afin qu'il ne dépasse jamais le solde restant (en émettantFrozen), DOIT exécuter les hooks avecOP_FORCED_TRANSFER, DOIT émettreForcedTransfer, et DEVRAIT fonctionner pendant la pause.canSend/canReceivesont la conjonction de tous les hooksHOOK_ELIGIBILITY.- Un jeton DEVRAIT exposer
transferCheck(from, to, id, amount) → bytes32.
Couche 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;
}
Obligations du jeton : liste ordonnée ; vérifier supportsInterface(0x332afcbb) et mettre en cache les drapeaux non nuls à l'enregistrement ; appeler check pour chaque identifiant et chaque hook CHECK, et annuler la transaction avec ARexHookRejected(hook, code) au premier code non nul avant toute modification de solde ; appeler onExecuted pour chaque identifiant et chaque hook EXECUTED après la mise à jour des soldes ; empêcher la réentrance pendant la séquence ; transmettre data ; garder la liste et les drapeaux publics.
Obligations du hook : drapeaux constants ; check est une fonction de lecture seule (view) qui renvoie des codes et n'annule jamais la transaction pour des motifs de politique ; le jeton appelant est msg.sender ; renvoyer 0 pour OP_FORCED_TRANSFER sauf si la règle régit la mise en application ; onExecuted n'appelle jamais de fonctions modifiant les soldes.
Couche 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 exige un émetteur de confiance pour le thème et un lot existant. isAttested vaut faux si l'attestation est absente, révoquée, expirée, ou si l'émetteur n'est plus de confiance. Les émetteurs révoquent leurs propres attestations ; un administrateur PEUT révoquer n'importe laquelle.
Codes de rejet (recommandés)
keccak256("AREX:" + NAME) pour 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. Codes personnalisés : keccak256("NAMESPACE:NAME").
Caractéristiques de l'implémentation de référence
| Propriété | Valeur |
|---|---|
| Base | OpenZeppelin 5.3.0 (contrats pouvant être mis à niveau) : ERC1155, ERC1155Supply, AccessControl, Pausable, UUPS |
| Stockage | espace de noms ERC-7201 arex.storage.token, emplacement 0x8ed599b6be05dec05297c009a1279a71df771ad996fe924d0b5a47326ff60900 |
| Compilateur | solc 0.8.22, viaIR, EVM paris ; optimizer.runs = 1 pour ARexToken (24,136 octets déployés), 200 ailleurs |
| Rôles | ISSUER, TRANSFORMER, STATUS, ENFORCER, HOOK_ADMIN, PAUSER, UPGRADER, DEFAULT_ADMIN ; ATTEST_ADMIN sur ARexAttestations |
| Réentrance | verrou de stockage autour de toute la séquence _update ; éprouvé par le test ReentrantHook |
| Transmission des données | safeTransferFrom / safeBatchTransferFrom / createBatch mettent data de côté pour les hooks ; transform transmet abi.encode(processRef) |
| Métadonnées | uri(id) renvoie l'URI du lot lorsqu'elle est définie, sinon l'URI de base |
| Tests | 46 dans test/standard : cycle de vie, filiation, gel et transfert forcé, ordre des hooks, rejet, données et réentrance, attestations, identifiants d'interface, autorisation de mise à niveau, hooks métier, extensions, invariants aléatoires, et le guide d'implémentation exécuté de bout en bout |
Gestion des versions du standard
Toute modification d'une signature, d'un événement ou d'une exigence impérative (DOIT) constitue une version majeure accompagnée d'une note de migration. Les recommandations additives (codes, champs de schéma) constituent des versions mineures. Les modifications ne sont acceptées que lorsque deux implémenteurs indépendants confirment leur adoption. Les interfaces ne seront pas soumises en tant qu'ERC avant qu'un déploiement en production ait fonctionné pendant six mois, que trois implémentations de hooks indépendantes existent et qu'un audit par un tiers soit publié.