Aller au contenu

Décodage des calldata dans un wallet

Un guide axé sur la vérification des calldata, mots et offsets ABI, collisions de sélecteurs, proxies, lots, autorisations, permits de données typées, simulation et rapprochement postérieur.

Mis à jour

À des fins éducatives uniquement ; ne constitue ni un conseil en investissement ni une recommandation d’investissement. Les investissements peuvent entraîner des pertes.

Réponse directe

Les calldata sont la séquence immuable d’octets fournie en entrée d’une transaction Ethereum de premier niveau ou d’un appel interne. Un appel conventionnel de fonction Solidity commence par un sélecteur de 4-byte, suivi d’arguments encodés selon l’ABI. Les calldata ne sont toutefois pas autodescriptives : les mêmes octets peuvent avoir un sens différent selon le code d’exécution, l’implémentation du proxy ou le schéma. Les fonctions fallback, l’assemblage brut et les protocoles étrangers à Solidity ne sont pas tenus de suivre l’ABI conventionnelle des fonctions.

Un wallet doit donc afficher davantage qu’un nom de fonction potentiel. Une vérification sûre relie les octets à chainId, un bloc, from, to, le value natif, le codeHash d’exécution, l’implémentation active et une ABI fiable ; décode strictement chaque appel imbriqué ; distingue les calldata on-chain des signatures EIP-712 ; simule dans un état explicite ; puis rapproche le reçu et les modifications d’état effectives après l’inclusion.

Décodage des calldata
0 / 5
0 articles examinés; 5 points toujours non résolus

La réalisation de cet examen ne prouve pas qu'un actif, une transaction ou un système est sûr.

Fonctionnement

  1. Fixez l’enveloppe de signature et le point d’observation : chainId, numéro et hash du bloc, from, to, value natif, octets d’entrée, nonce et champs de frais. Conservez la source du wallet ou du RPC ; une charge décodée sur un autre réseau ou bloc ne représente pas la même affirmation.
  2. Classez l’objet avant de le décoder. Une transaction, une demande de données typées EIP-712, un permit ERC-2612, une UserOperation ERC-4337 et un message personal-sign brut utilisent des domaines et schémas distincts ; ne les forcez pas tous à passer par l’ABI des transactions.
  3. Résolvez la cible au bloc fixé. Lisez le bytecode d’exécution et le codeHash ; identifiez le proxy, beacon ou l’implémentation le cas échéant ; consignez les slots d’implémentation et d’administration ; puis obtenez une ABI correspondant à cette version exacte du code. Un registre de sélecteurs fournit des candidats, pas une autorité.
  4. Décodez strictement. Le sélecteur correspond aux premiers 4 bytes du Keccak-256 de la signature canonique de la fonction, sans les types de retour. Les valeurs statiques occupent des mots de 32-byte ; les en-têtes dynamiques contiennent des offsets mesurés depuis le bloc d’arguments après le sélecteur. Rejetez les données tronquées, offsets hors limites, longueurs impossibles, padding invalide et octets finaux inexpliqués.
  5. Développez récursivement les multicalls, calldata imbriquées et exécutions déléguées. Pour chaque appel enfant, énumérez la cible, la valeur native, le sélecteur, les arguments, le type d’appel et tout indicateur allowFailure. Avec delegatecall, le code de l’implémentation s’exécute dans le contexte d’adresse, de solde et de stockage de l’appelant, tout en conservant msg.sender et msg.value.
  6. Établissez des registres distincts d’autorité et de valeur, puis simulez. Consignez destinataires, spenders, opérateurs NFT, unités brutes des jetons, décimales, échéances, limites de slippage et valeur native. Simulez avec le bloc, l’expéditeur et la valeur exacts, mais considérez le résultat comme un instantané conditionnel, car l’état, les prix, le temps, le code et l’ordre des transactions peuvent changer.
  7. Confirmez chaque champ important avant de signer. Après l’inclusion, examinez le statut du reçu, les logs, les traces disponibles et les variations de soldes, allowances et états d’opérateurs ; distinguez les échecs enfants interceptés du succès au premier niveau ; comptabilisez le gas même lors d’un revert ; attendez la finalité requise ; puis arrêtez-vous plutôt que de signer à nouveau sans comprendre l’échec.

Exemples détaillés

  • Transfert ERC-20 statique. transfer(address,uint256) utilise couramment le sélecteur 0xa9059cbb. Un sélecteur et deux mots ABI représentent 4 + 2 * 32 = 68 bytes. Un montant brut de 1,500,000 pour un jeton dont l’emploi de 6 decimals a été vérifié indépendamment s’affiche comme 1.5 tokens. Les décimales sont des métadonnées externes du contrat, non encodées dans ces arguments, et le sélecteur seul n’identifie pas de manière unique le contrat ou la fonction.
  • Offset d’octets dynamiques. Pour f(address,bytes) avec une charge de 3-byte, l’en-tête de deux mots occupe 64 bytes. L’offset dynamique vaut 0x40, mesuré depuis le début du bloc d’arguments sans inclure le sélecteur. Sa partie finale contient un mot de longueur de 32-byte et un mot de données complété à 32-byte, portant les calldata totales à 4 + 64 + 32 + 32 = 132 bytes. Considérer l’offset comme absolu depuis l’octet zéro décale la cible de quatre octets.
  • La valeur d’un lot dépend de l’implémentation. Un appel externe transporte 1.00 ETH ; trois appels enfants décodés demandent explicitement 0.20 ETH, 0.30 ETH et 0.10 ETH, soit 0.60 ETH. Les 0.40 ETH restants peuvent être remboursés, conservés, transférés ou provoquer un revert selon le code du lot. Si le troisième appel échoue avec allowFailure=true, les appels antérieurs peuvent rester validés ; une implémentation atomique peut au contraire tout annuler.
  • Un permit n’est pas la calldata du relayer au moment de la signature. Un propriétaire disposant de 1,000 USDC signe un permit ERC-2612 de 300 USDC au nonce 41. La signature seule ne modifie ni le solde ni l’allowance. Après sa soumission réussie par un relayer, le nonce passe à 42 et l’allowance à 300 ; lorsque le spender utilise 180, le solde est de 820 et l’allowance restante de 120. Déconnecter le site ne révoque pas cette permission.

Risques

  • Décoder avec le mauvais réseau, fork, block tag ou enveloppe de transaction.
  • Signer pour un domaine, une adresse cible ou un destinataire usurpés.
  • Considérer un sélecteur de 4-byte comme unique malgré les collisions possibles.
  • Utiliser une ABI supposée, obsolète ou mal vérifiée.
  • Faire confiance à une mention de source vérifiée sans la rapprocher du codeHash actuel.
  • Manquer une mise à niveau de l’implémentation, du beacon ou de l’administrateur entre vérification et exécution.
  • Ignorer une fonction de proxy dont le sélecteur entre en collision avec celui de l’implémentation.
  • Oublier que delegatecall écrit dans le contexte de stockage de l’appelant.
  • Accepter des offsets, longueurs, padding ou octets finaux dynamiques mal formés.
  • Ne pas développer un lot imbriqué qui dissimule des cibles, valeurs ou permissions.
  • Supposer l’atomicité alors que l’implémentation intercepte ou autorise les échecs enfants.
  • Ignorer le value natif de premier niveau parce que les arguments de jeton semblent inoffensifs.
  • Appliquer des décimales erronées ou supposer que les jetons fee-on-transfer et rebasing sont des ERC-20 standard.
  • Accorder une allowance ERC-20 illimitée ou mal gérer la condition de concurrence lors de sa modification.
  • Négliger la portée sur toute la collection de setApprovalForAll pour les NFT.
  • Confondre des données typées EIP-712 ou un permit ERC-2612 avec les calldata d’une transaction.
  • Omettre le nonce, l’échéance, le contrat de vérification, le domaine de réseau ou les limites de replay.
  • Considérer une simulation comme stable malgré les changements d’oracle, de timestamp, d’état en attente, de MEV ou de code.
  • Considérer le statut du reçu, les logs ou les traces du fournisseur comme une preuve exhaustive de l’état économique.
  • Signer à nouveau à l’aveugle par une UI compromise ou ignorer les risques d’inclusion, de réorganisation et de finalité.

Idées reçues

  • Un sélecteur de fonction identifie de manière unique ce que le contrat exécutera.
  • Un résumé vérifié du frontend correspond exactement aux octets et à l’implémentation actuelle signés.
  • Une transaction avec value=0 ne peut déplacer ni jetons, ni NFT, ni actifs délégués.
  • Une simulation ou un reçu réussis prouvent la sécurité et le résultat économique attendu.
  • Déconnecter une dapp révoque les approbations, permits et permissions des opérateurs NFT.

Sujets connexes

Sources

Navigation

Rechercher dans le wiki...