Une attaque de frappe infinie se produit lorsqu’un contrat de token permet à un attaquant de créer des tokens sans autorisation valable, sans faire respecter un plafond maximal, ou les deux. L’attaquant frappe un solde arbitraire, puis vend, échange, dépose ou collatéralise ces tokens nouvellement créés avant que les marchés et les intégrations ne puissent réagir.
La conséquence n’est pas seulement l’inflation. La frappe infinie peut détruire la valeur d’échange d’un token, vider les pools de liquidité, et créer des collatéraux non adossés dans les systèmes de prêt ou de trésorerie. Elrond a enregistré une perte de 113,0 M$ le 2022-06-05 lors d’un incident classé comme Infinite Mint, illustrant comment une limite d’émission défectueuse peut entraîner une perte à l’échelle de l’écosystème.
Qu’est-ce qu’une attaque de frappe infinie et comment fonctionne-t-elle ?
Une attaque de frappe infinie exploite un chemin d’émission de tokens qui ne prouve pas de manière fiable qui peut frapper et dans quelle mesure. Une fonction mint publique ou mal protégée permet à un attaquant de créer des soldes que le reste du protocole considère comme des tokens ERC-20 légitimes.
Une fonction de frappe est sûre uniquement lorsque l’autorisation du détenteur qui l’appelle, les conditions d’émission et le comptage de l’offre s’alignent sur la conception économique du protocole. Si n’importe quel appelant peut atteindre une logique équivalente à _mint, ou si un appelant privilégié peut contourner un plafond d’offre, la rareté du token n’est qu’une supposition.
Une attaque de frappe infinie suit généralement cette séquence :
- L’attaquant identifie un chemin d’émission accessible extérieurement ou une vérification d’accès facilement contournable.
- L’attaquant appelle la fonction avec un destinataire et un montant contrôlés par lui.
- Le contrat augmente le solde de l’attaquant et souvent la
totalSupply. - L’attaquant transfère les tokens frappés dans un pool DEX, un marché de prêt, un pont, ou un coffre-fort.
- L’attaquant échange des tokens artificiels contre de vrais actifs ou les utilise en tant que collatéral.
- Les fournisseurs de liquidité, prêteurs et détenteurs de tokens absorbent la perte économique résultante.
Les vulnérabilités liées à la frappe infinie sont particulièrement dangereuses lorsque les soldes de tokens sont acceptés par d’autres contrats sans vérification indépendante de leur origine. Un protocole de prêt ne fait pas de distinction entre les tokens frappés via des émissions prévues et ceux frappés via un contrôle d’accès manquant.
Quels schémas Solidity provoquent des vulnérabilités de frappe infinie ?
Les vulnérabilités de frappe infinie naissent généralement d’un manque d’autorisation, d’absence de limite d’offre, ou d’une autorisation qui, en apparence, est correcte mais accessible via une voie administrative non intentionnelle. Passez en revue chaque fonction pouvant augmenter un solde, directement ou indirectement.
Le schéma le plus évident est une fonction mint publique sans restriction sur l’appelant :
function mint(address to, uint256 amount) external {
balanceOf[to] += amount;
totalSupply += amount;
}
Le même risque existe lorsqu’un développeur contrôle la mauvaise identité. Par exemple, tx.origin ne devrait pas définir une autorité de frappe, car un attaquant peut faire appeler un portefeuille privilégié à un contrat intermédiaire malveillant. De même, vérifier si un destinataire est privilégié ne prouve pas que l’appelant est autorisé à émettre des tokens.
Parmi les autres causes courantes :
- Une fonction
setMinteraccessible à n’importe quel compte, permettant à un attaquant de s’accorder des droits d’émission. - Un proxy upgradable dont l’autorité de mise à jour peut remplacer les restrictions de frappe.
- Un pont ou gestionnaire cross-chain qui fait confiance à des messages arbitraires comme instructions de frappe.
- Un plafond appliqué dans une fonction de frappe mais pas dans d’autres routes d’émission, comme les récompenses ou les migrations.
- Des erreurs arithmétiques ou de conversion d’unités rendant un plafond nominal inefficace.
- Un comptage de brûlures et frappes où les brûlures ne sont pas réellement vérifiées avant de distribuer de nouveaux tokens.
Une variable totalSupply seule ne constitue pas un contrôle de l’offre. Le contrat doit rejeter toute frappe entraînant que totalSupply + amount dépasse le maximum prévu. Cette invariant doit s’appliquer à chaque route d’émission, y compris la récupération administrative, la frappe via pont, les récompenses, et les migrations.
À quoi ressemble un code Solidity vulnérable ou corrigé face à la frappe infinie ?
Un contrat vulnérable à la frappe infinie expose une fonction d’émission non restreinte. Tout compte externe peut frapper un montant quelconque pour lui-même, sans limite de plafond.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract TokenFrappeInfinieVulnérable {
string public constant name = "Token Vulnérable";
string public constant symbol = "VULN";
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
event Transfer(address indexed from, address indexed to, uint256 amount);
function mint(address to, uint256 amount) external {
// VULNÉRABLE : n'importe quel appelant peut créer des tokens arbitraires.
totalSupply += amount;
balanceOf[to] += amount;
emit Transfer(address(0), to, amount);
}
function transfer(address to, uint256 amount) external returns (bool) {
require(balanceOf[msg.sender] >= amount, "solde insuffisant");
unchecked {
balanceOf[msg.sender] -= amount;
}
balanceOf[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
}
La version corrigée sépare l’administrateur des minters autorisés et applique un plafond maximal avant chaque augmentation de solde. Le plafond n’est pas un substitut à l’autorisation, et l’autorisation ne remplace pas un plafond. Les deux contrôles traitent des modes de défaillance différents.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract TokenAvecPlafondEtRôles {
string public constant name = "Token Plafonné";
string public constant symbol = "CAP";
address public immutable admin;
uint256 public immutable maxSupply;
uint256 public totalSupply;
mapping(address => bool) public isMinter;
mapping(address => uint256) public balanceOf;
event Transfer(address indexed from, address indexed to, uint256 amount);
event MinterMisAJour(address indexed minter, bool allowed);
modifier onlyAdmin() {
require(msg.sender == admin, "pas admin");
_;
}
modifier onlyMinter() {
require(isMinter[msg.sender], "pas minter");
_;
}
constructor(uint256 maxSupply_) {
require(maxSupply_ > 0, "plafond nul");
admin = msg.sender;
maxSupply = maxSupply_;
isMinter[msg.sender] = true;
}
function setMinter(address minter, bool allowed) external onlyAdmin {
isMinter[minter] = allowed;
emit MinterMisAJour(minter, allowed);
}
function mint(address to, uint256 amount) external onlyMinter {
// CORRIGÉ : l’émission autorisée ne peut dépasser le plafond immuable.
require(totalSupply + amount <= maxSupply, "plafond dépassé");
totalSupply += amount;
balanceOf[to] += amount;
emit Transfer(address(0), to, amount);
}
function transfer(address to, uint256 amount) external returns (bool) {
require(balanceOf[msg.sender] >= amount, "solde insuffisant");
unchecked {
balanceOf[msg.sender] -= amount;
}
balanceOf[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
}
Si votre protocole prévoit une frappe via récompenses, ponts, partages dans un coffre, ou migrations, une audit de smart-contract et revue de sécurité peut remonter chaque voie d’émission à sa source d’autorisation et à sa limite d’offre avant déploiement.
Comment empêcher les attaques de frappe infinie en Solidity ?
Empêcher les attaques de frappe infinie nécessite une conception d’émission en couches : une autorité explicite, des contraintes d’offre imposées, des pouvoirs administratifs restreints, et des tests prouvant qu’aucune autre voie ne peut créer de soldes. La conception appropriée dépend que l’offre soit fixe, plafonnée, basée sur une émission, adossée à un collatéral, ou sur un pont.
| Technique | Ce qu’elle empêche | Coût en gaz ou complexité | Limitation |
|---|---|---|---|
role-gated mint |
Création arbitraire de tokens par des acteurs externes | Lecture de stockage pour la vérification du rôle | Un minter compromis peut toujours frapper dans son autorité |
| Plafond maximal immuable | Dépasser la quantité prévue dans l’émission | Une vérification arithmétique par frappe | Ne prévient pas une partie autorisée de frapper prématurément le plafond |
| Rôles séparés d’administrateur et de minter | Se donner silencieusement plus d’autorité | Code de gestion de rôles additionnel | L’administrateur reste une frontière de confiance critique |
| Administration multisignature ou avec délai | Changements à un seul clé sur les minters ou la configuration d’émission | Délais opérationnels et gestion governance | Ne corrige pas une logique de frappe déjà déployée et défectueuse |
Chemin centralisé _mint interne |
Vérifications de plafond omises dans d’autres fonctions d’émission | Refonte nécessaire | Les revues doivent toujours examiner chaque route externe vers _mint |
| Surveillance des événements de frappe | Détection retardée d’émissions anormales | Infrastructure de monitoring hors chaîne | La surveillance ne peut pas annuler une frappe malveillante déjà validée |
Pour les tokens à offre fixe, le schéma le plus robuste consiste souvent à frapper l’intégralité de l’allocation lors du déploiement et supprimer toute capacité de frappe ultérieure. Pour les tokens d’émission, définir un calendrier mesurable, faire respecter le plafond global dans la fonction interne d’émission partagée, et s’assurer que chaque fonction externe a un but d’autorisation distinct.
La méthodologie Soken considère la frappe comme une frontière de création d’actifs, et non comme une fonction de commodité. Chaque voie augmentant un solde de token doit comporter un modèle d’autorisation documentée, une limite économique explicite, et une invariant testable.
Quels incidents réels ont été classés comme attaques de frappe infinie ?
Les incidents de frappe infinie enregistrés montrent que les défauts d’émission de tokens affectent aussi bien les actifs établis que les protocoles plus récents, sur plusieurs chaînes. Les incidents ci-dessous sont classés comme Infinite Mint dans la base de données des hacks de DefiLlama.
| Protocole | Date | Montant perdu | Chaîne |
|---|---|---|---|
| Elrond | 2022-06-05 | 113,0 M$ | Elrond |
| Paid Network | 2021-03-05 | 27,0 M$ | Ethereum |
| Gala | 2024-05-20 | 22,3 M$ | Ethereum |
| Haven Protocol | 2021-07-03 | 8,2 M$ | Haven |
| OcelotDex | 2024-09-20 | 4,9 M$ | Ethereum |
| TSR | 2026-06-01 | 2,5 M$ | BSC |
| Raydium AMM | 2026-06-10 | 1,3 M$ | Solana |
| Flooring Protocol | 2026-06-08 | 500 K$ | Ethereum |
Ces incidents partagent une propriété économique fondamentale : une fois que des tokens non adossés entrent sur un marché intégré, l’attaque dépasse le contrat de token. Les pools de liquidité achètent l’offre artificielle, les marchés de prêt peuvent l’accepter comme collatéral, et les ponts ou coffres-forts peuvent propager le solde dans d’autres systèmes.
La question de revue pertinente est donc plus large que « Un attaquant peut-il appeler mint ? » Il s’agit de « N’importe quelle voie non fiable ou trop confiée peut-elle faire reconnaître à la plateforme des tokens non adossés comme des actifs valides ? »
Comment détecter une vulnérabilité de frappe infinie avant déploiement ?
La détection des vulnérabilités de frappe infinie nécessite de cartographier toutes les transitions d’état qui augmentent les soldes, puis de prouver que chaque transition est autorisée et maintient l’invariant de l’offre prévu. Une revue manuelle doit commencer par les contrats de tokens, mais doit continuer dans les ponts, coffres-forts, distributeurs de récompenses, modules de mise à niveau, et helpers administratifs.
Utilisez cette checklist de revue :
- Repérez chaque écriture sur les soldes, parts, compteurs d’offre, et fonctions de frappe internes.
- Vérifiez que chaque chemin d’émission externe possède une autorisation d’appel adaptée à son but.
- Confirmez que chaque chemin applique la même limite ou contrainte d’émission.
- Passez en revue les fonctions qui ajoutent, retirent, ou remplacent des minters et administrateurs.
- Tracez les chemins de mise à niveau, pont, migration, et callback pouvant atteindre la logique de frappe.
- Vérifiez si des comptes privilégiés peuvent modifier le plafond, l’émetteur, ou l’unité d’évaluation de façon imprévue.
- Vérifiez que les événements de frappe et la
totalSupplycorrespondent à la création réelle de soldes. - Testez les intégrations qui acceptent le token en tant que collatéral, actif de liquidité, ou dans un coffre-fort.
Slither est utile pour recenser les fonctions accessibles extérieurement, les changements d’état privilégiés, et les motifs suspects de contrôle d’accès, mais ne peut pas établir que le modèle d’offre d’un protocole est économiquement correct. La réflexion manuelle est essentielle pour les voies d’émission indirectes et l’autorité entre contrats.
Le fuzzing avec Foundry peut tester que des appelants arbitraires ne peuvent pas frapper et que l’offre ne dépasse jamais le plafond. Les tests d’invariance sont particulièrement efficaces car ils expriment directement la propriété de sécurité :
function invariant_supplyNeverExceedsCap() public view {
require(token.totalSupply() <= token.maxSupply(), "limite d’offre dépassée");
}
Une seconde invariant doit assurer que seuls des acteurs approuvés peuvent augmenter l’offre. Dans la méthodologie de revue Soken, cette propriété est testée sur toutes les fonctions publiques, et pas seulement celle nommée mint. Pour plus de détails sur le contrôle d’accès, la comptabilité des tokens, et les risques associés, consultez le Hub Soken.
Insight sécurité : La méthodologie Soken considère que chaque nouvelle unité de token est une responsabilité créée par le protocole. Si le contrat ne peut pas prouver qui a autorisé cette responsabilité et pourquoi elle reste dans le cadre du modèle d’émission, la voie de frappe devient une frontière de sécurité nécessitant une refonte.
Un contrôle de rôle et un plafond d’offre transforment une émission non limitée en une opération encadrée et responsable, mais uniquement si chaque voie de frappe y recourt. La tâche d’ingénierie cruciale consiste à centraliser la création de soldes et rendre toute dérogation impossible par conception.
Avant lancement ou mise à niveau, rédigez l’invariant d’offre prévu en langage clair, implémentez-le dans la route partagée de frappe, et faites respecter avec des tests d’invariance Foundry.