Skip to main contentA-REX Core

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

ConstanteValeurSignification
OP_MINT1nouvelles unités (createBatch)
OP_TRANSFER2transfert initié par le détenteur
OP_BURN3rachat, livraison, passage en pertes
OP_FORCED_TRANSFER4mise en application
OP_TRANSFORM5entrées (to = 0) et sorties (from = 0) de transform
HOOK_CHECK1le hook demande check
HOOK_EXECUTED2le hook demande onExecuted
HOOK_ELIGIBILITY4le hook répond à canSend / canReceive
STATUS_ACTIVE0par défaut
STATUS_RETIRED255terminal

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 ARexInvalidBatch lorsque quantity == 0, commodity == 0, unit == 0, ou expiresAt != 0 && producedAt != 0 && expiresAt <= producedAt.
  • producedAt == 0 DOIT être accepté (avant récolte).
  • DOIT exécuter le pipeline avec OP_MINT ; DOIT émettre BatchCreated puis TransferSingle.

setBatchStatus

  • DOIT être réservé à un rôle. 255 est terminal. Lots retirés : aucune émission, aucun transfert (ARexBatchRetired), destructions autorisées.

transform

  • msg.sender DOIT être from ou avoir reçu de from une approbation globale.
  • DOIT annuler la transaction avec ARexLengthMismatch en 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éfinir issuer = msg.sender sur les sorties.
  • DOIT exécuter le pipeline avec OP_TRANSFORM pour chaque destruction et chaque émission, avec data = abi.encode(processRef).
  • DOIT émettre BatchCreated par sortie, les événements ERC-1155 et un BatchTransformed.
  • 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

  • metadataHash DOIT être le keccak256 du document canonique situé à uri. Le document DEVRAIT suivre standard/schema/batch-metadata.schema.json.
  • originRef NE 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 :

  • canTransfer DOIT valoir faux lorsque le jeton est en pause, lorsque le lot n'existe pas ou est retiré, lorsque l'éligibilité échoue, lorsque amount > balance − frozen, ou lorsqu'un hook CHECK rejette OP_TRANSFER.
  • Les transferts publics NE DOIVENT PAS réussir lorsque canTransfer vaut faux.
  • forcedTransfer DOIT ê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 émettant Frozen), DOIT exécuter les hooks avec OP_FORCED_TRANSFER, DOIT émettre ForcedTransfer, et DEVRAIT fonctionner pendant la pause.
  • canSend / canReceive sont la conjonction de tous les hooks HOOK_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
BaseOpenZeppelin 5.3.0 (contrats pouvant être mis à niveau) : ERC1155, ERC1155Supply, AccessControl, Pausable, UUPS
Stockageespace de noms ERC-7201 arex.storage.token, emplacement 0x8ed599b6be05dec05297c009a1279a71df771ad996fe924d0b5a47326ff60900
Compilateursolc 0.8.22, viaIR, EVM paris ; optimizer.runs = 1 pour ARexToken (24,136 octets déployés), 200 ailleurs
RôlesISSUER, TRANSFORMER, STATUS, ENFORCER, HOOK_ADMIN, PAUSER, UPGRADER, DEFAULT_ADMIN ; ATTEST_ADMIN sur ARexAttestations
Réentranceverrou de stockage autour de toute la séquence _update ; éprouvé par le test ReentrantHook
Transmission des donnéessafeTransferFrom / safeBatchTransferFrom / createBatch mettent data de côté pour les hooks ; transform transmet abi.encode(processRef)
Métadonnéesuri(id) renvoie l'URI du lot lorsqu'elle est définie, sinon l'URI de base
Tests46 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é.