Relecture indépendante de la version corrigée, menée en attaquant le contrat sur un réseau d'essai plutôt qu'en le relisant. Ce rapport dit ce qui a été trouvé, ce qui a été regardé et jugé sain, et ce qui n'a pas été vérifié.
version auditée a6e79483ccd8e289 · approval ba05406b512aadf2 · 31 août 2026
Le contrat portait 107 contrôles verts quand cet audit a commencé. Ce nombre ne disait pas ce qu'on croyait qu'il disait.
Les 20 méthodes du contrat, avant l'audit :
11 sur 20 avaient tourné contre la chaîne. Neuf n'avaient jamais été appelées, dont la clôture d'enchère, la fermeture du marché et la suspension d'urgence.
Les mêmes 20 méthodes, après :
20 sur 20. Toutes les méthodes du contrat ont désormais tourné contre un vrai nœud, au moins une fois.
Sur les gardes, le compte reste très incomplet et il faut le dire. Les méthodes publiques portent 93 assertions, réparties sur 54 messages de refus distincts. Avant l'audit, 2 de ces messages seulement étaient cités par un contrôle, ceux du prix plancher et du prix plafond ; citer le message est la seule façon de prouver que c'est bien la garde visée qui a refusé. Cet audit porte ce nombre à 24, dont 20 vérifiés par comparaison automatique et 4 relevés avec leur adresse dans le programme.
Soit 44 %, contre moins de 4 % auparavant. Trente gardes n'ont toujours jamais provoqué de refus vérifiable, et c'est la limite principale de ce rapport, plus importante que n'importe lequel des défauts qu'il signale.
Ni l'un ni l'autre n'est passé par une faute de raisonnement. Tous deux sont passés par un trou de couverture, et les deux formes de ce trou méritent d'être nommées, parce qu'elles se reproduiront.
Un contrôle s'intitulait « Une offre, retirée puis acceptée ». Son code déposait l'offre, tentait une attaque qui devait échouer, puis retirait. Le chemin heureux n'avait jamais tourné. Le titre décrivait une intention, pas un parcours.
Cet audit a failli commettre la même faute sous une autre forme, et c'est ce qui lui a appris à s'en garder. Un premier essai de la fermeture du marché a été refusé deux fois, comme attendu : deux verdicts verts, deux gardes qui auraient été déclarées couvertes.
Elles ne l'étaient pas. Le refus tombait au compteur de programme 390, sur txn ApplicationID, c'est-à-dire au routeur, avant que le corps de la méthode ne commence : fermer le marché suppose de supprimer l'application, et un appel ordinaire est rejeté d'emblée pour une raison qui n'a rien à voir avec ce qu'on croyait éprouver.
Seule la lecture du message de la garde, et de son adresse dans le programme, distingue un refus prouvé d'un refus supposé. Un banc qui se contente de constater l'échec fabrique de la fausse confiance.
Tous les contrôles de ce rapport nomment donc la garde attendue et vérifient que le refus vient bien d'elle.
La correction la plus importante de cette version répondait à un danger qui, jusqu'ici, n'était qu'affirmé : personne ne l'avait joué, parce qu'une enchère ne se clôt pas avant une heure. Une correction ne se juge pourtant que contre le défaut démontré, jamais contre le défaut décrit.
Cet audit a donc ouvert une enchère sur la version d'avant dès son commencement, pour la clore une heure plus tard, dans un montage sans échappatoire : réserve nulle de sorte que le prix de départ en tienne lieu, première mise l'atteignant donc, enchère dès lors non annulable, et enchérisseur n'ayant pas accepté la pièce, ce qui est le cas de tout le monde avant d'avoir gagné.
Aucune méthode du contrat ne rendait sa pièce au vendeur. Il avait remis son bien, l'enchère était finie, et il n'avait aucun recours.
L'essai a poursuivi : le gagnant accepte enfin la pièce, et la clôture passe alors immédiatement. Le blocage n'est donc pas éternel, il est otage. Il dure aussi longtemps que le gagnant ne fait rien.
Dire « les biens sont perdus pour toujours » aurait été faux. Dire « le bien d'un vendeur dépend entièrement de la bonne volonté d'un tiers qui n'a aucune obligation, ni même forcément conscience du problème » est exact, et suffit largement.
C'est ce qui rend le repli ajouté par la nouvelle version non pas confortable mais nécessaire : sans lui, le seul dénouement possible est un geste que personne ne peut exiger.
Un attaquant peut geler la pièce d'un vendeur aussi longtemps qu'il le souhaite, pour moins de 0,6 ALGO par jour, et récupérer sa mise à la fin.
Aucune garde n'empêche le meilleur offrant de surenchérir sur lui-même : les seules conditions sont que le montant dépasse la mise en cours, atteigne le prix de départ et reste sous le plafond. Rien ne regarde qui est déjà le meilleur offrant.
Deux mécanismes se combinent alors. Toute mise déposée dans les dernières minutes recule l'échéance, ce qui est voulu et sain, pour empêcher qu'une enchère se gagne à la dernière seconde. Et la mise précédente est rendue immédiatement à son auteur, donc à l'attaquant lui-même. Il peut donc monter d'un microALGO sur sa propre mise, indéfiniment, sans jamais immobiliser plus que la dernière mise.
Le vendeur, lui, est sans recours : dès la première mise atteignant la réserve, l'annulation lui est fermée par la garde « reserve atteinte, vente due ».
Ouvrir une enchère avec une réserve nulle, de sorte que le prix de départ en tienne lieu et que la première mise l'atteigne. Miser une fois. Vérifier que le vendeur ne peut plus annuler. Puis miser à nouveau, depuis le même compte, un microALGO au-dessus, dans la fenêtre de prolongation.
Mesuré : l'attaquant s'est surenchéri quatre fois, et une enchère ouverte pour 60 secondes durait encore après 151 secondes, l'annulation restant fermée au vendeur d'un bout à l'autre.
Condition de l'essai, déclarée. Cet essai a tourné sur une instance portant la version auditée avec deux constantes de temps réduites, la durée minimale d'une enchère de 3 600 à 60 secondes et la prolongation de 600 à 45. Ce sont les deux seules différences, vérifiées par comparaison ligne à ligne, et elles ne changent aucun chemin d'exécution : seulement le temps qu'il faut attendre pour l'atteindre. Une preuve dont on tait la condition n'est pas une preuve.
Sur la version réelle, il faut une mise par période de 600 secondes, soit 144 par jour à 4 000 microALGO de frais : environ 0,58 ALGO par jour pour geler la pièce d'un vendeur, mise intégralement récupérable. L'attaquant peut opérer sur toutes les enchères du marché à la fois.
Cela contredit la troisième mesure de sécurité du projet, « le retrait ne se bloque jamais », par la porte même que le repli de clôture venait de fermer. Formulation exacte : ce n'est pas un blocage permanent, c'est un blocage aussi longtemps que l'attaquant paie. La nuance change le mot, pas la situation du vendeur.
Ne prolonger que sur une mise venue d'un tiers ferme le cas de l'attaquant seul, mais deux comptes complices qui alternent le rouvrent. Plafonner le nombre de prolongations ferme cela aussi.
Mais aucune de ces mesures ne ferme le vrai problème, et c'est le point le plus important de cette section. L'attaquant n'a pas besoin de prolonger : il lui suffit de miser une seule fois au-dessus de la réserve et de ne jamais accepter la pièce. L'enchère se termine à l'heure, le repli joue, il n'y a pas de vente, les enchérisseurs honnêtes ont été évincés en cours de route, et l'attaquant récupère tout. Coût : quelques milliers de microALGO, une fois.
La racine n'est pas la prolongation. C'est que miser ne coûte rien à qui n'a pas l'intention de recevoir.
La seule correction qui aligne les intérêts est de retenir une part de la mise du gagnant défaillant au profit du vendeur quand l'enchère échoue faute d'acceptation. Une part modeste suffit : il ne s'agit pas de dédommager mais de rendre le sabotage non gratuit. Son défaut connu est de pénaliser un gagnant honnête qui aurait perdu l'accès à son compte, mais accepter un actif est une action délibérée, et ne pas l'avoir faite au terme d'une enchère est difficilement un accident.
La retenue décidée est de 10 % de la mise. Un sabotage complet a donc été joué de bout en bout, au prix plancher, pour mesurer ce que la pénalité couvrirait réellement.
| Sabotage d'une enchère ouverte au prix plancher de 0,01 ALGO | microALGO |
|---|---|
| perte sèche du vendeur | 13 000 |
| ce que le sabotage coûte à l'attaquant, en frais | ≈ 4 000 |
| ce que l'attaquant récupère de sa mise | la totalité |
| pénalité à 10 % de la mise | 1 000 |
| manque pour couvrir le vendeur | 12 000 |
La pénalité couvre donc 7,7 % de ce que le vendeur perd. Et le problème n'est pas le taux : on ne peut pas retenir plus que la mise, et la mise au prix plancher vaut 10 000, soit moins que les 13 000 perdus. Au prix plancher, aucune pénalité prélevée sur la mise ne peut couvrir la perte du vendeur. C'est une limite structurelle et non un réglage.
La cause en est simple : les frais fixes d'une enchère, ouverture et clôture, ne dépendent pas du prix, alors que la pénalité en dépend. La sortie la plus simple est un prix de départ minimal plus élevé pour les enchères que pour les annonces, d'au moins 0,13 ALGO, pour que 10 % de la mise dépasse les frais engagés. Cela ne touche qu'une constante.
À l'autre extrémité, 10 % d'une mise de 1 000 ALGO font 100 ALGO, ce qui est très lourd pour un gagnant honnête ayant perdu l'accès à son compte. Un plafond en valeur absolue garde tout le pouvoir dissuasif : qui sabote pour nuire n'est pas arrêté par la différence entre trois et cent ALGO, tandis qu'un honnête est ruiné par elle.
Enfin, le taux, le plancher et le plafond doivent être réglables par une méthode d'administration et non gravés. Sur un contrat immuable, une valeur gravée est une décision qu'on ne pourra jamais corriger, et celle-ci sera forcément mal calibrée au début puisque personne ne sait encore à quels prix ce marché échangera.
Le dépôt exigé du vendeur est inférieur de 3 200 microALGO à ce que le protocole immobilise réellement. Le coffre finance la différence sur son propre solde, à chaque enchère.
Le défaut jumeau, sur la mise en vente, a été trouvé par la session qui écrit le contrat. Cet audit a mesuré l'endroit qu'elle n'avait pas éprouvé.
| Sur une enchère réelle | microALGO |
|---|---|
| dépôt total exigé du vendeur | 162 900 |
| dont frais de sortie provisionnés | 4 000 |
| dépôt hors frais de sortie | 158 900 |
| ce que le protocole immobilise vraiment | 162 100 |
| écart | −3 200 |
Le coffre puise donc 3 200 dans les 4 000 provisionnés, et il ne lui en reste que 800 pour quatre transactions de sortie à 1 000.
La constante vaut 55 700, calculée pour 124 octets de valeur. La structure en fait 132 : 128 octets de champs fixes, plus deux d'offset et deux de longueur pour son tableau de pièces. Le bon chiffre serait 58 900.
Les huit octets manquants sont ceux d'un champ ajouté précisément pour corriger le deuxième défaut du rapport d'audit précédent. La correction d'un défaut en a donc créé deux, un par structure ayant reçu ce champ. Et le commentaire du contrat met en garde contre exactement cela, mot pour mot, en rappelant qu'une première version avait déjà gardé le chiffre d'avant l'ajout d'un autre champ.
Une constante dérivée d'une structure n'est jamais recalculée quand la structure change. C'est la deuxième génération du même défaut.
La troisième boîte, celle d'une offre, a été mesurée aussi : elle est juste au microALGO près. Le défaut ne touche que les deux structures modifiées, ce qui confirme le motif et le circonscrit.
Les constantes de boîte ne se vérifient pas en relisant leur commentaire. Elles se mesurent contre le protocole, en lisant le solde minimum réel du coffre avant et après. C'est la seule référence qui ne mente pas, et cela coûte une transaction.
Le défaut précédent n'apparaissait dans aucun des 107 contrôles verts, et la raison mérite d'être écrite telle quelle, parce qu'elle vaut pour tout ce qui sera ajouté au contrat :
On vérifiait la poche de l'utilisateur, jamais celle de la maison.
Tous les contrôles mesuraient que le vendeur, l'acheteur ou l'offrant retrouvent leur compte. Aucun ne mesurait que le coffre ne s'appauvrit pas. Une fuite proportionnelle au nombre d'annonces pouvait donc coexister avec une batterie de tests entièrement verte, et c'est exactement le mécanisme qui avait gelé le marché une première fois : un coffre qui ne peut plus émettre de transaction interne arrête tout.
Cette partie n'existe dans aucun signalement au fil de l'eau, et c'est pourtant elle qui dit ce que vaut le reste. Tout ce qui suit a été joué contre la chaîne, sur la version auditée, et chaque refus est identifié par le message de sa garde.
C'est la troisième mesure de sécurité du projet, et elle n'était jusqu'ici qu'une phrase dans un commentaire. Le marché a été réellement suspendu, une nouvelle annonce a été refusée par la garde prévue, et le vendeur a retiré sa pièce pendant ce temps, la pièce revenant bien chez lui. Puis le rétablissement a été joué et l'annonce est redevenue possible. C'était la première fois que l'interrupteur d'urgence était actionné.
Le contrat promet que n'importe qui peut déclencher la reprise d'une annonce expirée sans rien pouvoir détourner, de sorte qu'un vendeur récupère son bien même si ASA Compass disparaît. La seule façon d'éprouver cette phrase est de la faire appeler par quelqu'un d'autre que le bénéficiaire.
C'est donc le compte attaquant qui a déclenché la reprise, et la pièce est revenue au vendeur, l'appelant n'ayant rien reçu et ayant seulement payé les frais. Le même essai a été refait sur une offre expirée, qui porte de l'argent plutôt qu'une pièce : l'argent est revenu à l'offrant.
La fermeture du marché est réservée à l'exploitant, et elle lui est refusée tant que le coffre garde le bien d'autrui. Une fois le coffre vidé, elle passe et rend le solde. La question que le propriétaire du projet avait posée, « est-ce qu'il est prisonnier pour toujours ? », a donc une réponse mesurée : non.
Une correction annoncée n'est pas une correction constatée. La démonstration a donc été faite en quatre temps, et le deuxième est celui qui compte : le défaut d'origine a d'abord été rejoué tel qu'il était. Le coffre accepte une monnaie, la fermeture devient impossible, la nouvelle méthode rend ce que la monnaie immobilisait, et la fermeture passe.
Un test qui ne verrait pas le défaut d'origine ne prouverait rien en ne le voyant plus.
La moitié manquante de cette démonstration a été obtenue par accident, en voulant simplement récupérer un solde : l'application déployée par la relecture précédente refuse définitivement de se fermer. Elle porte 19 méthodes au lieu de 20, la méthode corrective n'y existe pas, son coffre a accepté trois actifs et garde six boîtes, et son minimum imposé est de 0,5326 ALGO contre 0,1 pour un compte nu. Ses 1,4658 ALGO sont perdus pour toujours. Le défaut se constate ainsi sur la version qui le portait, et sa disparition sur celle qui le corrige.
La correction la plus récente ajoute trois gardes. Deux ont été rejouées par cet audit, de façon indépendante de ceux qui les ont écrites :
Une observation qui compte pour l'interface, et que la mesure confirme : cette seconde garde ne parle que si l'appel au contrat précède le transfert dans le groupe. En plaçant le transfert d'abord, la même situation est refusée par le protocole, avec un message brut et incompréhensible pour un utilisateur. L'ordre des transactions dans le groupe décide donc du message que la personne lira, et c'est un choix d'interface, pas un détail technique.
La mise en vente est la méthode la plus riche du contrat, avec treize gardes, et c'était la moins couverte. Sept ont été attaquées une à une, et chacune a refusé pour la bonne raison : un tiers qui paie le dépôt à la place du vendeur, un dépôt adressé ailleurs qu'au coffre, un dépôt insuffisant, une pièce envoyée par un tiers, une pièce adressée ailleurs, une annonce sans pièce, et une expiration déjà passée. Ce sont les gardes qui décident de ce qui entre dans le coffre.
L'auteur du contrat a délibérément refusé d'exiger l'acceptation de la pièce au moment de miser, au motif que cela ferait payer la foule pour un seul gagnant. Cet audit confirme son arbitrage, et pour une raison plus forte que la sienne : une telle garde n'empêcherait rien, puisque rien n'oblige un enchérisseur à rester opté jusqu'à la clôture. Il pourrait accepter pour miser, puis se dés-opter avant la fin. Le repli de clôture n'est donc pas un pis-aller, c'est la seule protection qui tienne.
Cette partie est aussi importante que les deux autres. Un rapport qui ne dit pas où s'arrête son regard laisse croire qu'il a tout vu.
Sur les 54 messages de refus distincts que porte le contrat, 17 ont provoqué un refus identifié au terme de cet audit, contre 2 avant lui. Trente-sept n'ont donc jamais été déclenchés de façon vérifiable, et parmi eux se trouvent des gardes qui protègent des mouvements d'argent : celles qui vérifient l'origine d'un dépôt, celles qui bornent les durées, et la plupart de celles de la mise en vente, qui en porte treize à elle seule.
C'est la limite principale de ce rapport, et elle est plus importante que n'importe lequel des défauts qu'il signale.
Elle est de trente jours. Seule la garde de borne a été éprouvée, jamais son écoulement.
Deux appels simultanés sur le même objet, et le comportement du contrat sous une charge réelle, n'ont pas été éprouvés : cela demande un banc dont cet audit ne dispose pas, et un réseau d'essai ne s'y prête pas.
L'enchère perpétuelle, pour la raison déclarée plus haut. Le comportement est identique, mais l'honnêteté demande que le lecteur sache que cet essai précis n'a pas tourné sur une instance strictement identique à la version auditée.
Cet audit porte sur le contrat, pas sur l'interface. Le fait qu'un portefeuille matériel sache signer ce que le site construit a été établi ailleurs, et ne relève pas de ce rapport.
Elles figurent ici pour une raison simple : un audit qui tait ses erreurs n'est pas croyable sur ses trouvailles. Toutes deux ont été rattrapées par vérification et non par chance, et toutes deux enseignent quelque chose.
Décrite plus haut : deux verdicts verts sur la fermeture du marché, alors que le refus venait du routeur et non des gardes visées. Rattrapée parce que le banc affiche le message de la garde et son adresse dans le programme. Sans ce contrôle, ce rapport aurait affirmé que deux gardes tiennent sans les avoir jamais atteintes.
Un contrôle sur la reprise d'une offre expirée a signalé que l'offrant récupérait 0,006 ALGO de moins que ce qu'il avait sorti. Vérification faite, le contrat avait raison et le seuil de l'audit avait tort : l'offrant paie les frais de ses trois transactions d'entrée et le contrat retient ceux des deux de sortie, soit exactement six frais, ce que le contrat documente d'ailleurs.
La leçon est la même que celle qu'une autre session a apprise le même jour sur un contrôle de commission faussé par un financement arrivé au milieu d'un essai : une mesure de solde doit encadrer la seule transaction qu'elle juge. Sinon les frais des transactions voisines s'y mêlent en silence, et l'on croit avoir trouvé un défaut.
Elles valent au-delà de cet audit, parce que chacune vient d'une erreur réelle commise sur ce projet, y compris par cet audit lui-même.
Audit mené sur des instances déployées par la relecture elle-même, distinctes de celles de
l'équipe, sur le réseau d'essai d'Algorand. Aucune écriture n'a été faite sur le réseau principal.
Les instances jetables ont été fermées et leur solde rendu ; celle de la relecture précédente
n'a pas pu l'être, et c'est un résultat consigné plus haut.
La relecture précédente, qui portait sur une version antérieure du contrat et dont ce rapport
reprend les six défauts, reste consultable :
rapport du 31 août, empreinte f33e994b.