ASA Compass · contrat de règlement · relecture adverse

Ce que le contrat ne vérifie pas

Relecture statique, ligne à ligne, conduite sans lire aucun rapport d'audit antérieur ni la liste des défauts connus. Chaque conclusion a été éprouvée sur le testnet Algorand, sur une instance déployée pour cette relecture.

Source reluecontrat/marche/contrat.py
2 058 lignes
Empreintea69217710c287fe2
d95c1b3534b5fc1e
Instance d'essai770797552
Programme mesuré4 522 octets
67fd7a8736810d52
Ce qui rattache mes mesures à l'application réelle

J'ai compilé la source gelée dans mon propre dossier et obtenu un TEAL identique bit pour bit à celui des artefacts. Puis j'ai lu le programme d'approbation de trois endroits et comparé leurs empreintes :

app 770793296 (celle des autres sessions)  4522 o  sha256 67fd7a8736810d52dd08c751
app 770797552 (la mienne)                4522 o  sha256 67fd7a8736810d52dd08c751
ma compilation locale                    4522 o  sha256 67fd7a8736810d52dd08c751

Ce que j'ai mesuré sur mon instance vaut donc exactement pour l'application déployée.

Ce contrat ne doit pas être déployé sur le réseau principal en l'état.

J'ai trouvé dix-huit défauts, et voici où ils tombent. Le plus grave fait perdre à un tiers parfaitement innocent, longtemps après les faits, la pièce qu'il a payée et le dépôt qu'il a versé. Cinq autres rendent un bien ou une somme définitivement inaccessibles, dont un qui permet en outre à n'importe qui de fermer une monnaie du marché, contournant un pouvoir réservé à l'exploitant en n'y passant pas. Deux font sortir du coffre plus d'argent qu'il n'y est entré, et un fait passer l'argent d'un acheteur au vendeur sans contrepartie. Un donne à l'exploitant un pouvoir de blocage que le contrat déclare ne pas exister. Les huit restants coûtent de l'argent, affaiblissent une protection, faussent l'historique publié, laissent l'exploitant sans réponse en cas d'incident, ou décrivent une procédure que le code refuse.

Aucun de ces dix-huit n'est une garde qui refuserait mal : ce sont toutes des gardes qui n'existent pas. C'est pourquoi aucun ne pouvait apparaître dans un compteur de contrôles verts.

Le plus grave se ramène à une seule phrase, répétée cinq fois dans le fichier : le contrat compte les transactions du groupe, mais ne les consomme jamais. Une même transaction sert donc autant d'appels qu'on en place dans le groupe.

1. Ce que j'ai trouvé

Classé par ce que ça coûte, pas par l'ordre où je l'ai trouvé. Chaque défaut porte sa reproduction : le numéro de tour permet de retrouver la transaction sur un explorateur du testnet.

Le défaut de fond, et ses cinq visages

Cinq méthodes lisent le groupe atomique avec la même boucle for i in urange(Global.group_size) pour constater qu'une somme est arrivée ou qu'une pièce est passée. Aucune ne marque la transaction constatée. Le protocole Algorand n'offre aucun marquage implicite : deux appels de la même application dans un même groupe voient donc rigoureusement la même chose.

À l'inverse, les paramètres déclarés de type transaction en ARC-4 (le depot de lister, d'offrir, d'encherir, de mettre_aux_encheres et d'accepter_un_jeton) sont résolus par le compilateur en GroupIndex moins 1, ce que j'ai lu dans le TEAL. Cette position est imposée par le protocole et ne se falsifie pas. Le principe du dépôt n'est donc pas en cause : c'est la lecture par balayage qui l'est.

01 Un seul paiement finance autant d'achats qu'on veut critique

acheter, boucle ligne 936, garde absente après la ligne 956

acheter ne reçoit pas le paiement en argument : il l'additionne en parcourant le groupe, puis vérifie recu >= prix + commission. Deux appels placés dans le même groupe additionnent le même paiement et se croient chacun payé. Le contrat verse alors deux fois le prix au vendeur et livre deux fois les pièces, pour un seul encaissement. La différence sort du coffre, c'est-à-dire de l'argent que d'autres utilisateurs y ont immobilisé : mises d'enchères en cours, offres en attente, dépôts de vendeurs.

Reproduction, tour 66866432
deux annonces à 0,2 ALGO, groupe de trois transactions :
  [ paiement 0,202 ALGO , acheter(0) , acheter(1) ]

le groupe est accepté
  acheteur   -0,205000 ALGO   et il repart avec LES DEUX pièces
  vendeur    +0,679400 ALGO
  coffre     -0,489400 ALGO
  pièces reçues : 770797563 et 770797565, valeur affichée 0,4 ALGO

Le groupe relu sur l'indexeur, et non dans mon propre journal, pour que l'affirmation soit vérifiable par quiconque ouvrira le bloc 66866432 :

groupe de 3 transactions
  paiement de 202 000 µALGO vers le coffre
  appel app 770797552, sélecteur f94001c5, annonce 0  -> 4 transactions internes
  appel app 770797552, sélecteur f94001c5, annonce 1  -> 4 transactions internes

Le contrat a donc réglé deux ventes complètes, huit transactions internes en tout, pour un unique encaissement qui n'en couvrait qu'une.

Un groupe Algorand porte seize transactions : un paiement peut donc financer jusqu'à quinze achats. L'attaque se répète tant que le coffre a de quoi payer, et s'arrête quand il est vide, c'est-à-dire quand plus aucun retrait légitime n'est possible. Aucun réglage de l'exploitant ne l'arrête, voir le défaut 10.

02 Une enchère de quatre pièces en jeton ne peut ni être close ni être annulée critique

PIECES_PAR_ENCHERE_MAXIMUM ligne 108, PIECES_PAR_LOT_MAXIMUM ligne 107

Les deux maxima ont été mesurés, la note du fichier le dit, et le raisonnement est juste. Mais il a été mené sur une vente en ALGO. Un appel d'application porte au plus huit références. La clôture d'une enchère en nomme : les N pièces, le vendeur, le gagnant, l'exploitant et la boîte, soit N + 4, donc N au plus 4. Une vente libellée en JETON exige une référence de plus, celle du jeton, que le coffre doit nommer pour émettre le versement et la commission. Le compte devient N + 5, donc N au plus 3.

Reproduction, mesure du protocole
clôture, 4 pièces sans jeton   = 8 réf.  acceptée en forme
clôture, 4 pièces + le jeton   = 9 réf.  tx references exceed
                                          MaxAppTotalTxnReferences = 8
clôture, 3 pièces + le jeton   = 8 réf.  acceptée en forme

J'ai éprouvé la borne dans l'autre sens, pour ne pas fonder un défaut sur un simple comptage : un lot de cinq pièces distinctes se met en vente et s'achète réellement en ALGO, tours 66867308 et 66867321, les cinq pièces arrivant chez l'acheteur. La constante est donc exacte pour le cas où elle a été mesurée, et c'est précisément ce qui rend le décalage en jeton net : il ne s'agit pas d'une marge approximative, mais d'une référence de plus sur une limite qui est atteinte au caractère près.

Une enchère de quatre pièces en jeton se met donc en vente sans obstacle. Sa réserve une fois atteinte, la transaction de clôture n'est pas constructible, et annuler_l_enchere la refuse par la garde reserve atteinte, vente due, que j'ai fait jouer séparément. Les quatre pièces et la mise du gagnant sont alors perdues pour toujours.

C'est exactement le blocage que la note de PIECES_PAR_LOT_MAXIMUM dit vouloir éviter, revenu par la porte de la monnaie de paiement. Le même décalage vaut pour l'achat : un lot de cinq pièces en jeton est déposable sans être achetable, mais là le vendeur peut au moins annuler.

03 Une pièce déposée une seule fois, deux positions qui la promettent critique

lister ligne 747 et mettre_aux_encheres ligne 1399, même boucle

Les deux méthodes construisent leur liste de pièces en balayant le groupe. Rien n'interdit qu'elles cohabitent, et aucune ne marque le transfert. Un seul NFT déposé ouvre donc à la fois une annonce à prix fixe et une enchère.

Reproduction, tours 66866466, 66866481 et 66866485
groupe de cinq transactions, un seul transfert de pièce :
  [ dépôt , lister , dépôt , mettre_aux_encheres , transfert ]
accepté : annonce 2 et enchère 0 promettent toutes deux 770797699

un enchérisseur mise 0,13 ALGO de bonne foi, la pièce est au coffre
le vendeur vend l'annonce : la pièce sort, le coffre en détient 0
l'enchère promet toujours cette pièce et retient 0,13 ALGO

le vendeur tente d'annuler pour rendre l'argent :
  REFUSÉ, garde « reserve atteinte, vente due » (pc 3058)

L'échéance est passée, et la clôture a été tentée. Elle échoue, mais pas où je l'attendais : le contrat ne bute pas en transférant une pièce absente, il bute dès la LECTURE de son solde, le coffre s'étant dés-opté en livrant la pièce par l'autre porte, via asset_close_to.

La clôture, une heure après l'ouverture
clôture tentée par un tiers, ce que le contrat autorise :
  ÉCHEC pc 4351, garde « account opted into asset »

le vendeur retente d'annuler, après l'échéance :
  REFUSÉ pc 3058, garde « reserve atteinte, vente due »

la boîte de l'enchère existe toujours, 200 octets,
et le coffre n'a plus aucune position sur la pièce.

L'enchérisseur n'a rien fait d'anormal. Il a misé sur une pièce réellement présente au coffre. Son argent est immobilisé dans une enchère que personne ne peut plus ni clore ni annuler, et cela coûte à peine des frais de réseau à celui qui l'a montée. Mais l'enchère ne dort pas pour autant, et c'est le défaut suivant.

16 Une position fantôme est une créance dormante sur la prochaine occurrence de la pièce critique

conséquence des défauts 03 et 04, aucune garde nulle part

C'est le défaut le plus grave que j'aie trouvé, et je ne l'avais pas vu avant que la mesure précédente n'aboutisse. Une position qui référence une pièce que le coffre ne détient plus n'est pas inerte : elle attend. Le jour où n'importe qui dépose de nouveau cette pièce au marché, le coffre s'y réopte et la reçoit, et la position fantôme redevient exécutable. Elle livrera alors à son bénéficiaire la pièce d'un vendeur qui n'a jamais eu affaire à elle.

Reproduction, tour 66867861 et suivants
un tiers avait acheté la pièce au prix fort, en toute légitimité
il la remet en vente : opération parfaitement ordinaire, annonce 10
  le coffre se réopte et détient de nouveau la pièce

n'importe qui clôt alors l'enchère fantôme :
  LA CLÔTURE PASSE MAINTENANT
  gagnant de l'enchère fantôme   détient 1 exemplaire
  tiers qui a déposé             détient 0 exemplaire

le tiers tente d'annuler son annonce pour récupérer sa pièce :
  ÉCHEC, garde « account opted into asset »
  il a perdu SA PIÈCE ET SON DÉPÔT, sans avoir rien fait d'anormal

Ce que cela vaut pour celui qui l'arme. Il vend la pièce au prix fort par l'annonce, puis la récupère gratuitement dès que sa victime, ou n'importe quel propriétaire ultérieur, la remet en vente. Il a donc encaissé le prix et repris le bien. Le montage lui coûte un dépôt d'enchère et une mise, qu'il récupère. Rien n'oblige le déclenchement à survenir vite : le fantôme peut dormir des mois, et la victime finale n'a aucun moyen de savoir qu'il existe, la boîte d'une enchère ne se lisant pas depuis une annonce.

Pourquoi cela décide de la correction. Fermer le balayage de groupe empêche d'armer de nouveaux fantômes, mais le défaut 04 arme exactement le même piège par une autre porte, la reprise de l'actif par son émetteur. Les deux corrections se tiennent : exclure du marché les actifs dont la reprise n'est pas vide cesse d'être une prudence pour devenir une nécessité. Je n'ai pas trouvé de troisième porte pour créer un fantôme, ces deux-là fermées, mais je n'ai pas non plus de preuve qu'il n'y en a pas : la garde de ceinture serait qu'une sortie refuse de livrer une pièce arrivée au coffre après l'ouverture de la position.

04 Un actif dont l'émetteur a gardé la reprise vide le coffre à volonté critique

lister ligne 747 et mettre_aux_encheres ligne 1399, garde absente

La note de tête du contrat s'appuie sur un fait mesuré hors chaîne : « les NFT de 2021 visés par ce projet ont tous une clawback vide, vérifié sur la chaîne ». C'est une constante qui décrit une réalité, et aucune ligne du contrat ne la vérifie au dépôt. Or le marché accepte n'importe quel actif, pas seulement ce corpus.

Reproduction, tour 66866788
actif 770798271, clawback = l'émetteur
le vendeur le détient de bonne foi et le met en vente
  annonce 7 ouverte, le contrat n'objecte rien

l'émetteur reprend la pièce DANS LE COFFRE, sans rien signer du marché
  le coffre détient 0 exemplaire, l'annonce en promet toujours 1

le vendeur tente d'annuler pour récupérer son dépôt :
  ÉCHEC : inner tx 0 failed: underflow on subtracting 1
                 from sender amount 0
  garde du contrat ayant joué : AUCUNE

Otage tant que l'actif existe, définitif dès qu'il est détruit. La distinction compte, et je l'ai mesurée dans les deux sens plutôt que de la supposer.

Les deux branches, tours 66867217 et 66867236
A. l'émetteur RENVOIE la pièce au coffre, de son plein gré
     le vendeur retente d'annuler : l'annulation passe

B. l'émetteur DÉTRUIT l'actif au lieu de le rendre
     le vendeur tente d'annuler : ÉCHEC, underflow on
     subtracting 1 from sender amount 0
     plus aucun état du monde ne peut faire réussir cet appel

Dire « perdu pour toujours » sans distinguer serait donc faux dans la branche A. Ce qui est exact, et suffit largement : le bien d'un vendeur et l'argent d'un enchérisseur dépendent entièrement du bon vouloir de l'émetteur de la collection, qui n'a aucune obligation, et le blocage devient irréversible d'un seul geste de sa part. Sur une enchère dont la réserve est atteinte, l'annulation étant fermée, c'est la mise du gagnant qui disparaît. Ni le vendeur ni le gagnant n'ont commis d'erreur.

Et la destruction va plus loin que cette annonce. Le coffre reste opté à un actif qui n'existe plus, ce qui immobilise sa réserve d'acceptation à jamais. Aucune méthode ne peut la libérer : _rendre_les_pieces échoue sur un solde nul, et fermer_une_monnaie refuse par la garde « monnaie non acceptee », l'actif n'ayant jamais été une monnaie du marché. Mesuré sur mon coffre après l'essai : solde minimum imposé de 414 300 µALGO contre 100 000 pour un compte nu. Comme fermer_le_marche exige que ce chiffre soit retombé au minimum nu, un seul actif détruit rend le marché infermable pour toujours.

La promesse publique du projet est ici en jeu. Écrire que le NFT « ne peut aller qu'à un acheteur payant ou revenir au vendeur » est faux pour tout actif dont la clawback n'est pas vide, et le contrat ne fait rien pour restreindre ce qu'il accepte.

05 Un seul transfert de pièce solde deux offres, le vendeur encaisse deux fois grave

accepter_l_offre, boucle ligne 1271

La méthode constate le passage de la pièce en balayant le groupe et pose trouvee = 1. Deux offres du même acheteur sur la même pièce se règlent donc avec un seul transfert. Un acheteur qui surenchérit sur lui-même sans retirer sa première offre, ce que rien n'empêche et que l'interface peut encourager, paie deux fois et ne reçoit qu'une pièce.

Reproduction, tour 66866530
deux offres de 0,15 ALGO du même acheteur sur la pièce 770797807
groupe : [ accepter_l_offre(0) , accepter_l_offre(1) , transfert ]

le groupe est accepté
  vendeur  +0,289000 ALGO pour UNE seule pièce vendue
  acheteur : 1 pièce reçue, 0,3 ALGO immobilisés
06 Un dépôt de boîte payé une fois, remboursé plusieurs fois grave

offrir, boucle ligne 1129

Une offre libellée en jeton exige un second dépôt en ALGO pour payer sa boîte, et ce dépôt est cherché par balayage. Chaque offre du groupe inscrit alors depot = 36 900 dans sa boîte, alors qu'une seule l'a financé. Le remboursement, lui, est bien versé à chacune.

Reproduction, tour 66866688
groupe : [ transfert jeton , offrir , transfert jeton , offrir ,
           paiement ALGO 0,0369 ]

accepté
  versé par l'offrant      0,041900 ALGO  (un seul dépôt, plus les frais)
  rendu après retrait      0,063800 ALGO
  bilan de l'aller-retour  +0,021900 ALGO  au détriment du coffre
07 L'exploitant peut bloquer toutes les ventes dans une monnaie grave

accepter_un_jeton ligne 1801, garde absente ; commission émise ligne 981

accepter_un_jeton fait accepter la monnaie au COFFRE. Rien ne vérifie que l'EXPLOITANT l'a acceptée, alors que c'est vers lui que part la commission. Une monnaie ouverte sans cela rend toute vente impossible dans cette monnaie, et le refus ne vient d'aucune garde du contrat : c'est le protocole qui rejette la transaction interne.

Reproduction, échec puis tour 66866626
monnaie 770797992 ouverte au marché par l'exploitant
l'exploitant l'a-t-il acceptée pour lui-même ?  NON

l'acheteur tente d'acheter en payant dans cette monnaie :
  ÉCHEC : must optin, asset 770797992 missing from AQ2GHD…
  garde du contrat ayant joué : AUCUNE

l'exploitant accepte la monnaie, RIEN D'AUTRE ne change :
  le même achat passe, tour 66866626

Cela contredit frontalement la quatrième mesure de sécurité, inscrite dans le constructeur : « Le SEUL pouvoir de l'exploitant est de suspendre les NOUVEAUX dépôts. Il ne doit exister aucun chemin de code permettant d'empêcher un retrait. » Se dés-opter d'une monnaie est un tel chemin, et il ne passe par aucune méthode du contrat, donc aucune relecture du seul fichier ne le montre. Sur une enchère en jeton dont la réserve est atteinte, le blocage est définitif, l'annulation étant interdite.

08 Une poussière envoyée par n'importe qui interdit à jamais de fermer une monnaie, donc le marché moyen

fermer_une_monnaie ligne 1861, garde trop grossière

La garde refuse dès que le coffre détient la moindre unité de la monnaie. Son intention est juste, mais elle ne distingue pas un dépôt légitime d'un résidu. Or n'importe qui peut envoyer une unité au coffre par un transfert ordinaire, hors du contrat, et le contrat n'a aucune méthode capable de balayer ce résidu.

Reproduction
après une vente propre, le coffre détient 0 unité de 770797992
un tiers quelconque envoie UNE unité au coffre
l'exploitant tente de fermer la monnaie :
  REFUSÉ, garde « le coffre detient encore cette monnaie »

Le défaut 04 produit d'ailleurs le même blocage par un autre chemin, et sans qu'aucune monnaie soit en cause : un actif déposé puis détruit par son émetteur laisse le coffre opté à jamais. Il y a donc au moins trois façons distinctes de rendre le marché infermable, dont deux qu'aucune méthode du contrat ne peut défaire.

La conséquence porte plus loin que la monnaie. fermer_le_marche exige que le solde minimum du coffre soit retombé à celui d'un compte nu, ce qui suppose qu'aucun actif ne reste accepté. Une monnaie qu'on ne peut plus fermer rend donc la fermeture du marché impossible, définitivement, et avec elle la récupération du solde immobilisé du créateur. Le surplus d'un acheteur qui paie trop, jamais rendu (défaut 12), produit le même effet sans aucune malveillance.

09 La réserve d'acceptation que personne ne récupère moyen

_rendre_le_depot ligne 566, et _rendre_les_pieces ligne 614

Sur un actif tiré à plusieurs exemplaires, le premier vendeur paie la réserve d'acceptation et le second n'en paie aucune. Le champ reserves borne correctement le remboursement par ce que chacun a versé, et empêche bien le second de voler le premier : cette correction fonctionne. Mais elle ne traite qu'une moitié du problème. Si le premier sort AVANT le second, il ne referme rien, le coffre détenant encore un exemplaire, donc il ne récupère pas sa réserve ; et quand le second referme, il n'a rien payé, donc il ne reçoit rien non plus.

Reproduction, tours 66866662 et 66866664
actif 770798077 tiré à deux exemplaires, deux annonces du même vendeur
  versé au total              0,197400 ALGO
annulation de l'annonce qui a PAYÉ la réserve, puis de l'autre
  position refermée, réserve libérée AU COFFRE
  récupéré                    0,075400 ALGO
  perte nette sur un aller-retour sans vente : 0,122000 ALGO

Les 0,1 ALGO restent au coffre sans propriétaire, et aucune méthode ne les réattribue. Le corpus visé étant surtout fait de pièces uniques, le cas est rare, mais il se produit dès qu'une collection comporte des éditions à plusieurs exemplaires.

10 La suspension ne ferme pas la porte par laquelle le coffre se vide moyen

trois gardes de suspension seulement, lignes 701, 1044 et 1356

suspendu est vérifié dans lister, offrir et mettre_aux_encheres. Ni acheter ni encherir ne la portent. Pour acheter c'est défendable, un achat étant un règlement et non un dépôt. Deux conséquences le sont moins.

  • encherir immobilise de l'argent neuf et n'est pas suspendable, ce qui contredit la formule « suspendre les NOUVEAUX dépôts ».
  • Surtout, le défaut 01 passe par acheter. En cas d'incident, l'exploitant n'a aucun moyen d'arrêter l'hémorragie : le seul pouvoir dont il dispose laisse ouverte exactement la porte par laquelle le coffre se vide. Le contrat étant immuable, il n'existe alors aucune réponse.
11 La documentation de lister décrit un groupe que le code refuse moyen

lister ligne 719, et la même phrase dans mettre_aux_encheres

Le commentaire 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 la boucle ne FILTRE pas les transferts d'actif, elle les EXIGE tous conformes, par des assert. Une acceptation est un transfert de zéro unité vers soi-même : elle échoue donc sur asset_receiver == app et sur asset_amount == 1.

Reproduction
[ dépôt , lister , transfert de la pièce , ACCEPTATION de la monnaie ]
  REFUSÉ, garde « piece mal adressee » (pc 638)

le même groupe SANS l'acceptation
  accepté, tour 66866578

Un site qui suivrait cette documentation composerait un groupe qui échoue toujours, précisément dans le cas normal du premier vendeur en XDB. La procédure décrite est impossible : il faut une signature séparée.

12 Le trop-payé n'est jamais rendu mineur

acheter ligne 956, offrir ligne 1121

La vérification est recu >= prix + commission, et seuls prix et commission ressortent. La différence reste au coffre sans qu'aucune méthode puisse la rendre. En ALGO c'est une perte pour l'acheteur ; en jeton, ce résidu déclenche en plus le blocage du défaut 08.

Où finit cet argent, et c'est ce qui relie trois défauts. Le trop-payé, la réserve orpheline du défaut 09 et la retenue indue du défaut 14 s'accumulent tous au coffre sans propriétaire. fermer_le_marche se termine par un close_remainder_to vers l'exploitant : à la fermeture, ces sommes lui reviennent donc intégralement. La garde qui empêche l'exploitant de partir avec la caisse est solide sur ce qui est encore dû à quelqu'un, mais elle ne voit pas ce qui a cessé d'être dû par accident de calcul.

13 Deux réglages qui échappent aux bornes qu'ils sont censés avoir mineur

mettre_aux_encheres ligne 1365, fixer_le_seuil_d_exoneration ligne 1968

  • Le plancher propre aux enchères ne s'applique que si jeton == 0. Une enchère libellée en jeton n'est tenue que par le plancher de la monnaie, qui peut être bien plus bas. Or ce plancher existe, la note le dit, pour que la part retenue sur un gagnant défaillant ait de quoi mordre. En jeton, elle peut redevenir symbolique, et le sabotage gratuit.
  • designer_le_jeton_exonere est à usage unique, pour qu'on ne puisse pas modifier les frais après coup. Mais fixer_le_seuil_d_exoneration n'est borné par rien et reste modifiable à volonté : fixer un seuil inatteignable annule l'exonération. La promesse tenue par une porte est reprise par l'autre.
14 Les sorties à frais nuls sont facturées deux fois mineur

_rendre_l_argent ligne 1151, et le paramètre sorties de _rendre_le_depot

_rendre_l_argent émet ses transactions avec fee=0 : elles sont donc payées par la mise en commun des frais du groupe, c'est-à-dire par celui qui appelle. Mais sorties les compte quand même, et _rendre_le_depot retient sorties * FRAIS_RESEAU sur le dépôt. L'appelant paie donc une première fois de sa poche, et une seconde fois par retenue.

Reproduction, tour 66866999, mesure encadrée sur la seule transaction jugée
une offre de 0,15 ALGO, dépôt de boîte 36 900, retirée par son auteur
frais d'appel réglés au strict nécessaire : 2 000 (l'appel + la sortie à fee 0)

  variation de l'offrant   +182 900 µALGO
  variation du coffre         -185 900 µALGO
  attendu si le compte juste  +183 900 µALGO
  écart                       -1 000 µALGO

le coffre a émis DEUX transactions internes, dont UNE SEULE à ses frais,
et il a pourtant retenu 2 000 µALGO sur le dépôt.

Le même écart vaut pour reprendre_une_offre_expiree et accepter_l_offre, et il monte à 2 000 dans la clôture d'une enchère conclue, où sorties compte le versement au vendeur et la commission, tous deux à frais nuls. À l'inverse, acheter, annuler, reprendre_une_annonce_expiree et annuler_l_enchere comptent juste : leurs sorties portent toutes un frais explicite, ou bien celles à frais nuls ne sont pas comptées. Le défaut est donc précisément localisé, et il n'est pas de conception : c'est un décompte.

15 Un vendeur peut fabriquer le prix public de sa propre pièce moyen

acheter ligne 910 et encherir ligne 1463, aucune comparaison des parties

Le contrat émet des événements que Karl a voulus pour une raison précise, écrite dans le fichier : rendre « l'historique exact et indexable en une requête ». Aucune méthode ne compare pourtant l'acheteur au vendeur, ni l'enchérisseur au vendeur. Un détenteur peut donc acheter sa propre annonce, ou faire monter sa propre enchère, et le contrat inscrit cette vente dans l'historique public exactement comme une vraie.

Reproduction, tour 66867153, événement relu sur l'indexeur
le vendeur achète sa propre annonce 3, affichée 0,2 ALGO
  accepté, il récupère sa pièce et son dépôt

l'événement VenteConclue réellement publié, décodé depuis le bloc :
  sélecteur  e04d391d
  annonce    3
  vendeur    XVPHZ3Z5FNXCZ7W5YVB3ZN75MXK3OU5LUHTVR4TXPRE34C6GPJY46Z4VVM
  acheteur   XVPHZ3Z5FNXCZ7W5YVB3ZN75MXK3OU5LUHTVR4TXPRE34C6GPJY46Z4VVM
  prix       200 000
  les deux adresses sont identiques

Le coût de l'opération est la commission, soit un pour cent du prix qu'on souhaite afficher, plus quelques frais de réseau. Tout le reste revient à son auteur. Un catalogue peut donc se voir doter de prix de référence entièrement fabriqués, et l'acheteur suivant paiera d'après eux.

Les trois chemins de vente sont concernés, pas seulement l'achat. Je l'ai vérifié sur l'endroit jumeau plutôt que de le déduire : un détenteur fait une offre sur sa propre pièce et l'accepte lui-même, en se l'envoyant, ce que le contrat accepte puisqu'il ne compare jamais les deux parties.

L'endroit jumeau, tour 66867357
événement OffreConclue réellement publié, décodé depuis le bloc :
  sélecteur  132bed09
  vendeur    LU44CKFWNKOMJT7L4J23G4HSQGHXPSAORCWZY37TDGCGCPSE5X6DRDMALI
  acheteur   LU44CKFWNKOMJT7L4J23G4HSQGHXPSAORCWZY37TDGCGCPSE5X6DRDMALI
  montant    150 000
  les deux adresses sont identiques

Reste encherir, où rien ne compare non plus l'enchérisseur au vendeur. Je ne l'ai pas joué, une enchère demandant une heure, mais la lecture ne laisse aucun doute : la méthode n'a que trois gardes de montant et une garde de temps.

Le contrat ne peut pas empêcher cela complètement, deux comptes complices suffisant à contourner toute comparaison d'adresses. Mais refuser le cas identique coûte une ligne, et il fait la différence entre une manipulation qui demande deux comptes et une somme immobilisée, et une qui n'en demande aucun. Surtout, l'événement devrait porter de quoi distinguer les deux : ce qu'il n'inscrit pas est perdu pour toujours, comme le dit sa propre note.

17 Un même actif peut être une monnaie et une pièce, et le solde ne les distingue pas critique

lister ligne 747, mettre_aux_encheres ligne 1399, aucune comparaison aux planchers

Rien n'interdit de déposer comme PIÈCE un actif que l'exploitant a ouvert comme MONNAIE. Or le contrat compte les pièces par le SOLDE du coffre, et un solde ne distingue pas les unités déposées comme pièce de celles reçues comme paiement : un actif fongible est fongible, et aucune instruction de la machine virtuelle ne dit d'où vient une unité. Une cause unique, deux effets opposés selon le solde.

Première face, quand le solde dépasse un : le contrat livre l'argent des autres.

Reproduction, tours 66868058 à 66868066
une unité déposée comme pièce, deux positions ouvertes dessus
un tiers dépose une offre de 200 unités de cette même monnaie
  le coffre détient 1 pièce + 200 d'argent, indiscernables
les deux positions sont vendues : 402 → 401 → 400
  DEUX unités sorties pour UNE déposée
  la seconde a été prise sur le stock où dort l'argent des offrants

Seconde face, quand le solde vaut exactement un : n'importe qui ferme une monnaie du marché. Celle-ci n'a besoin d'aucun autre défaut. Ni balayage, ni actif reprenable : elle marche sur un contrat où tout le reste est corrigé.

Reproduction, tours 66868116 et 66868119
l'exploitant ouvre une monnaie, le coffre l'accepte
un vendeur quelconque dépose UNE unité comme pièce, puis ANNULE
  le coffre se dés-opte de la monnaie (asset_close_to)

la boîte du plancher existe toujours : le marché la croit ouverte
une offre dans cette monnaie      REFUSÉE, garde : aucune
l'exploitant ferme la monnaie      passe, la réserve lui revient

fermer_une_monnaie est réservée à l'exploitant par une garde explicite. Ce chemin-là la contourne en n'y passant pas : un pouvoir réservé qui fuit, pour le prix d'un dépôt et d'une annulation, tous deux remboursés. Toutes les annonces libellées dans cette monnaie deviennent inachetables, et le refus ne vient d'aucune garde du contrat : c'est le protocole qui rejette le transfert, donc l'utilisateur reçoit un message brut. Mon banc l'affiche comme « garde : aucune », et c'est cette absence-là qui est le signal.

La garde tient en une ligne et ferme les deux faces : refuser comme pièce un actif présent dans self.planchers. Le contrat ne peut pas s'en sortir en renforçant ses lectures de solde, puisque c'est le solde lui-même qui ment ; il faut séparer les deux usages en amont.

18 Le seuil d'exonération se loue le temps d'un groupe atomique moyen

_commission ligne 527, lecture du solde de celui qui paie

Karl a posé ce seuil pour une raison qu'il a dite lui-même : « seulement un coin, ça ne fait pas de sens ». Mais _commission lit le solde du payeur à l'instant de l'appel, et un solde à un instant n'est pas une détention. Il suffit d'emprunter le jeton dans le même groupe et de le rendre juste après, tout étant atomique.

Reproduction, tours 66868169 et 66868179, seuil fixé à 100 000
A. achat par un compte détenant 3 792 unités        commission : 10
B. le MÊME achat, 100 000 unités prêtées dans le
   groupe et rendues dans la même transaction       commission : 0

l'acheteur n'a rien gardé.

Un seul compte détenant le million de jetons peut donc exonérer tout le marché, en les prêtant transaction par transaction pour le prix de deux transferts. L'incitation à détenir le jeton, qui est l'intention entière de la mesure, disparaît : il suffit d'en connaître un qui en a.

Je n'ai pas de bonne correction à proposer, et cela fait partie du constat. Aucune instruction de la machine virtuelle ne dit depuis quand un compte détient un actif : le contrat ne peut mesurer qu'un solde. Trois voies, dans l'ordre où je les défendrais : l'accepter et le documenter, en sachant que le seuil devient une friction et non une barrière ; faire porter l'exonération sur une inscription enregistrée dans une boîte, qui coûte du solde immobilisé et ne se prête pas ; ou refuser un groupe contenant un transfert du jeton vers l'acheteur, ce que je ne recommande pas, étant contournable par un compte relais et cassant l'usage légitime de qui achète ses jetons juste avant. La première me paraît la bonne, et c'est une décision de modèle économique, pas une décision technique.

Le budget de calcul, mesuré à la borne

Cette partie manquait à ma section « non vérifié » et n'y est plus. Un appel dispose de 700 unités de budget de calcul, et c'est la seconde limite qui décide des maxima de pièces, l'autre étant les huit références. Voici ce que consomment réellement les chemins les plus lourds.

Chemin mesuréPiècesMonnaieBudget consomméIssue réelle
clôture, chemin conclu4ALGO612 / 700passe, tour 66868930
clôture, chemin conclu3jeton554 / 700passe, tour 66868932
clôture, chemin de repli4ALGO612 / 700passe, tour 66868985, 4 pièces rendues
achat d'un lot1jeton439 / 700simulation
achat d'un lot3jeton541 / 700simulation

Le coût marginal mesuré est de 51 unités par pièce à l'achat et d'environ 58 à la clôture. Deux conséquences chiffrées, qui valident les constantes du fichier pour la monnaie où elles ont été prises et les invalident pour l'autre :

Un piège de mesure qui vaut plus que ces chiffres. L'API de simulation renvoie un champ « budget disponible » qui valait 5 600 pour un appel seul dans son groupe, soit 700 multiplié par huit, l'appel ayant émis sept transactions internes. Ce n'est pas la limite applicable : elle reste de 700 par transaction d'application du groupe. Lire ce champ comme une marge conduirait à fixer une borne à quinze pièces et à graver un contrat dont les enchères ne peuvent pas se clore. La marge se calcule 700 moins le consommé, et rien d'autre. Je l'écris ici parce que je m'y suis laissé prendre pendant deux minutes avant de recouper avec un fait connu : si la limite était de plusieurs milliers, sept pièces ne produiraient jamais l'erreur de budget que d'autres ont mesurée.

Les preuves, vérifiables sans mon banc

Chaque mesure ci-dessus cite un tour, ce qui suffit à la retrouver tant qu'un indexeur garde ce bloc. Un identifiant de transaction, lui, se prouve. Les voici pour les mesures qu'un lecteur voudra revérifier, toutes sur l'application 770797552, dont le programme d'approbation porte l'empreinte 67fd7a8736810d52, identique à celle de l'application 770793296.

MesureTourIdentifiant de transaction
01 deux achats, un seul paiement668664326VUIQBNNU3IAA7WNWNQ7TOMV46ECTJJJMJPLCY5B5YBURPOBY4PQ
03 une pièce, deux positions668664662ZILQQRB74K2FDWJ4KDXMCCWRVFXU34ZAEOSDRX22HUA54MV5UJQ
03 la vente vide le coffre66866485UEIODWXW4K4B5CC3OK2GALSTQDUIINNXAHYTRXS4OEHYP3YEJABA
16 le fantôme se déclenche66867861PQ4SY6IICOGDKB6GUNCOGFWPJXUA6IG5BK42CXGMGTIO7SUNI2YQ
04 reprise par l'émetteur66866788K6VSSACHSL2RML6O46S4ZKQ5AXLHMBPIZKLCZ2SWSNPUNVEKLRJA
04 branche otage, l'annulation repasse66867217GHE6VFOV4RPBIWHI3S7UP54A6PGXMP6FYL3A5EFFDJOXX6O44NGQ
04 branche définitive, actif détruit668672367E5WNXQ5RMOYFWGRG3TCSI3SZZFFSJYC3IKBSKXKST4BYMUM6QXA
05 deux offres, un seul transfert66866530XOYR547JJ2QQDUMUN7XEIEJZ4Q4D2LBFT2IVJFQVJ6335NK3LMMQ
06 un dépôt de boîte, deux remboursements66866688SPFURQWA7EFSPVTYWO36NT7ZHETEFSDOZQXTLJ5RACK7ROWJ23WQ
07 l'achat passe après l'opt-in de l'exploitant66866626PZTREIB2VFZZIHS2ISBWJXEEQGCMWFJYIZ5X7Y2J5J3SOLS5GQBA
13 lot de cinq pièces acheté66867321DUBJVFXGN5F7EGRSHYQUEF32O3CGXFM74YXT7NXJN4B63UJMVIVQ
17 deux unités sorties pour une déposée668680662WNOHQR2NCLQYCSCRUSIQ5ODRRUHER2AYGDFH2CEND3GHK547VHA
17 la monnaie se dés-opte668681164VKPVT4AH7RK47GDHGTLCO7GRWYFKDZWKVCXYMEV5QBHEVKS6GYQ
18 le seuil emprunté66868179Z7ATYQSWHQHJLVFTBNSJMN5VISSIKHORPB2F2EIVLUQ2HFJZRLUA

Une réserve sur ces preuves, et elle n'est pas de forme. L'historique d'une application supprimée survit dans ce que l'INDEXEUR conserve, pas dans le protocole. Un indexeur qui purgerait ses blocs anciens effacerait ces lignes. Les identifiants ci-dessus valent donc aussi longtemps qu'un indexeur du réseau d'essai les garde, ce qui est plus long qu'un tour seul mais n'est pas éternel.

2. Ce que j'ai regardé et jugé sain

Une relecture ne vaut que si elle dit aussi ce qu'elle a examiné sans rien trouver. Ces points-là ont été lus ligne à ligne, et pour plusieurs d'entre eux éprouvés sur la chaîne.

Les arguments transaction ARC-4 ne se falsifient pas. Le TEAL les résout en txn GroupIndex ; 1 ; -, avec un contrôle de type. Un second appel ne peut pas viser le dépôt du premier : il lirait le premier appel, dont le type n'est pas pay. Le dépôt d'offrir, celui d'encherir et celui d'accepter_un_jeton sont donc corrects.

La fermeture de position ne livre jamais la pièce d'un autre. _rendre_les_pieces ne pose asset_close_to que si le coffre ne détient plus que l'exemplaire qui sort. Mesuré à l'essai des éditions multiples : la première sortie laisse la position ouverte, la seconde la referme. Le raisonnement de la note est exact.

Un vendeur ne peut pas empocher la réserve d'un autre. Le champ reserves borne le remboursement par ce que ce vendeur a effectivement versé. La correction décrite dans les commentaires fonctionne. Le défaut 09 est son revers, pas son échec.

L'exploitant ne peut pas partir avec la caisse. fermer_le_marche ne se contente pas d'une condition déclarative : elle lit le solde minimum réel imposé par le protocole au coffre et exige qu'il soit retombé à celui d'un compte nu. Une seule boîte, un seul actif accepté suffit à la bloquer. C'est la meilleure garde du fichier.

La part retenue sur un gagnant défaillant ne peut pas devenir une confiscation. regler_les_encheres borne le taux à PENALITE_POINTS_PLAFOND, soit vingt pour cent, et cette borne est une constante que l'exploitant ne peut pas déplacer. La pénalité est en outre plafonnée en montant absolu.

Le gel d'enchère par surenchère sur soi-même est bien fermé. Les deux gardes se complètent comme la note l'annonce : on ne prolonge que sur une mise venue d'un autre compte, ce qui ferme le cas de l'attaquant seul, et fin_maximum est fixée à l'ouverture et jamais modifiée, ce qui ferme celui de deux complices. J'ai vérifié dans la boîte que fin_maximum vaut bien fin + 86 400 et qu'aucun chemin ne la réécrit.

Sans réserve déclarée, le prix de départ en tient lieu. _reserve_atteinte ferme le trou par lequel un vendeur sans réserve aurait pu annuler à volonté après avoir vu les mises. Mesuré : mon enchère à réserve nulle et départ 0,13 est bien réputée atteinte à 0,13, et l'annulation a été refusée.

Le repli de clôture quand le gagnant s'est dés-opté est le bon choix. Exiger l'acceptation au moment de la mise n'empêcherait rien, puisque rien n'oblige à rester accepté. Le repli, lui, garantit que les pièces reviennent au vendeur au lieu de rester au coffre. Ce raisonnement, écrit dans le commentaire, tient.

Les méthodes ouvertes à tous ne sont pas détournables. reprendre_une_annonce_expiree, reprendre_une_offre_expiree et clore_l_enchere lisent leurs destinataires dans la boîte, jamais dans les arguments ni dans Txn.sender. Le pire qu'un inconnu puisse faire est de payer les frais à la place de l'ayant droit.

Aucun dépassement d'entier trouvé. prix * COMMISSION_POINTS plafonne à 1014 pour un prix au plafond, loin des 64 bits. Les sommes de dépôt, les réserves et les calculs de temps restent dans les mêmes ordres. La sentinelle AUCUNE_EXONERATION est correctement choisie : un identifiant d'actif ne peut pas valoir le maximum d'un entier 64 bits, et zéro aurait désigné l'ALGO.

Aucune méthode réservée à l'exploitant ne déplace l'avoir d'un tiers. Vérifié méthode par méthode : sur les huit méthodes gardées par Txn.sender == self.exploitant, six n'émettent aucune transaction interne, et les deux qui en émettent, fermer_une_monnaie et fermer_le_marche, sont l'une et l'autre conditionnées à ce que le coffre ne détienne plus rien d'autrui. Le pouvoir de blocage du défaut 07 ne passe justement par aucune méthode : il s'exerce en dehors du contrat.

Le maximum de pièces annoncé tient, pour la monnaie où il a été mesuré. Un lot de cinq pièces distinctes se dépose et s'achète en ALGO, l'appel portant exactement huit références. Ni le budget de calcul ni la limite de références ne mordent avant. La mesure inscrite dans le fichier est donc juste, et mon défaut 02 ne la contredit pas : il ajoute la dimension qu'elle n'a pas explorée.

Le programme d'effacement est inatteignable, et c'est correct. Il approuve tout, ce qui inquiète à la lecture. Mais le contrat déclare un schéma d'état local vide, vérifié sur l'application déployée comme sur la mienne (local {num-uint: 0, num-byte-slice: 0}) : personne ne peut donc s'opter à l'application, et il n'existe aucun chemin pour atteindre ce programme.

3. Ce que je n'ai pas vérifié

C'est cette section qui dit ce que vaut le reste. Chaque ligne porte son motif, et aucune n'est là par prudence de façade.

Non vérifiéMotif
Le scénario complet du défaut 02, joué de bout en bout Chaque maillon est mesuré séparément, la chaîne entière ne l'est pas. Trois clôtures réelles ont été jouées depuis, à l'échéance, ce qui établit le coût des chemins mais pas l'enchère de quatre pièces en jeton, qu'aucune transaction ne peut porter.
Le blocage définitif d'une enchère par un exploitant qui se dés-opte Déduit de deux mesures réelles, l'échec de la commission et le refus d'annulation, mais non joué en une seule chaîne, pour la même raison de durée.
Le budget de calcul, 700 unités par appel Mesuré depuis, voir « Le budget de calcul, mesuré à la borne ». Reste non mesurée la clôture sur la version corrigée, dont les gardes neuves lisent un solde de plus par pièce : le banc de testnet s'est épuisé avant, les comptes d'essai passant sous leur propre solde minimum. Mes chiffres sont donc un PLAFOND pour cette version, jamais une garantie.
Les actifs gelés, par freeze ou default_frozen Non éprouvé. Par lecture, le mécanisme est le même que celui du défaut 04, sans qu'il soit besoin d'une reprise : un gel suffit à faire échouer toute sortie. Un gel est réversible, il tomberait donc dans la branche « otage » que j'ai mesurée pour la reprise, jamais dans la branche définitive.
Le comportement en cas de rekey d'un compte Non examiné. Les adresses sont stockées dans les boîtes, un rekey ne les change pas, mais je n'ai pas cherché les conséquences.
Les enchères libellées en jeton, de bout en bout Seuls l'ouverture et le comptage de références ont été mesurés. La clôture réelle d'une enchère en jeton n'a pas été jouée.
Les événements ARC-28 et leur exactitude Hors de mon angle. Je n'ai pas vérifié que les champs émis correspondent à ce qui s'est réellement passé.
Le service, le site et la composition réelle des groupes Hors périmètre : ma relecture porte sur le contrat seul. Plusieurs défauts ci-dessus dépendent pourtant de ce que le site compose, notamment le 11.
Le comportement sous charge, et les courses entre transactions Non éprouvé. Toutes mes mesures sont séquentielles.

Une limite de méthode qu'il faut connaître. J'ai cherché ce qui MANQUE, et une garde absente ne laisse aucune trace : ni test rouge, ni message d'erreur, ni compteur de programme. Ce que je rapporte est donc ce que j'ai su imaginer, pas la liste complète de ce qui manque. Les dix-huit défauts ci-dessus sont réels et reproduits ; leur nombre ne dit rien de ce qui reste.

4. Ma lecture du rapport d'audit existant

Lu seulement maintenant, après que tout ce qui précède a été écrit. C'était la condition de l'exercice, et elle a servi : sur les dix-huit défauts ci-dessus, quatorze ne figurent nulle part dans ce rapport.

Une précaution d'honnêteté avant de le juger. Ce rapport n'est plus dans l'état où je l'aurais trouvé si j'avais commencé par lui. J'ai signalé mon premier défaut à la session de coordination dès que je l'ai mesuré, sans attendre ce document, et le rapport porte maintenant un bandeau de retrait qui le mentionne, ainsi qu'une correction datée du 1er septembre. Je juge donc ci-dessous deux choses distinctes : ce que le rapport affirmait avant mon signalement, et ce qu'il en dit après.

Ce qu'il a bien vu, et que je confirme

Sa méthode vaut mieux que ses trouvailles, et c'est un compliment. Exiger de tout contrôle qu'il nomme la garde attendue et vérifie que le refus vient bien d'elle, en lisant le message et le compteur de programme, est la meilleure idée de tout ce dossier. Je l'ai employée pour chacun de mes refus mesurés. Sans elle, mon défaut 11 serait passé pour une simple erreur de composition de groupe au lieu d'une garde nommée qui refuse ce que la documentation prescrit.

L'enchère perpétuelle est réellement fermée. Le rapport l'a trouvée seul, l'a mesurée, et a vu que la seule correction sur la durée ne suffisait pas. La version que je relis porte les deux gardes qu'il réclamait, la concurrence et fin_maximum. J'ai relu ce chemin en cherchant une troisième voie et je n'en ai pas trouvé.

Son raisonnement sur la pénalité impossible au prix plancher est juste, et il a produit le bon réglage. Il a établi qu'au plancher des annonces aucune part retenue sur la mise ne pouvait couvrir la perte du vendeur, et en a tiré la nécessité d'un plancher propre aux enchères. Ce plancher existe aujourd'hui.

Il a nommé le bon principe pour le maximum de pièces. Sa mesure donnait sept avec un actif répété, et il a écrit lui-même : « Le sept mesuré est un plafond optimiste, pas une garantie : la mesure est à refaire avec des actifs distincts. » Cette prudence était exacte, et elle a produit les maxima actuels.

Ce qu'il affirme et qui est faux

A « Le retrait ne se bloque jamais, et c'est maintenant prouvé sain » faux

C'est le titre d'une de ses sections « sain », et il repose sur une seule épreuve : le marché a été suspendu, un vendeur a retiré sa pièce pendant la suspension. Cette épreuve est valide, mais elle ne prouve que ce qu'elle teste, à savoir que suspendre ne ferme pas annuler.

Trois blocages que j'ai mesurés la contredisent, et aucun ne passe par la suspension : un actif dont l'émetteur a gardé la reprise fait échouer toute sortie sans qu'aucune garde ne joue (défaut 04) ; une enchère de quatre pièces en jeton n'est ni clôturable ni annulable (défaut 02) ; un exploitant qui n'a pas accepté une monnaie bloque toutes les ventes dans cette monnaie (défaut 07). Le point commun des trois : le blocage ne vient d'aucune méthode du contrat, donc aucune épreuve de garde ne pouvait le rencontrer.

B Les trois méthodes déclarées saines par erreur d'argument corrigé depuis

Le rapport avait conclu que lister, mettre_aux_encheres et offrir étaient protégées parce qu'elles reçoivent un argument transaction déclaré. Il l'a lui-même rectifié : l'argument protège le dépôt, tandis que la boucle ramasse autre chose à côté, les pièces dans deux cas et l'ALGO de la boîte dans le troisième.

Je confirme la rectification, et j'ajoute la moitié qui manque encore : la protection par argument déclaré est réelle et elle est solide. Je l'ai vérifiée dans le TEAL (txn GroupIndex ; 1 ; - avec contrôle de type) et elle interdit bien à un second appel de viser le dépôt du premier. Le diagnostic exact n'est donc pas que la protection est illusoire, c'est qu'elle ne couvre pas ce qui est ramassé par balayage à côté d'elle. Cette nuance décide du remède : il ne faut pas remplacer le mécanisme des arguments, il faut l'étendre à ce qui est aujourd'hui balayé.

C « L'exploitant ne peut pas partir avec la caisse » est vrai, mais il peut la fermer à clé incomplet

La démonstration est bonne et je la confirme : la garde par le solde minimum réel tient. Mais le rapport en déduit implicitement que l'exploitant est sans pouvoir sur les biens, ce que le contrat affirme aussi dans son constructeur. C'est inexact : en ne s'optant pas à une monnaie qu'il ouvre, ou en s'en dés-optant, il bloque toutes les ventes dans cette monnaie (défaut 07). Le rapport ne pouvait pas le voir en éprouvant les méthodes une à une, puisque ce pouvoir ne s'exerce par aucune méthode.

Symétriquement, il a constaté qu'une instance antérieure est « définitivement infermable » et l'a consigné comme un résultat, ce qui est juste. Il n'a pas vu que le même état revient sur la version corrigée : une seule unité envoyée au coffre par n'importe qui suffit à rendre une monnaie infermable, donc le marché aussi (défaut 08).

D Sa seconde « fausse alerte » n'en était pas tout à fait une à revoir

Le rapport consigne honnêtement deux fausses alertes, ce qui est à son crédit. La seconde portait sur un manque de 0,006 ALGO à la reprise d'une offre expirée, et il a conclu que « le contrat avait raison et le seuil de l'audit avait tort », au motif que « 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 ».

Le raisonnement est juste sur ce que le contrat DOCUMENTE, et faux sur ce qu'il DÉPENSE. Le contrat retient bien deux frais de sortie, mais il n'en paie qu'un : la seconde transaction porte fee=0 et retombe sur le pool du groupe, donc sur l'appelant. C'est mon défaut 14, mesuré à 1 000 µALGO près sur une transaction encadrée. L'audit avait donc vu quelque chose de réel et l'a classé en fausse alerte après avoir vérifié le commentaire du contrat plutôt que ses transactions internes.

Cela n'enlève rien à sa leçon de méthode, qui reste bonne : une mesure de solde doit encadrer la seule transaction qu'elle juge. C'est en l'appliquant que j'ai pu isoler ces mille microALGO.

Ce qui lui manque, et pourquoi il ne pouvait pas le voir

Quatorze de mes dix-huit défauts sont absents de ce rapport : les numéros 02, 04, 07, 08, 09, 10, 11, 12, 13, 14, 15, 16, 17 et 18. Ce n'est pas un reproche de négligence, et la raison est écrite dans le rapport lui-même, dans la phrase la plus juste qu'il contient : « aucune garde ne les portait, donc aucune épreuve ne pouvait les voir ».

Un audit qui éprouve les gardes mesure la qualité de ce qui a été écrit. Il ne peut rien dire de ce qui ne l'a pas été. Les cinquante-six gardes qu'il a fait refuser refusent effectivement, je n'en ai pris aucune en défaut, et pourtant dix-huit chemins font perdre un bien ou de l'argent. Les deux constats sont parfaitement compatibles, et c'est cela qu'il faut retenir de la comparaison entre nos deux lectures.

Deux manques méritent une mention particulière, parce qu'ils prolongent directement une mise en garde que le rapport avait lui-même formulée.

Sur le remède qu'il propose

Le rapport recommande de « refuser qu'un groupe contienne plus d'un appel à cette application ». C'est le bon remède pour le défaut de fond : il ferme les cinq balayages d'un coup, il est simple à écrire, et il se vérifie d'une ligne. Deux réserves, la seconde plus importante que la première.

Ce que je retiens de la comparaison

Le rapport existant et cette relecture ne se contredisent presque nulle part : ils regardent deux objets différents. Lui a mesuré que tout ce qui est écrit fonctionne, et c'est vrai. J'ai cherché ce qui n'est pas écrit, et il en manque beaucoup. Un contrat immuable a besoin des deux, et l'ordre a son importance : la lecture statique aurait dû venir en premier, parce qu'un compteur de gardes vertes donne une confiance que rien dans le dispositif ne vient contredire.

Le rapport le formule mieux que moi dans son bandeau de retrait, et cette phrase mérite de survivre à la correction du contrat : le chiffre ne disait pas ce qu'on croyait qu'il disait.