N° 11Solucky
Qui peut toucher à la cagnotte
Alex a raconté comment Solucky tire un gagnant. Voici l’autre moitié : où dort l’argent des joueurs d’ici là, qui peut le déplacer, et qui ne le peut pas.
Vincent Porret
Cofondateur et CEO · · 4 min de lecture
Alex a raconté comment Solucky tire son ticket gagnant. En gestion des risques, le tirage n’est que la moitié de la question. L’autre moitié, c’est la garde des fonds : où se trouve l’argent entre le premier ticket et le paiement, qui peut le déplacer, et ce qui l’en empêche. Dans une banque, la réponse est un ensemble de contrôles et les personnes qui les valident. Dans Solucky 1.0, c’est un programme de huit instructions, et chacun peut toutes les lire.
La cagnotte, c’est la manche
Il n’y a ni coffre ni wallet de la maison au milieu. Chaque jour UTC est une manche, et le compte de la manche détient lui-même sa cagnotte, en plus du petit solde que Solana exige pour qu’un compte existe. Un ticket, c’est un simple virement de l’acheteur directement vers ce compte. Le compte appartient au programme, et personne ne détient de clé capable de signer pour lui : les lamports n’en sortent que lorsqu’une des instructions du programme le décide.
Sur les huit, deux sortent de l’argent d’une manche, et le code est assez court pour être cité. claim_prize paie le gagnant :
let fee = fee_for(round.pot, round.fee_bps);
let payout = round.pot - fee;
ctx.accounts.round.sub_lamports(payout)?;
ctx.accounts.owner.add_lamports(payout)?;collect_fee paie la maison, et rien d’autre ne sort de lamports d’une manche. Une troisième instruction, close_ticket, ne fait que rendre le loyer du compte d’un ticket au joueur qui l’a payé, une fois la manche réglée.
Tout le monde peut l’envoyer, une seule personne reçoit
claim_prize ne demande aucune signature. Le gagnant, un ami ou un script peut l’envoyer, mais le programme vérifie que le compte qu’il paie est bien le propriétaire inscrit sur le ticket gagnant, et que ce ticket appartient à cette manche. Envoyée avec une autre adresse, elle échoue. La commission fonctionne de la même façon : n’importe qui peut envoyer collect_fee, et elle ne paie que l’adresse de trésorerie inscrite dans la configuration du programme. Il n’y a pas non plus de délai pour réclamer. Un gain non réclamé attend dans sa manche, et personne d’autre ne peut le prendre.
L’ordre compte tout autant. collect_fee ne fonctionne que sur une manche réglée : la commission reste donc dans la manche jusqu’à ce que le gagnant ait été payé. Une trésorerie mal configurée, ou simplement lente, ne peut pas retenir un gain.
La maison est payée après le gagnant, jamais avant.
Ce que l’opérateur peut changer
Le programme a une autorité, et aujourd’hui, c’est nous. Elle peut changer le prix du ticket, la commission de la maison, le plus grand numéro de ticket et l’adresse de trésorerie, et elle peut suspendre les ventes. Chacun de ces pouvoirs s’arrête quelque part. La commission est plafonnée dans le code à 10 %, MAX_FEE_BPS: u16 = 1_000. Une manche copie le prix et la commission de la configuration au moment où son premier ticket est vendu, et les garde : un changement ne touche que les manches pas encore ouvertes, jamais une cagnotte qui se remplit déjà. Les acheteurs envoient aussi le maximum qu’ils acceptent de payer : un prix relevé entre l’écran et la signature fait échouer l’achat au lieu de facturer plus.
Une pause arrête la vente des tickets, et rien d’autre. La clôture, le tirage et la réclamation restent ouverts, et un commentaire du code donne la raison : qu’une pause ne puisse jamais bloquer une cagnotte. Le seul levier que l’autorité garde sur l’argent, c’est l’adresse de trésorerie : une commission part vers la trésorerie configurée au moment où elle est collectée. Ce qu’elle n’a pas est tout aussi clair. Aucune instruction n’envoie une cagnotte ailleurs qu’à son gagnant, ni une commission ailleurs qu’à la trésorerie.
Arrondis et dépassements
La commission est arrondie à l’inférieur : une fraction de lamport va au gagnant, pas à la maison. Le nombre de tickets et la cagnotte sont additionnés avec une arithmétique vérifiée, et le programme est compilé avec les contrôles de dépassement activés : un dépassement annule la transaction au lieu de repartir de zéro. Et un ticket coûte au moins 0,001 SOL : avec une commission plafonnée à 10 %, un gain reste toujours au-dessus du solde minimal dont un wallet vide a besoin sur Solana, donc il peut toujours être payé.
Ce que le code ne couvre pas
Deux limites se trouvent hors des instructions. Le programme peut être mis à jour : quiconque détient son autorité de mise à jour pourrait remplacer le code, et chaque garantie ci-dessus vaut pour le code déployé aujourd’hui. Notre README le dit : un programme modifiable ne mérite pas plus de confiance que son autorité de mise à jour, et l’issue est un multisig, ou rendre le programme immuable une fois stable. Et comme l’a écrit Alex, le tirage est équitable face aux joueurs, pas face aux validateurs. C’est pourquoi l’app publique tourne sur devnet, avec des SOL de test, et pourquoi notre politique de sécurité dit de ne pas faire tourner de cagnottes en argent réel sur ce tirage.
Dans une banque, des contrôles comme ceux-là vivent dans un document de politique interne. Ici, ils vivent dans le code, et ils sont vérifiés à chaque transaction.
Vincent

