BlockLucky Documentation officielle GitHub

Référence de BlockLucky

Cette page liste chaque fonction, événement et erreur de BlockLucky. Les tableaux sont générés à la construction de la documentation à partir de l'ABI embarquée par le backend, backend/src/blockchain/abi/BlockLucky.json, et du source Solidity pour les modificateurs. Ils ne peuvent donc pas diverger du contrat compilé : la CI vérifie que l'ABI est à jour avec node scripts/export-abi.mjs --check.

Pour le fonctionnement d'ensemble, lire d'abord Contrat BlockLucky et Cycle de vie d'un tour.

Lire les tableaux

  • Fonctions du contrat : déclarées dans BlockLucky.sol, y compris les variables publiques, pour lesquelles Solidity génère un accesseur. La colonne « Modificateurs » reprend la signature Solidity (payable, whenNotPaused, nonReentrant, onlyRole(...), view).
  • Fonctions héritées : apportées par OpenZeppelin (AccessControl, Pausable) ou Chainlink (VRFConsumerBaseV2Plus, ConfirmedOwner).
  • Erreurs : le contrat n'utilise que des erreurs personnalisées, moins coûteuses en gaz qu'une chaîne de caractères et décodables avec leurs paramètres.
  • Les structures RoundConfig, VrfConfig et Round apparaissent dans l'ABI sous le type tuple. Leurs champs sont détaillés dans Contrat BlockLucky.

Appeler le contrat avec ethers

Les exemples utilisent ethers v6, la version du backend et des scripts Hardhat. Ils s'exécutent par exemple dans un script Node en module ES, à la racine du dépôt.

Connexion

En local, les adresses sont déterministes (voir Blockchain locale). Sur Sepolia, elles se lisent dans smart-contract/deployments/sepolia.json après le déploiement.

TypeScript
import { readFileSync } from "node:fs";
import { Contract, JsonRpcProvider, Wallet, formatEther, parseEther } from "ethers";

const abi = JSON.parse(readFileSync("backend/src/blockchain/abi/BlockLucky.json", "utf8"));
const provider = new JsonRpcProvider("http://127.0.0.1:8545");
const lottery = new Contract("0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", abi, provider);

Pour envoyer des transactions, il faut un signataire. Sur le nœud local, les comptes Hardhat de développement sont approvisionnés ; la clé se lit dans une variable d'environnement, jamais dans le code.

TypeScript
const signer = new Wallet(process.env.PLAYER_PRIVATE_KEY!, provider);
const asPlayer = lottery.connect(signer) as Contract;

Lire l'état d'un tour

TypeScript
const roundId: bigint = await lottery.currentRoundId();
const round = await lottery.getRound(roundId);

console.log({
  status: Number(round.status),
  ticketPrice: formatEther(round.ticketPrice),
  sold: Number(round.ticketCount),
  max: Number(round.maxTickets),
  endsAt: new Date(Number(round.endTime) * 1000),
  drawReady: await lottery.isDrawReady(),
});

round.status vaut 0 (Open), 1 (Drawing), 2 (Completed) ou 3 (Cancelled). Les entiers sont renvoyés en bigint.

Acheter et offrir des tickets

Le montant doit être exactement ticketPrice * quantity. Le plus sûr est de le calculer à partir du prix lu sur la chaîne, juste avant l'envoi.

TypeScript
const { ticketPrice } = await lottery.getRound(await lottery.currentRoundId());
const quantity = 3n;

const tx = await asPlayer.buyTickets(quantity, { value: ticketPrice * quantity });
const receipt = await tx.wait();

const gift = await asPlayer.buyTicketsFor("0x70997970C51812dc3A010C7d01b50e0d17dc79C8", 2n, {
  value: ticketPrice * 2n,
});
await gift.wait();

C'est ce que fait la passerelle du contrat du backend, avec le portefeuille intégré du citoyen comme signataire.

Lire les événements d'une transaction

TypeScript
for (const log of receipt!.logs) {
  const parsed = lottery.interface.parseLog(log);
  if (parsed?.name === "TicketsPurchased") {
    const { roundId, player, payer, quantity, firstTicketIndex, totalTickets } = parsed.args;
    console.log(roundId, player, payer, quantity, firstTicketIndex, totalTickets);
  }
}

Décoder une erreur

Un revert renvoie le sélecteur de l'erreur suivi de ses paramètres. interface.parseError le décode.

TypeScript
try {
  await asPlayer.buyTickets(1n, { value: parseEther("0.5") });
} catch (error) {
  const data = (error as { data?: string }).data;
  const decoded = data ? lottery.interface.parseError(data) : null;
  console.log(decoded?.name, decoded?.args);
}

Ici, decoded.name vaut IncorrectPayment et decoded.args contient le montant attendu puis le montant reçu. Le backend applique la même logique dans decodeContractError de backend/src/blockchain/chain-actions.ts, puis traduit chaque erreur en message HTTP dans backend/src/blockchain/lottery.gateway.ts.

Suivre les événements en continu

TypeScript
await lottery.on("WinnerPicked", (roundId, winner, winningTicket, prize, fee) => {
  console.log(`Tour ${roundId} : ticket ${winningTicket} gagné par ${winner}, ${formatEther(prize)} ETH`);
});

Pour une projection fiable en base, préférer une lecture par blocs avec curseur persistant, comme l'indexeur : un abonnement on ne rattrape pas les événements manqués pendant une coupure.

Retirer une cagnotte ou un remboursement

TypeScript
const winnerTx = await asPlayer.claimPrize(1n);
await winnerTx.wait();

const refundTx = await asPlayer.claimRefund(2n);
await refundTx.wait();

Simuler avant d'envoyer

staticCall exécute la fonction sans transaction et renvoie l'erreur éventuelle, sans frais. Il est utile avant un requestDraw ou un retryDraw pour savoir si la condition est remplie.

TypeScript
const requestId: bigint = await lottery.requestDraw.staticCall();

Depuis la console Hardhat

Les opérations ponctuelles d'exploitation se font aussi dans la console Hardhat, qui fournit ethers et les comptes configurés du réseau :

Terminal
npx hardhat console --network localhost
JavaScript
const { ethers } = await network.create();
const lottery = await ethers.getContractAt("BlockLucky", "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9");
await lottery.currentRoundId();

Voir Exploitation du contrat pour les opérations réservées aux rôles.

Table générée

Source : smart-contract/contracts/BlockLucky.sol. ABI : backend/src/blockchain/abi/BlockLucky.json, vérifiée en CI par node scripts/export-abi.mjs --check.

Fonctions du contrat

Fonction Retour Mutabilité Modificateurs Rôle
accruedFees() uint256 variable publique - Commissions cumulées des tours tirés et non encore versées à la trésorerie, en wei. Remis à zéro par withdrawFees.
BPS_DENOMINATOR() uint16 variable publique - Constante 10 000 : base des points de base, 10 000 valent 100 %. Sert au calcul de la commission.
buyTickets(uint32 quantity) - payable payable whenNotPaused nonReentrant Achète quantity tickets du tour en cours pour l'appelant, qui en est à la fois bénéficiaire et payeur. msg.value doit valoir exactement ticketPrice * quantity. Émet TicketsPurchased.
buyTicketsFor(address player, uint32 quantity) - payable payable whenNotPaused nonReentrant Offre quantity tickets à player. Le bénéficiaire détient les tickets et peut gagner ; l'appelant paie et sera remboursé en cas d'annulation. Mêmes contrôles que buyTickets, plus le refus de l'adresse nulle.
cancelRound() - nonpayable onlyRole(OPERATOR_ROLE) nonReentrant Annule le tour en cours, qu'il soit Open ou Drawing, efface la requête VRF éventuelle et ouvre le tour suivant. Les mises deviennent remboursables par claimRefund. Réservé à OPERATOR_ROLE.
checkUpkeep(bytes) bool upkeepNeeded, bytes performData view view Interface Chainlink Automation. Renvoie true et abi.encode(uint8(1)) si un tirage est à lancer, true et abi.encode(uint8(2)) si un tour expiré est à annuler, false sinon ou en pause.
claimPrize(uint256 roundId) - nonpayable nonReentrant Verse au gagnant la cagnotte nette du tour roundId. Exige le statut Completed, l'appel par le gagnant et une cagnotte non encore retirée. Émet PrizeClaimed. Utilisable en pause.
claimRefund(uint256 roundId) - nonpayable nonReentrant Rembourse à l'appelant l'intégralité des ethers qu'il a versés pour le tour annulé roundId, puis remet sa contribution à zéro. Émet RefundClaimed. Utilisable en pause.
closeExpiredRound() - nonpayable nonReentrant Annule le tour en cours quand sa durée est écoulée sans atteindre minTicketsToDraw, puis ouvre le suivant. Appelable par tous, y compris en pause.
contributionOf(uint256 roundId, address player) uint256 view view Ethers versés par le payeur player pour le tour roundId et non encore remboursés, en wei.
currentRoundId() uint256 variable publique - Numéro du tour en cours, qui vaut 1 au déploiement. Le tour qu'il désigne est toujours Open ou Drawing.
getRound(uint256 roundId) tuple view view Renvoie la structure Round complète du tour roundId. Un tour inexistant renvoie une structure vide, sans revert.
isDrawReady() bool view view Vrai si le tour en cours est Open, a au moins minTicketsToDraw tickets, et est complet ou a dépassé endTime.
isExpiredWithoutDraw() bool view view Vrai si le tour en cours est Open, a dépassé endTime et compte moins de minTicketsToDraw tickets.
MAX_FEE_BPS() uint16 variable publique - Constante 2 000 : plafond de la commission de la Mairie, soit 20 %. Toute configuration au-delà est refusée par InvalidConfiguration.
OPERATOR_ROLE() bytes32 variable publique - Identifiant du rôle opérateur, keccak256("OPERATOR_ROLE"). Autorise retryDraw, cancelRound et withdrawFees.
pause() - nonpayable onlyRole(PAUSER_ROLE) Active le disjoncteur : bloque buyTickets, buyTicketsFor et requestDraw. Les retraits, l'annulation et le callback VRF restent possibles. Réservé à PAUSER_ROLE.
PAUSER_ROLE() bytes32 variable publique - Identifiant du rôle disjoncteur, keccak256("PAUSER_ROLE"). Autorise pause et unpause.
performUpkeep(bytes) - nonpayable - Interface Chainlink Automation, appelable par tous. Lance requestDraw si le contrat n'est pas en pause et que le tirage est prêt, sinon closeExpiredRound si le tour est expiré, sinon revert DrawNotReady. Le paramètre est ignoré.
requestDraw() uint256 requestId nonpayable whenNotPaused nonReentrant Demande un mot aléatoire au coordinateur VRF pour le tour en cours et le passe à Drawing. Appelable par tous dès que isDrawReady() est vrai. Renvoie l'identifiant de la requête et émet DrawRequested.
retryDraw() uint256 requestId nonpayable onlyRole(OPERATOR_ROLE) nonReentrant Relance une requête VRF restée sans réponse : exige le statut Drawing et drawRequestedAt + drawTimeout dépassé. L'ancienne réponse sera ignorée. Réservé à OPERATOR_ROLE.
roundByRequestId(uint256) uint256 variable publique - Tour associé à une requête VRF en attente. Vaut 0 pour une requête inconnue, déjà servie, relancée ou annulée.
roundConfig() uint256 ticketPrice, uint32 maxTickets, uint32 minTicketsToDraw, uint32 maxTicketsPerPlayer, uint64 duration, uint16 feeBps variable publique - Paramètres appliqués au prochain tour ouvert : prix, plafonds, minimum, durée et commission.
setRoundConfig(tuple newConfig) - nonpayable onlyRole(DEFAULT_ADMIN_ROLE) Remplace roundConfig après validation. La nouvelle configuration s'applique au tour suivant, jamais au tour en cours. Émet RoundConfigUpdated. Réservé à DEFAULT_ADMIN_ROLE.
setTreasury(address newTreasury) - nonpayable onlyRole(DEFAULT_ADMIN_ROLE) Change l'adresse qui reçoit les commissions. Refuse l'adresse nulle. Émet TreasuryUpdated. Réservé à DEFAULT_ADMIN_ROLE.
setVrfConfig(tuple newConfig) - nonpayable onlyRole(DEFAULT_ADMIN_ROLE) Remplace vrfConfig après validation. S'applique dès la prochaine requête, et drawTimeout s'applique aussi au tirage en attente. Émet VrfConfigUpdated. Réservé à DEFAULT_ADMIN_ROLE.
supportsInterface(bytes4 interfaceId) bool view view override(AccessControl) ERC-165 : indique les interfaces prises en charge, dont IAccessControl (0x7965db0b) et ERC-165 lui-même.
ticketOwner(uint256 roundId, uint256 ticketIndex) address view view Bénéficiaire du ticket d'index ticketIndex, compté à partir de 0, dans le tour roundId. Revert si l'index dépasse le nombre de tickets vendus.
ticketsOf(uint256 roundId, address player) uint32 view view Nombre de tickets détenus par le bénéficiaire player dans le tour roundId.
treasury() address variable publique - Adresse de trésorerie de la Mairie, destinataire de withdrawFees.
unpause() - nonpayable onlyRole(PAUSER_ROLE) Désactive le disjoncteur. Réservé à PAUSER_ROLE.
vrfConfig() bytes32 keyHash, uint256 subscriptionId, uint32 callbackGasLimit, uint16 requestConfirmations, bool nativePayment, uint64 drawTimeout variable publique - Paramètres des requêtes Chainlink VRF : key hash, abonnement, gaz du callback, confirmations, mode de paiement et délai de relance.
withdrawFees() - nonpayable onlyRole(OPERATOR_ROLE) nonReentrant Verse la totalité de accruedFees à l'adresse treasury et remet le compteur à zéro. Émet FeesWithdrawn. Réservé à OPERATOR_ROLE, utilisable en pause.

Fonctions héritées

Fonction Retour Mutabilité Origine Rôle
acceptOwnership() - nonpayable ConfirmedOwner (Chainlink) Finalise un transfert de propriété ConfirmedOwner demandé par transferOwnership.
DEFAULT_ADMIN_ROLE() bytes32 view AccessControl (OpenZeppelin) Rôle administrateur (valeur nulle), administrateur de tous les autres rôles.
getRoleAdmin(bytes32 role) bytes32 view AccessControl (OpenZeppelin) Rôle qui administre le rôle donné ; DEFAULT_ADMIN_ROLE pour les trois rôles du contrat.
grantRole(bytes32 role, address account) - nonpayable AccessControl (OpenZeppelin) Accorde un rôle à un compte. Réservé à l'administrateur du rôle.
hasRole(bytes32 role, address account) bool view AccessControl (OpenZeppelin) Indique si un compte détient un rôle.
owner() address view ConfirmedOwner (Chainlink) Propriétaire ConfirmedOwner : le compte qui a déployé le contrat. Il peut changer le coordinateur VRF.
paused() bool view Pausable (OpenZeppelin) Indique si le disjoncteur est actif.
rawFulfillRandomWords(uint256 requestId, uint256[] randomWords) - nonpayable VRFConsumerBaseV2Plus (Chainlink) Point d'entrée du coordinateur VRF, qui livre l'aléa. Refuse tout autre appelant, puis appelle fulfillRandomWords.
renounceRole(bytes32 role, address callerConfirmation) - nonpayable AccessControl (OpenZeppelin) Permet à un compte de renoncer à son propre rôle.
revokeRole(bytes32 role, address account) - nonpayable AccessControl (OpenZeppelin) Retire un rôle à un compte. Réservé à l'administrateur du rôle.
s_vrfCoordinator() address view VRFConsumerBaseV2Plus (Chainlink) Adresse du coordinateur Chainlink VRF utilisé pour les requêtes et seul autorisé à livrer l'aléa.
setCoordinator(address _vrfCoordinator) - nonpayable VRFConsumerBaseV2Plus (Chainlink) Remplace le coordinateur VRF. Réservé au propriétaire ou au coordinateur actuel.
transferOwnership(address to) - nonpayable ConfirmedOwner (Chainlink) Propose un nouveau propriétaire ConfirmedOwner, qui doit accepter par acceptOwnership.

Événements

Événement Paramètres Rôle
CoordinatorSet address vrfCoordinator hérité (VRFConsumerBaseV2Plus, changement de coordinateur)
DrawRequested uint256 indexed roundId, uint256 indexed requestId, uint32 ticketCount Émis par requestDraw et retryDraw quand une requête VRF est envoyée, avec son identifiant et le nombre de tickets du tour.
FeesWithdrawn address indexed treasury, uint256 amount Émis par withdrawFees avec l'adresse de trésorerie et le montant versé.
OwnershipTransferred address indexed from, address indexed to hérité (ConfirmedOwner, transfert de propriété effectif)
OwnershipTransferRequested address indexed from, address indexed to hérité (ConfirmedOwner, transfert de propriété proposé)
Paused address account hérité (Pausable, disjoncteur activé)
PrizeClaimed uint256 indexed roundId, address indexed winner, uint256 amount Émis par claimPrize quand le gagnant retire sa cagnotte.
RefundClaimed uint256 indexed roundId, address indexed player, uint256 amount Émis par claimRefund quand un payeur récupère sa mise d'un tour annulé.
RoleAdminChanged bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole hérité (AccessControl, administrateur d'un rôle modifié)
RoleGranted bytes32 indexed role, address indexed account, address indexed sender hérité (AccessControl, rôle accordé)
RoleRevoked bytes32 indexed role, address indexed account, address indexed sender hérité (AccessControl, rôle retiré)
RoundCancelled uint256 indexed roundId, uint32 ticketCount, uint256 totalCollected Émis par closeExpiredRound ou cancelRound, avec le nombre de tickets et le total à rembourser.
RoundConfigUpdated uint256 ticketPrice, uint32 maxTickets, uint64 duration, uint16 feeBps Émis par setRoundConfig avec le prix, le plafond de tickets, la durée et la commission du prochain tour.
RoundOpened uint256 indexed roundId, uint256 ticketPrice, uint32 maxTickets, uint64 endTime Émis à chaque ouverture de tour : au déploiement, après un tirage et après une annulation. Porte le prix, le plafond et l'heure de fin.
TicketsPurchased uint256 indexed roundId, address indexed player, address indexed payer, uint32 quantity, uint32 firstTicketIndex, uint32 totalTickets, uint256 amount Émis à chaque achat ou cadeau. Distingue le bénéficiaire (player) du payeur (payer) et donne l'index du premier ticket acheté, le total vendu et le montant.
TreasuryUpdated address indexed previousTreasury, address indexed newTreasury Émis par setTreasury avec l'ancienne et la nouvelle adresse.
Unpaused address account hérité (Pausable, disjoncteur désactivé)
VrfConfigUpdated bytes32 keyHash, uint256 subscriptionId, uint32 callbackGasLimit Émis par setVrfConfig avec la key hash, l'abonnement et le gaz du callback.
WinnerPicked uint256 indexed roundId, address indexed winner, uint32 winningTicket, uint256 prize, uint256 fee, uint256 indexed requestId Émis par le callback VRF : gagnant, index du ticket gagnant, cagnotte nette, commission et identifiant de la requête.

Erreurs

Erreur Paramètres Déclenchée quand
AccessControlBadConfirmation - héritée (AccessControl, renounceRole appelé pour un autre compte)
AccessControlUnauthorizedAccount address account, bytes32 neededRole héritée (AccessControl, l'appelant n'a pas le rôle exigé)
DrawNotReady uint256 roundId requestDraw est appelé alors que isDrawReady() est faux ; retryDraw est appelé alors qu'aucun tirage n'est en cours ; performUpkeep n'a rien à faire.
DrawNotStale uint256 roundId retryDraw est appelé avant que drawRequestedAt + drawTimeout soit atteint.
EnforcedPause - héritée (Pausable, appel bloqué par le disjoncteur ou pause déjà active)
ExpectedPause - héritée (Pausable, unpause alors que le contrat n'est pas en pause)
IncorrectPayment uint256 expected, uint256 received Le montant envoyé à buyTickets ou buyTicketsFor diffère de ticketPrice * quantity, en plus ou en moins. Porte le montant attendu et le montant reçu.
InvalidConfiguration - Une configuration est incohérente au constructeur, dans setRoundConfig ou setVrfConfig : prix, plafond, minimum, plafond par participant ou durée nuls, minimum ou plafond par participant supérieurs à maxTickets, commission au-delà de 20 %, key hash nulle, gaz du callback sous 100 000, confirmations ou délai de relance nuls.
InvalidQuantity - Un achat porte sur zéro ticket.
NothingToRefund uint256 roundId, address player claimRefund est appelé par un compte sans contribution sur ce tour, ou déjà remboursé.
NothingToWithdraw - withdrawFees est appelé alors que accruedFees vaut zéro.
NotWinner address caller claimPrize est appelé par un autre compte que le gagnant du tour.
OnlyCoordinatorCanFulfill address have, address want héritée (VRFConsumerBaseV2Plus, rawFulfillRandomWords appelé par un autre compte que le coordinateur)
OnlyOwnerOrCoordinator address have, address owner, address coordinator héritée (VRFConsumerBaseV2Plus, setCoordinator appelé par un autre compte que le propriétaire ou le coordinateur)
PlayerLimitReached uint32 owned, uint32 limit L'achat ferait dépasser au bénéficiaire maxTicketsPerPlayer. Porte le nombre de tickets déjà détenus et le plafond.
PrizeAlreadyClaimed uint256 roundId claimPrize est appelé une seconde fois pour le même tour.
ReentrancyGuardReentrantCall - héritée (ReentrancyGuard, tentative de réentrance dans une fonction nonReentrant)
RoundExpired uint256 roundId Un achat arrive alors que endTime est atteint.
RoundNotCancellable uint256 roundId closeExpiredRound est appelé alors que isExpiredWithoutDraw() est faux.
RoundNotCancelled uint256 roundId claimRefund est appelé sur un tour qui n'est pas Cancelled.
RoundNotCompleted uint256 roundId claimPrize est appelé sur un tour qui n'est pas Completed.
RoundNotOpen uint256 roundId Un achat arrive alors que le tour en cours est en tirage (Drawing).
RoundSoldOut uint256 roundId, uint32 remaining L'achat dépasse le nombre de tickets restants du tour. Porte le nombre restant.
TransferFailed address recipient, uint256 amount Le destinataire d'une cagnotte, d'un remboursement ou des commissions refuse les ethers. L'état est restauré et le retrait peut être retenté.
ZeroAddress - héritée (VRFConsumerBaseV2Plus, coordinateur nul au constructeur ou dans setCoordinator)
ZeroAddressNotAllowed - L'adresse nulle est passée comme administrateur ou trésorerie au constructeur, comme bénéficiaire à buyTicketsFor, ou comme trésorerie à setTreasury.