Capitolo 4 di 9
Il contratto del prezzo
Il nostro oracolo è un contratto che conserva tre cose: il prezzo della benzina, il prezzo del POL e il momento dell'ultimo aggiornamento. Chiunque può leggere; può scrivere solo chi ha un ruolo preciso.
Due regole sui numeri. Primo: la blockchain non conosce i decimali, quindi i prezzi sono interi moltiplicati per un milione: 1,989 € diventa 1989000. Secondo: il momento viene dalla blockchain stessa (block.timestamp), non lo sceglie chi scrive.
contract FuelPriceOracle is AccessControl {
bytes32 public constant UPDATER_ROLE = keccak256("UPDATER_ROLE");
uint256 private _fuelEurPerLitre;
uint256 private _polEur;
uint64 private _updatedAt;
uint64 private _sourceDate;
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
}
function setPrices(uint256 fuelEurPerLitre, uint256 polEur, uint64 sourceDate)
external onlyRole(UPDATER_ROLE)
{
require(fuelEurPerLitre > 0 && polEur > 0, "zero price");
_fuelEurPerLitre = fuelEurPerLitre;
_polEur = polEur;
_sourceDate = sourceDate;
_updatedAt = uint64(block.timestamp);
}
function fuelPrice() external view returns (uint256, uint64) {
return (_fuelEurPerLitre, _updatedAt);
}
}
AccessControlè un modulo di OpenZeppelin: gestisce i ruoli.UPDATER_ROLEè il nome del ruolo «chi può aggiornare» (un'etichetta ottenuta da un testo).- Le quattro variabili
privatesono la lavagna: prezzo benzina, prezzo POL, istante dell'ultimo aggiornamento e data del dato (sourceDate, il giorno a cui si riferiscono i prezzi). - Il
constructorparte una volta sola, al momento della pubblicazione, e dà il ruolo di amministratore a chi pubblica. setPricesscrive i due prezzi e la data del dato;onlyRole(UPDATER_ROLE)respinge chiunque non abbia il ruolo.requirerifiuta un prezzo uguale a zero, che romperebbe le divisioni del capitolo 6.block.timestampè l'ora del blocco in cui la scrittura viene registrata.fuelPriceèview: si legge gratis, senza firmare nulla.
Il progetto completo aggiunge la funzione per il prezzo del POL e il controllo di freschezza, che il token userà nei prossimi capitoli, più un evento (PricesUpdated) che registra ogni aggiornamento:
function polPrice() external view returns (uint256, uint64) {
return (_polEur, _updatedAt);
}
function isFresh(uint64 maxAgeSeconds) external view returns (bool) {
return _updatedAt != 0
&& block.timestamp <= uint256(_updatedAt) + maxAgeSeconds;
}
polPricerestituisce il prezzo del POL e l'istante, comefuelPrice.isFreshrisponde «sì» solo se i prezzi esistono (_updatedAt != 0) e non sono più vecchi dimaxAgeSecondssecondi.
Prova tu
Apri il sito Remix (remix.ethereum.org), incolla il contratto con le righe import di OpenZeppelin e la riga pragma solidity ^0.8.24; in testa, compilalo e pubblicalo nell'ambiente di prova di Remix. Poi prova a chiamare setPrices senza avere il ruolo e osserva il rifiuto.
Cosa hai imparato
- Un oracolo minimo è una lavagna: valori, data e un solo scrittore autorizzato.
- I prezzi sono interi con sei decimali: 1,989 € è 1989000.
isFreshpermette ad altri contratti di rifiutare dati troppo vecchi.