Relecture aveugle 3 du contrat de marché ASA Compass
Relecture indépendante, sans connaissance des relectures précédentes, menée le 1er septembre 2026 sur le testnet Algorand. Tout ce qui est affirmé ici a été mesuré sur une instance déployée par la relectrice elle-même, sauf mention contraire.
L'objet relu, et aucun autre. Source contrat/marche/contrat.py copié à 08:38, empreinte sha256 196123eabb884f97d2a3c771526ad87aeb956ad4ff87f2131debd5a25d70c33a, compilé avec puyapy 5.10.1 puis assemblé par le nœud : programme d'approbation sha256 d9d56b60b3bd4575c9c7957c04e2b0e5dc94f22e380682910ff343098350ec5f, 5 243 octets ; effacement ed90f0d2…a640ce7, 4 octets. Déployé par moi sous le numéro 770822000 (testnet), avec une seconde instance jetable 770822702 pour une épreuve destructrice. Chaque constat ci-dessous porte sur cet objet-là.
Résumé
- L'application 770819337 ne correspond pas au source du jour. Son bytecode fait 5 220 octets (e8f753b9…), le source compile en 5 243 (d9d56b60…). Le source a été modifié dix-huit minutes après la création de l'application. La relecture porte donc sur le source, et l'objet a encore changé pendant le travail (voir la section « conformité »).
- Un pouvoir de l'exploitant que le contrat déclare ne pas avoir : en relevant le plancher d'une monnaie puis en la fermant, l'exploitant s'est approprié les 100 unités d'une offre déposée par un tiers, et le créancier ne peut plus rien réclamer. Mesuré de bout en bout.
- La comptabilité des créances fuit dans trois directions : la boîte de créance est payée deux fois par le coffre sur les offres (24 100 µALGO perdus par cycle, répétable à volonté par un inconnu), un trop-payé d'achat crée une créance que personne n'a provisionnée (même perte), et la provision du dernier enchérisseur d'une enchère en jeton n'est jamais rendue (22 100 µALGO gardés par le coffre à chaque enchère en jeton qui se termine).
- On peut accepter sa propre offre et inscrire un prix fictif dans l'archive des ventes, ce que le contrat interdit pour les annonces mais pas pour les offres. Coût : la commission, nulle dans la monnaie exonérée.
- Une unité de poussière envoyée au coffre fait perdre au vendeur sa réserve de 0,1 ALGO et rend le marché infermable pour toujours ; la tolérance ajoutée pour les monnaies n'existe pas pour les pièces.
- Les gardes que j'ai fait refuser quelque chose : cinquante-deux, avec le bon motif sauf trois refus laissés au protocole. Les portes réservées à l'exploitant, l'immuabilité du programme, la garde du groupe unique, les dépôts de tiers, les bornes de prix et de durée, la suspension, l'exonération, la pénalité du gagnant défaillant et la fermeture propre du marché tiennent.
- Le correctif appliqué pendant la relecture (49f6750d…) traite les paiements vers des comptes fermés et ne touche à aucun des constats ci-dessus : ils sont tous encore dans la version d'après.
1. Conformité : ce qui sera gravé n'est pas ce qui est déployé
Consigne reçue : vérifier d'abord que le bytecode de 770819337 correspond à la compilation du source, sans se fier à un fichier .teal. Mesure faite avant toute lecture du contrat, en assemblant le TEAL par le nœud (/v2/teal/compile) et en relisant le programme de l'application sur la chaîne :
| sha256 | taille | |
|---|---|---|
| approbation gravée dans 770819337 (créée au tour 66879133, 11:44:13 UTC) | e8f753b906850fb9964b619ff8a1ad75a1a243362be664adc518918158915fd3 | 5 220 octets |
| approbation compilée depuis le source de 08:02 (196123ea…) | d9d56b60b3bd4575c9c7957c04e2b0e5dc94f22e380682910ff343098350ec5f | 5 243 octets |
| effacement, identique des deux côtés | ed90f0d2da1f1d1abd773c45230651a292a90edbc12a7bf859a493a12a640ce7 | 4 octets |
Les deux diffèrent. L'application de la consigne est donc : relire le source, déployer mes propres instances depuis lui, et ne rien mesurer sur 770819337. Le greffier a confirmé la même mesure de son côté, indépendamment, et m'a demandé de ne pas chercher où le code diffère.
L'objet a bougé une seconde fois pendant la relecture. Vers 09:01, le source en place est passé à l'empreinte 49f6750d… (bytecode 4869956f…, 5 321 octets), et les artefacts ont été régénérés. Comme je travaillais sur une copie prise à 08:38, mes mesures n'en sont pas affectées, à une exception près que je signale par honnêteté : pendant une heure, la traduction des refus en phrases lisait la carte des positions de l'ARC-56 en place, donc celle de la nouvelle version, et une partie des motifs m'est apparue décalée de 22 octets. J'ai cru un instant à un artefact périmé ; c'était une course entre deux sessions. J'ai depuis fait lire à mon banc ma propre carte, et tous les motifs cités ici en viennent.
Une copie du source d'après correctif a été figée dans mon dossier sans être ouverte. Elle sert au contrôle ciblé de la section 7, fait après la clôture de la phase aveugle.
2. Ce qui a été fait, et comment
- Lecture intégrale du contrat (2 924 lignes), de
commun.py, dedeployer.py, demenage.pyet de la composition des groupes dans les scénarios existants. Aucun des fichiers interdits (A-FAIRE.md, A-FAIRE-JOURNAL.md, QUI-FAIT-QUOI.md, PROJET.md) n'a été ouvert, ni aucun rapport d'une autre relecture ; je n'ai pas listé le contenu des dossiers d'audit précédents, dont les seuls noms de fichiers auraient pu m'orienter. Aucune session ne m'a communiqué de défaut, d'endroit à regarder ou de conclusion ; le greffier m'a donné les comptes, l'outillage, et deux avertissements de conformité (l'écart de bytecode, que j'avais déjà mesuré, et le changement du source). - Un banc à moi :
relecture-aveugle-3/banc.py, qui déploie depuis mon bytecode, frappe les actifs d'essai, compose chaque groupe et lit le disponible du coffre (solde moins solde minimum) avant et après chaque geste. C'est cette différence qui a servi de balance : tout ce qui manque au disponible après un cycle complet est de l'argent que le coffre a payé de sa poche. - La simulation d'abord, la soumission ensuite : toute mesure de borne, de budget, de référence ou de motif a été faite par
simulate, qui ne coûte rien et rend le budget consommé. Les gestes qui déplacent de l'argent ont ensuite été soumis pour de vrai et l'état relu sur la chaîne. - Douze scripts, numérotés 01 à 12 dans
relecture-aveugle-3/, et leurs journaux dansrelecture-aveugle-3/mesures/. Rien danscontrat/n'a été modifié.
3. Les constats, classés par gravité et par réparabilité
Sur un contrat immuable, un défaut se juge autant par ce qui peut le corriger que par ce qu'il coûte. Trois niveaux : irréparable (aucun réglage ni discipline ne le referme, seul un nouveau contrat le peut), réparable par discipline (l'exploitant peut l'éviter en se tenant à une règle, mais rien ne l'y oblige), réparable hors chaîne (le site ou l'outillage peut le contourner sans toucher au contrat).
graveréparable par discipline seulementC1 · L'exploitant peut s'approprier l'argent déposé dans une monnaie
fermer_une_monnaie tolère, pour ne pas être bloquée par de la poussière, un solde inférieur au plancher de la monnaie et l'envoie à l'exploitant par asset_close_to. Mais ajuster_le_plancher permet de relever ce plancher à tout moment, jusqu'au plafond du marché, sans considération pour les offres et les mises déjà déposées. Il suffit donc de relever le plancher au-dessus de ce que le coffre détient, puis de fermer la monnaie.
Mesuré (script 04, monnaie K 770822354) : l'acheteur dépose une offre de 100 K sur une pièce (offre 1). fermer_une_monnaie(K) est refusée au plancher 10 avec le bon motif, « le coffre detient encore cette monnaie ». L'exploitant appelle ajuster_le_plancher(K, 1000), puis fermer_une_monnaie(K) : acceptée au tour 66880824, et l'exploitant reçoit 100 K (relevé sur son compte : 999 999 999 000 avant, 999 999 999 100 après). L'offrant tente ensuite retirer_l_offre : refus du protocole, « asset 770822354 missing from ECSQSHUG… », sans qu'aucune garde ne parle. En se dés-optant de K, il obtient une créance de 100 K et son dépôt en ALGO (58 000), mais reclamer échoue de la même façon : le coffre n'accepte plus K. La créance reste inscrite et immobilise 22 100 µALGO au coffre. Rouvrir K ne répare rien tant que l'exploitant ne renvoie pas l'argent de lui-même.
Pourquoi c'est grave. Le contrat affirme, dans son propre texte, que « le SEUL pouvoir de l'exploitant est de suspendre les nouveaux dépôts » et qu'« il ne doit exister aucun chemin de code permettant de déplacer les avoirs de quelqu'un ». Ce chemin existe, en deux appels réservés à l'exploitant et sans rien de suspect dans chacun pris isolément. Il touche toute somme déposée en jeton : offres, mises d'enchères, et le solde d'une vente en cours. Un exploitant honnête ne l'emploiera pas ; mais un contrat immuable qui garde l'argent d'inconnus doit rendre l'accident impossible, pas improbable, et la clé de l'exploitant devient une clé qui vaut tout ce que le coffre détient en jetons.
Réparabilité. Aucun réglage ne referme ce chemin. Une discipline (ne jamais relever un plancher au-dessus de ce que le coffre détient, ne jamais fermer une monnaie tant qu'une position y existe) l'évite, mais rien dans le code ne l'impose. Dans un nouveau contrat, la fermeture devrait exiger un solde nul ou refuser tant qu'une boîte d'offre, d'enchère ou d'annonce porte cette monnaie ; la tolérance à la poussière devrait être une constante et non le plancher courant.
moyen à graveirréparableC2 · La boîte de créance est payée par le coffre, deux fois, et n'importe qui peut le faire répéter
Le mécanisme des créances (une somme qui n'a pas pu être versée reste au coffre, inscrite au nom du destinataire, réclamable plus tard) est juste dans son principe. Sa comptabilité ne l'est pas : la provision de 22 100 µALGO que l'offrant ou le vendeur avance « pour payer sa propre boîte de créance » lui est rendue inconditionnellement avec son dépôt, que la créance ait été créée ou non ; et reclamer lui rend encore 22 100 µALGO en effaçant la boîte. Seule encherir fait le calcul correctement pour l'enchérisseur dépassé.
Mesuré, chemin des offres (script 03, T2) : l'attaquant dépose une offre de 100 J avec les 59 000 µALGO exigés (boîte 34 900, deux frais, provision 22 100), se dés-opte de J, puis appelle retirer_l_offre. La créance de 100 J est inscrite, et il reçoit 58 000 µALGO, provision comprise ; le disponible du coffre tombe de 1 155 500 à 1 109 300. Il se ré-opte et appelle reclamer : 100 J et 22 100 µALGO de plus. Sur le cycle complet, le disponible du coffre passe de 1 131 400 à 1 107 300 : 24 100 µALGO perdus (la boîte, plus les 2 000 de frais internes de reclamer), pour un attaquant qui n'a dépensé qu'une dizaine de milliers de microALGO de frais. Le cycle se répète sans limite.
Mesuré, chemin de l'achat (script 03b, T3) : l'acheteur d'une annonce en jeton envoie 150 J pour un prix de 101 (100 plus la commission) dans un transfert qui referme sa position (asset_close_to vers le coffre, reste nul). Au moment où le contrat veut lui rendre les 49 J de trop-payé, il n'accepte plus J : une créance de 49 J est créée, que personne n'a provisionnée. Le coffre paie la boîte (disponible 1 133 400 → 1 085 200, dont 22 100 pour la boîte), puis rend 22 100 µALGO à l'acheteur au reclamer. Même perte de 24 100 par cycle.
Par lecture, non mesuré séparément : le même défaut existe pour le vendeur d'une enchère en jeton (sa provision est comprise dans depot et rendue par _rendre_le_depot quel que soit le sort de sa créance), pour reprendre_une_offre_expiree, et pour la commission de l'exploitant, jamais provisionnée.
Pourquoi ça compte. Le disponible du coffre n'est pas de l'argent libre : c'est la somme des provisions de frais de sortie des positions ouvertes. Chaque cycle en retire 24 100 µALGO ; quand il n'en reste plus, une sortie ordinaire (un remboursement de dépôt, une pièce rendue) échoue sur un solde insuffisant, et c'est exactement le gel de marché que le contrat raconte avoir déjà connu. Le coût pour l'attaquant est faible, la réparation impossible sans nouveau contrat.
moyenirréparableC3 · La provision de créance du dernier enchérisseur n'est jamais rendue
Une mise en jeton exige 22 100 µALGO de provision (depot_mise), rendus à l'enchérisseur quand il est dépassé. Mais ni annuler_l_enchere, ni les deux chemins de clore_l_enchere ne rendent cette provision au dernier enchérisseur : elle reste au coffre. À chaque enchère en jeton qui se termine (vendue, annulée sous la réserve, ou reprise après défaillance), le gagnant ou le dernier offrant perd 22 100 µALGO.
Mesuré, annulation (script 07, E2) : mise de 200 J avec provision 22 100 ; le vendeur annule sous la réserve. L'attaquant retrouve ses 200 J ; son disponible en ALGO ne bouge pas (delta 0) ; le coffre garde la provision.
Mesuré, clôture conclue (script 07, E1, jeton, deux pièces, réserve atteinte) : mise de 130 J avec provision 22 100 ; clôture par un tiers au tour 66882037, budget 555. Le gagnant reçoit ses deux pièces, le vendeur 129 J, l'exploitant 1 J de commission, le vendeur retrouve 295 800 µALGO (dépôt 98 800 plus deux réserves moins trois frais). Le gagnant, lui : delta ALGO 0. Sa provision de 22 100 est restée au coffre, où elle est allée à l'exploitant à la fermeture du marché.
Mesuré, repli avec pénalité (script 07, E3, ALGO, gagnant dés-opté de la pièce avant l'échéance) : la pièce revient au vendeur, le gagnant retrouve 117 000 sur 130 000, le vendeur reçoit les 13 000 de part retenue avec son dépôt (183 500 au total), l'exploitant rien. Ce chemin-là est juste ; en ALGO il n'y a pas de provision, donc rien à perdre.
Ce défaut et le précédent se compensent partiellement dans les totaux du coffre, ce qui les rend invisibles à un bilan global : l'un enrichit le coffre aux dépens des enchérisseurs, l'autre le vide au profit de qui sait s'y prendre. Ni l'un ni l'autre n'est réparable par un réglage.
moyenirréparableC4 · On peut accepter sa propre offre et fabriquer un prix public
acheter refuse « on n achete pas sa propre piece », avec pour motif écrit que l'archive des ventes est l'argument du site et qu'un prix doit avoir eu deux parties. accepter_l_offre n'a pas le jumeau de cette garde : rien n'interdit à l'auteur d'une offre de l'accepter lui-même, en s'envoyant la pièce à lui-même.
Mesuré (script 06, T14) : l'attaquant, détenteur de la pièce 770822032, dépose une offre de 50 000 µALGO dessus (offre 2), puis appelle accepter_l_offre avec un transfert de la pièce de lui-même à lui-même. Accepté au tour 66880850, budget 337 ; il détient toujours la pièce, l'exploitant reçoit 500 µALGO de commission, et l'événement OffreConclue est émis avec un vendeur et un acheteur identiques. Dans la monnaie exonérée, l'opération est gratuite hors frais de réseau : on peut inscrire n'importe quel prix dans l'histoire d'une pièce.
Réparable seulement hors chaîne, en filtrant les événements dont le vendeur et l'acheteur coïncident ; mais l'événement gravé reste, et deux comptes complices ne se filtrent pas.
moyenirréparableC5 · Une unité de poussière fait perdre sa réserve au vendeur et rend le marché infermable
Le contrat compte ses pièces par le solde du coffre. Sur un actif tiré à plusieurs exemplaires, n'importe quel détenteur d'une unité peut l'envoyer au coffre par un simple transfert, hors de tout appel, pendant qu'une position y est ouverte. À la sortie de cette position, le solde n'est plus 1 : la position ne se referme pas, la réserve d'acceptation n'est pas libérée, et l'unité de trop ne peut plus jamais sortir. refermer_une_position exige un solde exactement nul, et fermer_le_marche exige que rien ne reste. La tolérance « la poussière ne doit pas condamner une monnaie » a été écrite pour les monnaies et n'a pas de jumeau pour les pièces.
Mesuré (script 05, sur l'instance jetable 770822702, édition à deux exemplaires 770822037) : le vendeur liste une unité en payant 143 700 µALGO dont 100 000 de réserve ; l'attaquant envoie l'autre unité au coffre ; le vendeur annule et ne retrouve que 41 700 µALGO au lieu de 141 700. refermer_une_position refuse « le coffre detient encore cet actif », fermer_le_marche refuse « le coffre garde encore quelque chose ». Relister et annuler ne change rien : le solde oscille entre 2 et 1 sans jamais repasser par la fermeture. L'instance 770822702 est laissée telle quelle sur le testnet, avec 0,2 ALGO au coffre, 0,3 ALGO de pages sur le déployeur et une position de 0,1 ALGO chez le vendeur, tous prisonniers : c'est la preuve, et son prix.
Portée : les pièces uniques (total 1) ne sont pas concernées, puisque personne d'autre ne peut en détenir une unité. Mais le corpus de 2021 compte des éditions multiples, et il suffit d'une seule pour bloquer la fermeture du marché entier. Un inconnu dispose ainsi, pour le prix d'une transaction, d'un pouvoir de blocage définitif sur l'exploitant, et d'un moyen de faire perdre 0,1 ALGO à tout vendeur d'une édition qu'il détient aussi.
faibleirréparableC6 · Le trop-versé d'une offre en jeton reste au coffre
Le contrat rend à l'acheteur ce qui dépasse le prix (« ce n'est pas un vol, c'est pire : une perte silencieuse ») et retient, pour une offre en ALGO, ce que l'offrant a réellement versé au-delà de son montant. Pour une offre en jeton, ni le jeton versé au-delà du montant ni l'ALGO versé au-delà de la constante ne sont retenus dans la boîte.
Mesuré (script 10) : offre de 100 J en versant 105 J et 70 000 µALGO (le dû est 59 000). La boîte retient depot = 59000. Au retrait, l'offrant retrouve 100 J et 58 000 µALGO : 5 J et 11 000 µALGO restent au coffre. Une interface qui arrondit au-dessus reproduit cette perte à chaque offre.
Même chose pour le dépôt d'une mise en vente (script 08) : en versant 5 000 µALGO de plus que le dû, la boîte retient le dû (43 700) et l'annulation rend 141 700 sur 148 700 versés. Le contrat vérifie « au moins », retient « exactement », et garde la différence sans le dire. mettre_aux_encheres fait pareil, par lecture.
faibleréparable par réglageC7 · Le plancher d'enchère en jeton déborde pour certains réglages
mettre_aux_encheres calcule planchers[jeton] × plancher_enchere // PRIX_PLANCHER en 64 bits. Le produit déborde dès que le plancher de la monnaie multiplié par le plancher d'enchère dépasse 1,8 × 1019, ce que les deux méthodes de réglage autorisent séparément (chacune va jusqu'à 1012).
Mesuré (script 06b, T8) : regler_les_encheres(10^12, …) puis ajuster_le_plancher(J, 10^8) passent ; toute mise aux enchères en J est alors refusée par la machine virtuelle avec « * overflowed », sans motif du contrat. Les réglages par défaut (130 000 et un plancher de monnaie inférieur à 1014) ne débordent pas. À tenir comme règle d'exploitation : garder le produit des deux planchers sous 1,8 × 1019.
faibleréparable hors chaîneC8 · Une hypothèse écrite dans le contrat est fausse : l'acceptation de la monnaie ne peut pas voyager dans le groupe de la mise en vente
Le texte de lister et de mettre_aux_encheres affirme : « L'acceptation étant une transaction que le vendeur signe lui-même, le site la place dans le même groupe : une seule signature suffit. » Or ces deux méthodes parcourent tout le groupe et exigent de chaque transfert d'actif qu'il soit adressé au coffre avec une unité.
Mesuré (script 06, T6) : un groupe [opt-in du vendeur à J, dépôt, appel, pièce] est refusé « piece mal adressee ». Le site devra faire signer l'acceptation à part, avant, ce qui fait deux signatures sur un portefeuille matériel. Pour offrir, l'acceptation de la pièce dans le groupe fonctionne (mesuré à chaque offre de cette relecture).
faibleirréparableC9 · L'histoire des enchères et des offres n'a ni ouverture ni retrait
Le contrat justifie longuement ses événements (« ce que son événement n'inscrit pas est perdu pour toujours ») et en émet pour l'ouverture, la fermeture et la vente d'une annonce, pour chaque mise et pour chaque clôture. Mais mettre_aux_encheres, annuler_l_enchere, offrir, retirer_l_offre, reprendre_une_offre_expiree et changer_le_prix n'émettent rien. Par lecture : une enchère annulée disparaît de l'histoire sans trace, on ne sait ni quand une enchère a ouvert ni à quel prix de départ autrement qu'en relisant la transaction d'ouverture, et le prix affiché d'une annonce n'a d'histoire que son prix initial. C'est le motif « un jumeau laissé à nu » appliqué aux événements.
faibleréparable hors chaîneC10 · Des refus sans motif lisible
Le contrat tient à ce qu'un refus parle. Trois chemins courants refusent par la machine virtuelle et non par une garde, mesurés (scripts 06b et 11) :
- acheter sans avoir accepté la pièce : « inner tx 0 failed: receiver error: must optin » ;
- annuler, ou toucher, une annonce qui n'existe pas ou plus : « index out of bounds » ;
- lister ou enchérir dans une monnaie fermée par l'exploitant, ou changer le prix d'une annonce dont la monnaie a été fermée : « invalid Box reference » ou « asset missing ».
Et la mise aux enchères en jeton exige, sans le dire, la référence de la boîte de plancher de tout actif que le coffre a déjà accepté ; sans elle, « invalid Box reference » (mesuré en relistant une pièce que le coffre connaissait).
Observations, sans gravité mesurable
- Marge de budget serrée à trois pièces. Mesuré par simulation sur mon instance :
lister3 pièces en ALGO 638 unités sur 700, en jeton 657 (43 unités de marge, la garde de monnaie et la lecture du plancher s'ajoutant) ;acheter3 pièces en jeton 541 ;mettre_aux_encheres2 pièces 551 ;annuler3 pièces 300. Le mur est bien à quatre : « lot trop grand » y est rendu avant le budget. Toute garde ajoutée à la boucle delisterdoit être remesurée en jeton, pas seulement en ALGO. - Références à la clôture. Une enchère en jeton à deux pièces se clôt avec 7 références, 8 si le vendeur est devenu créancier ; si l'exploitant l'est aussi, 9 : « tx references exceed MaxAppTotalTxnReferences = 8 » (simulé, script 12). La clôture attend alors que l'un des deux se ré-opte, ce qu'ils peuvent toujours faire.
- Frais à porter par l'appelant, mesurés par simulation (script 09) : lister une pièce 2 000, trois pièces 4 000 ; acheter 1 000, 2 000 s'il y a un trop-payé à rendre ; annuler 1 000 ; retirer une offre 2 000 ; enchérir en jeton 4 000. Les sorties à frais nuls sont payées par celui qui appelle, ce qui est le cas de toute personne qui clôt une enchère par courtoisie.
- Commission nulle sous cent unités d'une monnaie non exonérée : 1 % en division entière donne zéro pour tout prix inférieur à 100 unités de base, ce qui, pour une monnaie à deux décimales, couvre tout prix sous une unité.
- Pas de relève pour l'exploitant. L'exploitant est le créateur, pour toujours ; la perte de cette clé rend le marché impossible à fermer et ses monnaies impossibles à ouvrir, sans toucher aux avoirs des utilisateurs.
- Fragilité du dispositif de messages. Les phrases de refus sont retrouvées par position dans le programme ; elles deviennent fausses, en silence, dès que le bytecode bouge d'un octet. Vu pendant cette relecture au moment où les artefacts ont été régénérés sous mes yeux.
4. Ce qui a tenu : les gardes que j'ai fait refuser
Une garde qui n'a jamais rien refusé n'est pas une garde. Toutes celles-ci ont refusé devant moi, par simulation sur l'instance 770822000, avec le motif indiqué.
| Geste | Motif rendu |
|---|---|
| Mise à jour du programme par le créateur (UpdateApplication) | rejeté par le programme d'approbation |
| Suppression nue par le créateur, OptIn, CloseOut, appel nu | rejetés par le programme |
| fermer_le_marche par un inconnu · avec des positions ouvertes | seul l'exploitant · le coffre garde encore quelque chose |
| suspendre, regler_les_encheres, designer_le_jeton_exonere, ajuster_le_plancher, fermer_une_monnaie, refermer_une_position par un inconnu | reserve a l'exploitant · seul l exploitant |
| part retenue à 2 001 points · exonérer l'ALGO | part retenue trop elevee · l'ALGO ne peut pas etre exonere |
deux lister dans un même groupe | un seul appel au marche par groupe |
| pièce déposée par un tiers · dépôt payé par un tiers · mise versée par un tiers · paiement d'achat venu d'un tiers | piece deposee par un tiers · le depot doit venir du vendeur · depot d'un tiers · paiement insuffisant |
| paiement ALGO passé comme dépôt d'une enchère en jeton | transaction type is axfer |
| le vendeur achète sa propre annonce · enchérit sur sa propre enchère | on n achete pas sa propre piece · on n encherit pas sur sa propre piece |
| payer 302 pour 303 · dépôt d'un microALGO · sans provision de créance | paiement insuffisant · depot insuffisant · provision de creance manquante |
| changer_le_prix ou annuler par un tiers | seul le vendeur |
| prix 9 999 · plafond + 1 · sous le plancher d'une monnaie · départ d'enchère à 0,1 ALGO | prix sous le plancher · prix au-dessus du plafond · sous le plancher des encheres |
| monnaie inconnue · monnaie qui est une pièce du coffre · la monnaie J déposée comme pièce | monnaie non acceptee par le marche · prix sous le plancher (boîte absente) · cet actif est une monnaie du marche |
| 4 pièces en vente · 3 pièces aux enchères · durée 100 s · expiration passée | lot trop grand · lot trop grand · duree trop courte · expiration deja passee |
| offrir sans avoir accepté la pièce · mise à égalité · clore avant l'échéance · annuler une enchère dont la réserve est atteinte · reprendre avant l'heure · acheter une annonce expirée | acceptez la piece avant d offrir dessus · mise deja depassee · enchere en cours · reserve atteinte, vente due · pas encore expiree · annonce expiree |
| sous suspension : offrir, enchérir | nouveaux depots suspendus |
| sous suspension : changer_le_prix, acheter, annuler, annuler_l_enchere | passent, comme voulu (le retrait ne se bloque jamais) |
Et les chemins nominaux qui ont fait ce qu'ils promettent, pour de vrai : mise en vente et achat en ALGO et en jeton, annulation avec réserve rendue (141 700 sur 143 700 déposés, deux frais retenus), reprise d'une annonce expirée par un tiers (le vendeur reçoit 141 700), offre déposée, retirée, acceptée, enchère ouverte, mise, annulation sous la réserve, clôture (section E1/E3), ouverture et fermeture de monnaie, exonération de commission (script 08 : J désignée, un second appel refusé « le jeton exonere est deja fixe », une vente à 100 J passe en payant 100 tout rond, l'exploitant reçoit 0 ; la même pièce revendue en ALGO à 0,1 rapporte 1 000 µALGO de commission), et fermeture du marché une fois le coffre vide (tour 66882085, 1 309 800 µALGO rendus au déployeur).
Cinquante-deux refus au total, tous avec un motif, sauf les trois de C10 qui viennent du protocole.
5. Les enchères d'une heure
Trois enchères ouvertes dès la mise en place, une heure avant leur échéance, pour être closes pendant la relecture :
| E1 | E2 | E3 | |
|---|---|---|---|
| monnaie · pièces | jeton J · 2 | jeton J · 1 | ALGO · 1 |
| départ · réserve · mise | 130 · aucune · 130 | 130 · 500 · 200 | 130 000 · aucune · 130 000 |
| sort | clôture conclue par un tiers | annulée par le vendeur (sous la réserve) | gagnant dés-opté : repli, pénalité 10 % |
| budget de l'appel (simulation) | 555 / 700 · 7 références | 247 | 436 |
| pièces | au gagnant, positions refermées | au vendeur | au vendeur |
| argent | vendeur 129 J · exploitant 1 J | 200 J rendus | gagnant 117 000 · vendeur 13 000 |
| dépôt du vendeur | 295 800 rendus (98 800 + 2 réserves, 3 frais) | 192 600 rendus | 183 500 rendus, pénalité comprise |
| provision de créance du dernier enchérisseur (22 100) | gardée par le coffre | gardée par le coffre | sans objet en ALGO |
La clôture d'une enchère en jeton à deux pièces tient donc dans le budget avec 145 unités de marge, sans créance à inscrire. Avec une créance pour le vendeur, huit références ; avec une seconde pour l'exploitant, neuf, refusée par le protocole (script 12).
6. Ce que je n'ai pas regardé
Cette section vaut autant que les autres : elle dit où il reste du risque.
- Les actifs sous contrôle (clawback ou gel non vides) : la garde
_piece_sous_controlea été lue, pas éprouvée ; je n'ai frappé aucun actif avec reprise ou gel. Ni les actifsdefault_frozen. - La prolongation d'une enchère dans ses dix dernières minutes, la borne des vingt-quatre heures, et la surenchère sur soi-même : simulées comme acceptées (budget 410 et 416), mais leur effet sur la date de fin n'a pas été relu dans l'événement.
- Les comptes fermés : ce que fait un remboursement de moins de 0,1 ALGO vers un compte qui n'existe plus. Par lecture seulement sur ma version ; c'est précisément la surface que le correctif d'après (section 7) traite, et je ne l'ai pas éprouvée.
- Les hausses de frais du réseau : les transactions internes portent un frais fixe de 1 000 ; en cas de congestion, c'est la mise en commun des frais du groupe qui devra compenser, ce que je n'ai pas éprouvé.
- L'épuisement réel du disponible du coffre : j'ai mesuré la fuite par cycle (C2), pas le moment où une sortie ordinaire échoue faute de disponible, ni ce qui se passe alors pour les autres utilisateurs.
- La conformité de l'ARC-56 publié avec ce que le site envoie réellement, la composition des groupes côté site, et les portefeuilles.
- Les lots à la borne sur la version d'après : mes budgets sont ceux de 196123ea… ; toute garde ajoutée depuis les change.
- Le comportement sous horodatage adverse (un proposeur de bloc qui décale
latest_timestamp), les comptes re-clés, les signatures logiques comme appelants. - Les offres expirées :
reprendre_une_offre_expireen'a été exercée que par sa garde « offre sans expiration », pas sur une offre réellement échue. - Le chemin de pénalité en jeton (gagnant dés-opté sur une enchère en jeton) : mesuré en ALGO seulement (E3).
7. Contrôle ciblé sur la version d'après correctif
Fait après la clôture de la phase aveugle, une fois toutes les mesures acquises et l'instance fermée. J'ai lu l'écart entre ma copie (196123ea…) et la copie d'après correctif (49f6750d…, bytecode 4869956f…, 5 321 octets, empreintes vérifiées par le greffier) : 136 lignes, dont l'essentiel est du commentaire.
Ce que le correctif fait. Une sous-routine _le_paiement_aboutira(vers, montant) : un paiement en ALGO inférieur au solde minimum vers un compte qui n'existe plus ferait échouer tout le groupe ; le contrat n'émet alors pas le paiement et laisse le montant au coffre. Elle garde quatre endroits : le remboursement des dépôts, le paiement du vendeur et de la commission dans acheter, le remboursement en ALGO de _rendre_l_argent, et la provision rendue à l'enchérisseur dépassé. C'est le cas « comptes fermés » que j'avais rangé dans ce que je n'avais pas regardé ; il est traité.
Sort de mes constats, vérifié ligne par ligne dans la copie d'après correctif : aucun n'est touché. La tolérance de fermer_une_monnaie lit toujours le plancher courant (C1, ligne 2833) ; les trois chemins d'offre rendent toujours offre.depot sans condition et reclamer paie toujours la boîte (C2) ; depot_mise n'est toujours rendu que dans encherir (C3) ; accepter_l_offre n'a toujours pas de garde contre son propre auteur (C4) ; refermer_une_position exige toujours un solde nul (C5, ligne 2875) ; les dépôts retiennent toujours le dû et non le versé (C6) ; le produit des planchers est toujours calculé en 64 bits (C7, ligne 2000) ; aucun événement n'a été ajouté (C9). Mes budgets ne valent que pour ma version : la nouvelle garde ajoute des lectures de solde sur les chemins en ALGO, à remesurer.
Une remarque sur le correctif lui-même, par lecture seulement : quand le paiement n'est pas émis, le montant reste au coffre sans créance inscrite. Un vendeur qui a fermé son compte perd le prix d'une vente sous 0,1 ALGO, un enchérisseur sa provision, un offrant son dépôt, définitivement, et cet argent ira à l'exploitant à la fermeture du marché. C'est cohérent avec le choix annoncé (« il ne coûte qu'à celui à qui il est arrivé »), mais c'est une confiscation silencieuse de plus, à écrire quelque part où l'utilisateur la lira.
8. Matériel et traces
- Instance principale 770822000 (coffre ECSQSHUG…), vidée puis fermée au tour 66882085 ; instance jetable 770822702, laissée infermable (voir C5). Comptes du banc audit-bf, prêtés par le greffier ; ménage fait et fonds remontés : 34,64 ALGO disponibles sur le déployeur à la fin, contre 36,17 après le nettoyage initial, la différence étant les frais de la campagne et ce qui dort dans 770822702.
- Scripts et journaux :
/Volumes/DeepOnion/ASA Compass/relecture-aveugle-3/(banc.py, 01 à 12, mesures/*.md). La copie du source relu est danscopie-relue/, celle d'après correctif danscopie-apres-correctif/. - Actifs d'essai : pièces 770822017, 770822028, 770822029, 770822031, 770822032, lot 770822536 à 770822571 environ, édition x2 770822037, monnaie J 770822050, monnaie K 770822354.
Rapport rédigé en français, sans tiret cadratin, par la session de relecture aveugle 3. Empreintes relevées sur testnet-api.algonode.cloud.