Salut à toi ! Si tu as atterri ici, c’est soit que la sécurité des smart contracts te passionne de ouf, soit que tu es en train de scale l'architecture d'un tout nouveau protocole DeFi avec le petit coup de stress qui va avec, en te disant que demain tout ton build peut partir en fumée à cause d'une vulnérabilité complètement stupide.
Écoute, ça fait un moment que je tourne dans l'écosystème. J'ai commencé par poncer des hackathons où on codait des exploits à l'arrache sur un coin de table, nourris à la pizza et aux energy drinks, pour finir aujourd'hui au poste de CTO d’un exchange crypto. Et s’il y a bien une leçon que je retiens de tous les gros hacks que j'ai dû décortiquer (et de ceux que j’ai pu avoid de justesse pendant des sessions d'audit), c'est ça : la majorité des reks massifs ne viennent pas d'une faille dans la cryptographie. Ce n’est pas non plus le compilateur Solidity qui a vrillé. Ça vient d'un manque total de compréhension de la façon dont les standards ERC réagissent lorsqu'ils s'interfacent entre eux.
On a trop tendance à croire que les specs d'un standard font foi et garantissent la sécurité. Sauf que le diable se cache dans les détails d’implémentation et les effets de bord un peu chelou. Regardons d'un peu plus près là où les architectes se mangent le plus souvent le tapis, et voyons comment hardener ton système pour que tu puisses enfin dormir sur tes deux oreilles.
1. La face cachée de l'ERC-20 : Le grand classique qui fait mal
On pourrait croire que l’ERC-20 a été poncé dans tous les sens et n'a plus de secrets. Qu’est-ce qui pourrait mal tourner ? Absolument tout, à partir du moment où tu intègres les jetons des autres dans ton protocole sans mettre en place des checks ultra-strikes.
Le problème de la valeur de retour absente (The No-Return Dilemma)
Si on suit la EIP à la lettre, les fonctions transfer et transferFrom doivent obligatoirement renvoyer un bool. Sauf qu'en pratique, un paquet de tokens historiques et ultra-lourds (gros big up à l'USDT et au BNB sur leurs vieux contrats) n'en ont absolument rien à foutre. Ils ne retournent strictement rien quand le transfert se passe bien.
Si ton contrat attend sagement un bool via une interface standard comme celle-ci :
// À ne JAMAIS faire si tu ship un protocole avec des tokens arbitraires !
IERC20(token).transferFrom(msg.sender, address(this), amount);Et bien dès que ça va call de l'USDT, ta transaction va tout simplement revert. L'EVM va chercher la valeur de retour sur la pile, ne va rien trouver, et va panic. Pire encore : si tu oublies complètement de check le résultat (en faisant un bête token.transfer(...) au lieu de le wrap dans un require(token.transfer(...))), certains tokens vont te renvoyer un vieux false au lieu de trigger un revert en cas d'erreur. Ton contrat va continuer sa route comme si tout était safe. Résultat ? L'utilisateur vient de se print un solde interne magique à partir de rien.
Le fix : Oublie définitivement les calls directs à transfer et transferFrom. Utilise uniquement la librairie SafeERC20 d'OpenZeppelin avec ses méthodes safeTransfer et safeTransferFrom. Sous le capot, elle gère les vérifications des données de retour au niveau low-level et deal proprement avec les contrats non-conformes.
Weird ERC-20 Tokens : Quand le standard part en vrille
Voici un petit pense-bête des tokens exotiques qui ne se comportent pas du tout comme dans les manuels. En tant qu'architecte, tu as l'obligation de setup ça dans la logique de tes pools de liquidité.
| Type de token (Weird ERC-20) | C'est quoi le trick ? | Le risque pour ton architecture |
|---|---|---|
| Deflationary / Fee-on-Transfer (ex: STA, PAXG) | Ils prélèvent une taxe directement à la volée pendant le transfert. | Tu penses que ton contrat vient de recevoir 100 tokens, mais en réalité seuls 99 ont hit le wallet. Ta comptabilité interne est cassée, ce qui crée un déficit de liquidité. |
| Upgradable Proxies (ex: USDC, USDT) | La logique du jeton peut être modifiée instantanément par les admins. | L'implémentation de Blacklists. Si l'adresse de ton contrat se fait ban, toute ta liquidité se retrouve bloquée à tout jamais à l'intérieur. |
| Rebasing Tokens (ex: AMPL) | Le solde sur les wallets varie de façon dynamique (le supply s'ajuste pour stabiliser le prix). | Le token balance de ton contrat peut dump ou pump tout seul, sans qu'aucune fonction de transfert n'ait été call. |
2. ERC-721 et ERC-1155 : Le piège du onERC721Received et de la Reentrancy
Ah, là on touche à mon sujet "préféré". Le nombre de marketplaces NFT et de protocoles de lending qui se sont fait totalement rincer à cause des fonctions de safe transfer, c'est tout simplement hallucinant.
Quand tu appelles safeTransferFrom sur un contrat ERC-721 ou ERC-1155, le token va check si le receiver est un smart contract. Si c'est le cas, il va trigger un hook de callback chez le destinataire : onERC721Received ou onERC1155Received.
Le but ? S'assurer que le contrat cible sait manipuler les NFT pour éviter qu'ils ne restent bloqués à tout jamais.
Le piège ? Ce hook redonne la main à un code externe totalement inconnu et non contrôlé en plein milieu de ta transaction, juste avant que tu n'aies eu le temps d'update l'état interne de ton propre contrat !
Exemple de code de Mint / Marketplace vulnérable
Regarde attentivement ce snippet. Je l’ai codé exprès pour mettre en évidence une erreur d'architecture ultra-classique : la violation pure et simple du pattern Checks-Effects-Interactions.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
contract VulnerableNFCLending {
// On track le collatéral : Utilisateur => ID du Token => Statut actif
mapping(address => mapping(uint256 => bool)) public hasCollateral;
IERC721 public nftToken;
constructor(address _nft) {
nftToken = IERC721(_nft);
}
// L'utilisateur veut déposer un NFT en collatéral pour emprunter
function depositCollateral(uint256 tokenId) external {
// 1. Interactions: On transfert le NFT vers le contrat
// safeTransferFrom va trigger le hook onERC721Received sur le contrat du receiver ?
// Attends, ici le receiver c'est NOUS. Mais si le contrat call safeMint...
// Pivotons plutôt le contexte sur un scénario où on release le NFT ou quand un attacker hijack le flow.
// Réécrivons le scénario : Le contrat renvoie un NFT (comme un retrait de collatéral)
// ou c'est un contrat de mint qui transfert avant d'update l'état.
}
}Laisse-moi plutôt te montrer un exemple bien clean avec une fonction de mint vulnérable, ce sera beaucoup plus flagrant. Disons qu'on a un contrat qui permet de mint un seul NFT gratuit par wallet.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract VulnerableMint is ERC721 {
mapping(address => bool) public hasMinted;
uint256 public currentTokenId;
constructor() ERC721("DangerNFT", "DNFT") {}
function freeMint() external {
// Checks
require(!hasMinted[msg.sender], "Brother, you already have one!");
// Interactions (À l'intérieur de _safeMint se cache un call vers un contrat externe !)
_safeMint(msg.sender, currentTokenId);
currentTokenId++;
// Effects (L'update de l'état interne arrive BEAUCOUP TROP TARD)
hasMinted[msg.sender] = true;
}
}Et maintenant, voici le contrat de l'attaquant. Il va tout simplement catch ce fameux hook, voir que la variable hasMinted sur le contrat ciblé est toujours à false, et va spammer l'appel à freeMint en boucle récursive jusqu'à ce que la collection soit vidée ou qu'il soit out of gas.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IVulnerableMint {
function freeMint() external;
}
contract Attacker {
IVulnerableMint public target;
uint256 count;
constructor(address _target) {
target = IVulnerableMint(_target);
}
function attack() external {
target.freeMint();
}
// Le fameux hook foireux trigger par le standard ERC-721
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external returns (bytes4) {
if (count < 5) {
count++;
// Reentrancy en plein vol ! L'état interne de la victime n'a pas encore bougé
target.freeMint();
}
return this.onERC721Received.selector;
}
}Putain, le nombre de fois où j'ai catch ce pattern exact pendant des audits. Les devs se disent toujours : "Tranquille, c’est juste un transfert de NFT, c'est pas comme si on transférait de l'ETH natif, qu’est-ce qui peut bien se passer ?". Ce qui se passe, c'est que tu te fais siphoner l'intégralité de ton protocole.
La règle d'or de l'architecte : Modifie TOUJOURS ton état interne en premier (hasMinted[msg.sender] = true;) avant de lancer le moindre call de mint ou de transfert. Et s'il te plaît, prends l'habitude de coller systématiquement le modificateur nonReentrant d'OpenZeppelin sur toutes tes fonctions qui touchent à des transferts de NFT.
On enchaîne. Maintenant qu'on a bien décortiqué la Reentrancy via les hooks, on va s'attaquer à un problème beaucoup plus récent et vicieux. C’est le genre de faille à laquelle peu de devs pensent au moment de poser l’architecture – jusqu'au jour où tout explose en prod.
3. ERC-2612 (Permit) : Ghost Approvals et attaques de Front-running
Le standard ERC-2612 a apporté un énorme vent de fraîcheur dans le Web3 avec la fonction permit. Elle permet aux utilisateurs de signer un message offline (EIP-712) pour valider une autorisation de dépense (allowance), tout en déléguant les frais de gas à un relayer ou au protocole lui-même. Niveau UX, c'est le jour et la nuit : au lieu de se taper deux transactions reloues (approve + transferFrom), l’user plie ça en un seul clic.
Sauf que pas mal d'architectes oublient comment cette signature tourne concrètement sous le capot, et ils laissent des failles logiques fatales.
Front-running de signature (Signature Front-running)
Prends l'exemple classique d'une fonction de dépôt dans un smart contract qui utilise permit :
function depositWithPermit(
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
// D'abord, on exécute le permit avec la signature fournie par l'user
IERC20Permit(token).permit(msg.sender, address(this), amount, deadline, v, r, s);
// Ensuite, on pull les tokens
IERC20(token).transferFrom(msg.sender, address(this), amount);
// On mint les points internes / parts du pool
_mintShares(msg.sender, amount);
}Elle est où, la couille ? N'importe quel bot MEV qui scanne le mempool public va capter cette transaction en attente. Le bot a juste à extraire la signature valide (v, r, s) ainsi que les paramètres de ton appel, fabriquer sa propre transaction pour appeler token.permit(...) directement sur le contrat du token, et front-run l'utilisateur en balançant un Gas Price plus élevé.
La TX du bot passe en premier. La signature est consommée avec succès, l'allowance est posée. Juste après, la transaction de l'user honnête arrive sur la blockchain. Mais vu que la signature a déjà servi, le nonce de l'utilisateur sur le contrat du token a été incrémenté ! L'appel à permit à l'intérieur de depositWithPermit va direct revert parce que la signature est devenue obsolète.
Le résultat ? La transaction de l'utilisateur crash, il crame du gas pour rien, son UX est flinguée, et si c'était un ajout de marge ultra-critique pour sauver un levier long de la liquidation, le délai peut lui faire perdre ses fonds instantanément.
Comment blinder son architecture ?
Il faut wrap l'appel au permit dans un bloc try/catch. Si la signature a déjà été balancée sur le réseau par un front-runner, le contrat du token affiche déjà le bon allowance. Ton contrat doit simplement ignorer l'erreur de signature dupliquée et tracer sa route pour enchaîner sur le transferFrom.
try IERC20Permit(token).permit(msg.sender, address(this), amount, deadline, v, r, s) {}
catch {
// Si ça a revert, c'est fort possible que la signature ait été front-run.
// On vérifie si l'allowance actuel est déjà suffisant pour check l'opération.
require(IERC20(token).allowance(msg.sender, address(this)) >= amount, "Permit failed and allowance insufficient");
}Le problème du "Phantom Permit"
Ça, c'est une vraie douleur d'insider dont on parle très peu dans les docs. Qu'est-ce qui se passe si ton protocole DeFi accepte n'importe quel token exotique, et qu'un utilisateur tente un depositWithPermit sur un asset qui NE gère PAS l'ERC-2612 ?
Tu te dis sûrement : "Bah, la TX va fail vu que la fonction permit n'existe pas là-bas". Détrompe-toi, pas à tous les coups !
Si le contrat du token possède une fonction générique fallback() ou receive() qui ne revert pas quand on l'appelle avec un sélecteur inconnu (un comportement typique sur certains proxies ou sur de vieux tokens comme des anciennes versions du WETH), l'appel à permit va répondre que tout s'est bien passé (il renvoie success = true), alors qu'en réalité, aucune autorisation n'a été accordée.
Derrière, ton contrat va enchaîner sur le transferFrom, qui va se rétamer sauf s'il y avait un vieil approve qui traînait. Mais si cette vulnérabilité se croise avec une logique où l'allowance est checkée via d'autres flows, il y a moyen de se faire reked proprement. Vérifie toujours que le token cible implémente bien l'IERC20Permit via l'ERC-165, ou alors restreins ça sévèrement avec une whitelist de tokens.
4. ERC-3156 (Flash Loans) : Risque de manipulation de solde au sein d'une même transaction
Les flash loans sont des outils surpuissants, mais ils viennent briser toutes les hypothèses de timing sur lesquelles les architectes logiciels traditionnels se reposent. Au cours d'une seule et unique transaction atomique, un attaquant peut emprunter des dizaines de millions de dollars, manipuler des états, et rendre les fonds dans la foulée.
L'erreur d'architecture la plus fatale ici, c'est d'utiliser la fonction balanceOf(address(this)) pour calculer le prix des parts d'un pool ou la valeur d'un actif.
// ERREUR ARCHITECTURALE CATASTROPHIQUE
function getSharePrice() public view returns (uint256) {
// Le prix de la part dépend directement du solde actuel de tokens sur le contrat
return token.balanceOf(address(this)) / totalShares;
}Si ton contrat permet de faire des Flash Loans sur ce même token, dès que l'emprunteur récupère les fonds, le solde du contrat s'effondre pour atteindre quasiment zéro. Si à ce moment précis (à l'intérieur du callback onFlashLoan), ton protocole laisse la porte ouverte à d'autres opérations – comme des liquidations ou des calculs de rewards – le prix de la part calculé sera complètement biaisé.
L'attaquant prend un flash loan -> le solde du pool s'effondre -> le prix de la part tombe au sous-sol -> l'attaquant utilise un second wallet pour racheter des parts à un prix dérisoire -> le flash loan est remboursé -> le solde du pool revient à la normale -> l'attaquant dump ses parts au prix standard. Bim, pool siphonné.
La règle d'or : Ne te repose jamais sur balanceOf(address(this)) pour des calculs économiques ou mathématiques critiques si ce solde peut être altéré temporairement sans modifier l'état logique profond du système. Mets en place une comptabilité interne (internal accounting) avec une variable d'état du style uint256 internalReserve, mise à jour uniquement lors des dépôts et retraits officiels et contrôlés.
Bon, passons aux trucs qui me font flipper grave en tant que responsable de la sécu d'une info de CEX. On va parler de standards tout frais où les dev ne se sont pas encore pris les pieds dans le tapis des centaines de fois.
5. ERC-4337 (Account Abstraction) : Les pièges des transactions packagées et du Paymaster
L'Account Abstraction c’est lourd, y a pas de débat. On lâche enfin les EOA (les wallets classiques) pour passer sur des smart contracts en guise de wallets utilisateurs. Plus besoin de se faire chier avec une seed phrase, on peut setup du social recovery et payer le gas en stablecoins grâce aux Paymasters.
Sauf que pour l'architecte qui doit intégrer de l'ERC-4337 dans son protocole, ça ouvre la porte à des vecteurs d'attaque bien spécifiques et hyper vicieux.
La faille de signature sur validateUserOp
Dans l'ERC-4337, le point névralgique de la validation custom du wallet, c’est la méthode validateUserOp. Son taf, c'est de checker la signature de la transaction et de renvoyer un statut bien précis.
// Exemple ultra-simplifié de la logique de validation dans un smart wallet
function validateUserOp(
UserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external returns (uint256 validationData) {
// Erreur critique : on fait confiance à un appel venant de N'IMPORTE QUEL émetteur ?
// Non, le Bundler appelle ça via l'EntryPoint. Mais si on oublie le check...
require(msg.sender == entryPoint, "Only EntryPoint can trigger validation");
// Validation maison de la signature
if (_verifySignature(userOp, userOpHash)) {
// On return 0 si la validation est OK
return 0;
}
// On return SIG_VALIDATION_FAILED (généralement 1) en cas d'erreur
return 1;
}Attends, tu vois le loup là ? Selon la spec ERC-4337, si la validation de la signature foire, la fonction ne doit SURTOUT PAS faire de revert. Elle doit renvoyer une valeur packagée spécifique (une constante d'erreur) pour que l'EntryPoint (le contrat chef d'orchestre) comprenne que la transaction n'est pas valide. Comme ça, il ne ponctionne pas de gas au wallet et se contente de drop l'opération au niveau du Bundler.
Si par réflexe de dev Solidity tu balances un gros require(isValid, "Invalid signature");, tu vas te faire défoncer. Lors d'un envoi groupé (batch) de transactions, un seul revert sec sur une TX va faire sauter tout le package du Bundler. Résultat ? Ton wallet ou ton Paymaster se fait ban par les bundlers, et tes users se retrouvent bloqués, impossibles de passer une TX. La logique de validation doit être atomique et coller au millimètre à la logique mathématique des valeurs de retour de l'ERC-4337, pas aux patterns Solidity classiques.
Attaque sur le Paymaster (Gas Draining)
Si ton protocole DeFi joue le rôle de Paymaster (par exemple, si tu subventionnes le gas de tes utilisateurs pour qu'ils s'ambiancent sur ton DEX sans frais), tu dois impérativement blinder l'étape de validation et la couper du monde extérieur.
Dans le contrat du Paymaster, il y a la méthode validatePaymasterUserOp. À l'intérieur, il est **strictement interdit** d'utiliser un état dynamique qui pourrait bouger entre le moment où le bundler simule la transaction et celui où elle est inscrite dans le bloc. Genre, tu ne peux pas call des oracles de prix (Chainlink) en plein milieu de la validation du paymaster pour calculer combien de tokens prélever au user pour le gas.
**Pourquoi ?** Un attaquant peut push une transaction : pendant la simulation, l'oracle donne un prix qui fait passer la validation tranquille, mais juste avant l'inclusion dans le bloc, le hacker manipule le prix de l'oracle (via un flash loan ou un arbitrage éclair). La validation on-chain va revert, mais le bundler a déjà taffé et consommé du gas. C'est ton paymaster qui va raquer les frais de gas, et le user ne paiera que dalle. Ton wallet de gas se fait siphonner à sec en quelques heures.
6. ERC-4626 (Tokenized Vaults) : Front-running du premier dépôt (Inflation Attack)
L'ERC-4626, c'est le standard ultime pour les vaults tokenisés (staking, pools de rendement, lending). Ça a standardisé les méthodes deposit, mint, withdraw et redeem. C'est magique parce que maintenant, n'importe quel agrégateur à la Yearn peut brancher un nouveau pool en 5 minutes chrono.
Sauf que dans le design mathématique même du standard, il y a une mine antipersonnel connue sous le nom d'**Inflation Attack**. Elle cible les pools au moment précis où ils viennent d'être déployés et que leur balance est encore à zéro.
Mécanique de l'attaque
La formule pour calculer le nombre de parts (shares) qu'un utilisateur récupère en déposant des actifs (assets) ressemble généralement à ça :
$$\text{shares} = \frac{\text{assets} \times \text{totalShares}}{\text{totalAssets}}$$
Si le pool est vide (totalShares == 0), alors par défaut shares == assets. On est sur un ratio propre de 1 pour 1.
Maintenant, regarde bien le tour de passe-passe du hacker :
- Un utilisateur legit envoie une transaction
depositde 1 000 USDC sur un pool ERC-4626 tout neuf et complètement vide. - Le hacker repère la TX dans la mempool et la front-run en payant plus de gas. Il dépose seulement 1 wei d'USDC dans le pool. Le pool lui mint direct 1 wei de shares. On se retrouve avec
totalShares = 1ettotalAssets = 1. - Ensuite, dans cette même transaction atomique, le hacker fait un transfert direct (via un
transferERC20 classique, sans passer par la fonctiondeposit) d'une somme énorme—disons 10 000 USDC—vers l'adresse du contrat du pool. - Qu'est-ce qui arrive aux maths du pool ?
totalSharesvaut toujours 1, maistotalAssetsaffiche désormais 10 001 USDC (le transfert direct a gonflé la balance du contrat sans émettre de nouvelles shares). Le prix d'une seule share vient de s'envoler sur la lune. Enfin, la transaction du user legit à 1 000 USDC s'exécute. Le contrat calcule ses shares avec la formule :
$$\text{shares} = \frac{1000 \times 1}{10\,001} = 0$$
À cause de l'arrondi à l'inférieur (la division entière) de Solidity, l'utilisateur reçoit exactement 0 share ! Par contre, ses 1 000 USDC sont bel et bien encaissés sur la balance du pool.
- Le hacker n'a plus qu'à faire un
withdrawde sa seule et unique share (1 wei de shares) pour s'offrir un braquage en règle : il récupère ses 10 000 USDC, son wei d'origine, plus les 1 000 USDC volés au user s'étant fait rekt.
Solution d'architecture : Pour se prémunir de ça, il y a deux écoles. La première : au déploiement du pool, tu mint de force de la "liquidité fantôme" (dead shares) vers l'adresse zéro (tu bloques les 1 000 premiers wei de shares là-bas, comme ce qui se fait sur Uniswap V2). La deuxième : utiliser les dernières libs d'OpenZeppelin qui intègrent une protection via des offsets virtuels (virtual assets et virtual shares). Ça empêche le dénominateur de la fraction de tomber à zéro ou à un lors des manipulations de solde.
Écoute, on va passer au niveau supérieur. On a déjà parlé des tokens, des NFTs, des permits et des vaults. Mais comment assembler tout ça dans une architecture cohérente sans devenir fou à l'intégration ?
Quand tu conçois un système d'envergure, comme un agrégateur de rendement ou un bridge cross-chain, tu dois jongler avec tous ces standards en même temps. C'est là que l'effet de synergie des vulnérabilités frappe : deux features parfaitement sûres individuellement peuvent, une fois combinées, créer une faille critique.
7. Matrice des risques architecturaux en conception système
Pour que tu aies une vision claire, j'ai préparé ce tableau. Vois ça comme une checklist pour ton prochain "Architecture Review". Garde ça dans ton Notion ou imprime-le.
| Standard ERC | La menace cachée majeure | Impact sur la logique | Comment mitiger au design ? |
|---|---|---|---|
| ERC-20 | Valeur de retour manquante / Transfert non standard | Transaction qui plante (revert) ou erreur ignorée silencieusement | Utiliser exclusivement SafeERC20 (OpenZeppelin). |
| ERC-20 (Weird) | Fee-on-Transfer / Changement de balance (Rebase) | Désynchronisation entre la comptabilité interne et le solde réel du contrat | Calculer la différence balanceAfter - balanceBefore au lieu de faire confiance à l'argument amount. |
| ERC-721 / 1155 | Détournement de contrôle via les hooks onERC...Received | Reentrancy avant la mise à jour de l'état interne (state) | Suivre scrupuleusement le pattern Checks-Effects-Interactions + modificateur nonReentrant. |
| ERC-2612 | Frontrunning de signatures dans le mempool | Déni de service (DoS) pour les utilisateurs légitimes | Envelopper les appels permit dans des blocs try/catch. |
| ERC-3156 | Vidage temporaire de pool (Flash Loan) | Manipulation des prix spot basés sur balanceOf | Utiliser des variables de réserve internes (internal reserves) plutôt que le solde brut. |
| ERC-4337 | Revert brutal lors de la validation par batch | Contrat/Wallet banni par les bundlers | Retourner des constantes d'erreur magiques au lieu de faire échouer la TX avec require. |
| ERC-4626 | Inflation Attack (Exploit sur le 1er dépôt) | Arrondi des parts (shares) à zéro, vol des fonds du premier déposant | Minter des "parts mortes" (dead shares) à address(0) à l'initialisation ou utiliser des offsets virtuels. |
8. Thoughts & Règles d'or pour une architecture safe
Tu sais, après trois ans en tant que CTO, j'ai compris un truc. Le code le plus sûr, c'est celui qui n'est pas écrit. Plus ton schéma d'architecture est complexe, plus il y a de dépendances cachées, et plus tu as de chances qu'un petit génie du hackathon trouve une faille à laquelle tu n'avais même pas pensé en buvant ton café du matin.
Si je devais te donner seulement trois règles pour éviter que ton projet finisse à la une de Rekt News, ce serait celles-ci :
- Ne fais jamais confiance aux contrats externes. Peu importe si c'est le token le plus populaire du marché. Demain, leurs admins mettent à jour le proxy, ajoutent une blacklist, et ton système est bloqué. Code en partant du principe que chaque token externe est un acteur malveillant et imprévisible.
- State d'abord, transferts ensuite. Je ne le répéterai jamais assez. C'est la base apprise dès le premier jour, mais les gens continuent de transférer des tokens avant de mettre à jour leurs mappings. Modifie tes droits et soldes en interne, commite ça sur la blockchain, et fais les
transfer,safeMintoucallexternes uniquement en tout dernier lieu. - Isole ta logique mathématique du solde externe. La balance de ton contrat en EVM est publique et ultra facile à manipuler. N'importe qui peut t'envoyer des millions via un flash loan ou forcer un envoi d'ETH via
selfdestructsur un autre contrat. Si ton calcul de récompenses ou de prix des parts dépend du solde actuel du contrat, t'es déjà mort. Ta comptabilité interne doit être isolée comme un cockpit de pilote.
Voilà, on a fait le tour des points critiques de ces standards. Même une équipe de dev top niveau peut voir ses efforts anéantis si l'architecte n'a pas anticipé ces edge cases dès la conception.