ALLER AU CONTENU
STANDARDS ETHEREUM7 minStandards en production / Ép. 9 · Niveau 9-3

Transfert groupé : deux règles pour détecter le bug

Un batch est passé alors que le contrôle du token le refusait. Quelles deux règles l’auraient détecté avant que quiconque repère le bug ?

XIN
Scène nocturne en pixel art du poste de contrôle d’un coffre. Une lampe rouge refuse une grande caisse au portail, tandis qu’un tapis roulant fait passer deux petites caisses identiques par le même portail sous une lampe verte, et qu’une étagère cadenassée du coffre est vide.

EN 30 SECONDES

Un invariant est une règle qui doit rester vraie après chaque appel. Deux détectent ce bug : l’exécution n’autorise jamais ce que canTransfer a refusé, et les transferts d’un détenteur n’entament jamais ses tokens gelés.

À RETENIR · 3

  • Première règle : si un transfert s’exécute, canTransfer a dit oui pour le total de chaque token id, pas pour chaque élément.
  • Deuxième règle : les transferts d’un détenteur ne font jamais passer un solde sous le montant gelé, s’il n’y était pas déjà.
  • Appliquez les deux règles à tout chemin qui déplace plusieurs montants : batchs, opérateurs, transferts vers soi, routeurs et burns sans privilège.
Sur cette page

PR n° 2045 des ERC, ouverte au 7 oct. 2026 : voyez son état actuel.

L’invariant qui prouve que le contrôle colle au transfert

EN BREF Deux règles, vérifiées après chaque appel : l’exécution n’autorise jamais ce que canTransfer a refusé, et les transferts d’un détenteur n’entament jamais ses tokens gelés.

LES MOTS À CONNAÎTRE

Invariant
Une règle qui doit rester vraie après chaque appel à un contrat, quel que soit l’ordre des appels.
canTransfer
Une fonction en lecture seule, une « vue », qui répond oui ou non : ce transfert serait-il autorisé ?
Opérateur
Un compte que le détenteur a autorisé à déplacer ses tokens à sa place.
Transfert forcé
Un transfert fait par un rôle autorisé sans l’accord du détenteur.

EN CLAIR

Un test unitaire pour ce cas précis s’écrit facilement une fois le bug connu. Je veux une règle qui l’aurait détecté avant que quiconque le connaisse : un Invariant Une règle qui doit rester vraie après chaque appel à un contrat, quel que soit l’ordre des appels. Voir le glossaire, vrai après chaque appel.
LES DEUX RÈGLES
Le contrôle colle au transfert
Si un batch s’exécute, canTransfer, interrogé par le même appelant avant l’appel, a dit oui pour le total de chaque id, toutes ses entrées additionnées
L’exécution peut être plus stricte que la vue (elle peut encore échouer faute de solde), jamais plus permissive
Les tokens gelés restent en place
Après chaque transfert du détenteur, solde ≥ gelé pour chaque id, si c’était vrai au départ
Un gel supérieur au solde est permis ; sous sa forme générale, la règle dit que les transferts du détenteur n’aggravent jamais l’écart
Pour les transferts du détenteur. Les transferts forcés et les burns privilégiés sont hors périmètre : le standard les autorise à réduire d’abord le montant gelé, ils demandent donc leur propre règle.

La première règle est générale, et ERC-7943 l’énonce noir sur blanc pour les transferts publics, en nommant la vue canTransfer Une fonction en lecture seule, une « vue », qui répond oui ou non : ce transfert serait-il autorisé ? Voir le glossaire :

MUST NOT succeed in cases where canTransfer would return false.

ERC-7943

OBJET OBTENU

La vue est une promesse

Si canTransfer dit non, le transfert doit échouer aussi. Il peut refuser plus que la vue, jamais autoriser plus.

La seconde règle est celle pour laquelle le gel existe : un détenteur ne peut envoyer que ce qui dépasse le montant gelé. La première dépend du total par id : interrogez la vue élément par élément, et cette règle passe sur le code défectueux.

Où la vue et l’exécution peuvent encore diverger

EN BREF Dès qu’un token sépare la vue de contrôle et le chemin de transfert, les deux peuvent diverger : interrogez chaque fonction qui déplace plusieurs montants.

ÉTAPE PAR ÉTAPE

Décision : Chaque élément voit-il le débit du précédent ?Arbre de décision : Chaque élément voit-il le débit du précédent ? Si Oui : il boucle sur un transfert unitaire complet, alors Tokens gelés protégés par construction. Si Non : contrôler chaque élément, puis débiter, alors Additionner par id ; un contrôle après débit peut compléter. Si Impossible de trancher à la lecture, alors Écrire d’abord le handler de test.DÉCISIONChaque élément voit-il ledébit du précédent ?Si oui : il boucle sur untransfert unitaire completTokens gelés protégéspar constructionLancez quand même le test :un transfert vers soi peutcontredire la vueSi non : contrôler chaqueélément, puis débiterAdditionner par id ;un contrôle aprèsdébit peut compléterVérifiez aussi les burns etles mises à jour du geldans la boucleSi impossible de trancher àla lectureÉcrire d’abord lehandler de testPeu d’ids, un drapeaucomparé à la vue

Étape 1. La question. Lisez le chemin batch et demandez-vous : chaque élément voit-il le débit du précédent ?

Choisissez une étape pour la voir bouger
  1. La question Lisez le chemin batch et demandez-vous : chaque élément voit-il le débit du précédent ?
  2. Oui Protégés par construction, mais lancez quand même le test : un transfert vers soi peut contredire la vue.
  3. Non Additionnez par id. Un contrôle après débit, placé avant l’appel au destinataire, peut compléter, mais il rate les transferts vers soi. Vérifiez aussi les burns dans la même boucle.
  4. Pas sûr Écrivez d’abord le handler de test (niveau 4) et laissez-le trancher.
La seule question que je poserais à chacune de ces fonctions.

Le schéma n’est pas propre à ERC-1155. À titre de comparaison, l’implémentation T-REX d’ERC-3643 fait boucler batchTransfer sur transfer() : chaque élément est comparé à un solde déjà débité par le précédent.

ATTENTION

Le canTransfer d’ERC-3643 se trouve sur son contrat de conformité, un contrat distinct qui vérifie les règles, directement ou via des modules. Ce n’est pas la même fonction que celle d’ERC-7943.

Au-delà des batchs, j’appliquerais les deux mêmes règles aux transferts d’Opérateur Un compte que le détenteur a autorisé à déplacer ses tokens à sa place. Voir le glossaire (autorisés via setApprovalForAll), aux transferts vers soi-même et à tout wrapper ou routeur qui appelle le token en boucle. Ajoutez les burns sans privilège : le standard exige (MUST NOT) qu’ils ne brûlent pas plus que le montant non gelé.

ATTENTION

Les chemins privilégiés ont une autre règle, car ils peuvent entamer les tokens gelés. Un Transfert forcé Un transfert fait par un rôle autorisé sans l’accord du détenteur. Voir le glossaire ou un burn privilégié qui le fait doit mettre à jour le montant gelé en conséquence (le contrat de référence le réduit de l’excédent), et émettre Frozen avant l’événement de transfert.

Les constats annexes de l’issue, comme un transfert forcé où from est égal à to, relèvent de ce cas. Vous recrutez pour ce type de contrat ? Exigez ce test dans le brief avant l’audit.

Sources : ERC-7943 (Final), issue n° 1814 des ERC, Token.sol de T-REX, ERC-3643.

FAQ

Deux règles : si un batch passe, canTransfer a dit oui pour le total de chaque token id avant l’appel. Et les transferts d’un détenteur ne font jamais passer son solde sous le montant gelé, s’il n’y était pas déjà. Transferts forcés et burns privilégiés ont leur propre règle.

Non. Le bug se trouve dans l’implémentation de référence, dans assets/erc-7943, au sein du dépôt des ERC. Le texte normatif dit déjà que les tokens gelés ne peuvent pas être transférés par le compte, et que canTransfer doit comparer le montant à la part non gelée.

NIVEAU TERMINÉ

Ce que vous savez maintenant

  • Les deux règles : rien de ce que la vue a refusé ne s’exécute, et aucun transfert du détenteur n’entame ses tokens gelés.
  • Pourquoi la première règle doit porter sur le total de chaque id, pas sur chaque élément.
  • Quels autres chemins vérifier, et pourquoi les chemins privilégiés ont leur règle.

Vérifiez par vous-même

Touchez ou cliquez une question pour voir la réponse.

Un routeur déplace les tokens d’un détenteur en appelant le token dans une boucle. Les deux règles s’appliquent-elles encore ?

Oui. Tout chemin qui déplace les tokens du détenteur doit respecter les deux, et une boucle est justement l’endroit où la vue et l’exécution peuvent diverger.

Niveau suivant :Comment écrire un test Foundry qui vérifie les deux règles sur des batchs aléatoires ?7 min
Adam Boudjemaa
Adam Boudjemaa

Ancien CTO d’Integra. Auteur nommé (1 sur 5) d’ERC-3643, premier auteur d’ERC-6960, co-auteur d’ERC-7410 et co-auteur d’ERC-8203, encore à l’état de brouillon. Conçoit des systèmes d’IA en production et des systèmes Web3 régulés.

Un projet à livrer ou un poste à pourvoir ?

Parlons de votre besoin
Suivre les notes techniques

Recevez les nouveaux articles sur l’IA en production et les smart contracts par email.