Capitolo 6 di 9
Comprare e vendere
Il token si compra mandando POL al contratto con buy() e si rivende con sell(). Il contratto legge i due prezzi dall'oracolo e fa una proporzione.
Il conto, con numeri tondi
Supponiamo che la benzina costi 2,00 € al litro e che 1 POL valga 0,10 €. Un litro costa quindi 2,00 ÷ 0,10 = 20 POL, e 1 POL compra un ventesimo di litro:
| Operazione | Formula | Esempio |
|---|---|---|
| Compro con POL | token = POL × prezzoPOL ÷ prezzoBenzina | 1 POL × 0,10 ÷ 2,00 = 0,05 token |
| Vendo token | POL = token × prezzoBenzina ÷ prezzoPOL | 0,05 token × 2,00 ÷ 0,10 = 1 POL |
Nel contratto i prezzi sono interi con sei decimali (2000000 e 100000) e le quantità sono in unità da 10-18. Il conto è identico: 10**18 * 100000 / 2000000 = 5 * 10**16, cioè 0,05 token. La divisione tra interi arrotonda per difetto, quindi andata e ritorno non fanno mai guadagnare qualcosa dal nulla.
contract FuelToken is ERC20 {
FuelPriceOracle public immutable oracle;
uint64 public maxAge = 3 days;
constructor(address oracle_) ERC20("FuelToken", "FUEL") {
oracle = FuelPriceOracle(oracle_);
}
function buy() external payable {
(uint256 f, uint256 p) = _prices();
_mint(msg.sender, msg.value * p / f);
}
function sell(uint256 amount) external {
(uint256 f, uint256 p) = _prices();
uint256 polOut = amount * f / p;
require(address(this).balance >= polOut, "reserve too low");
_burn(msg.sender, amount);
payable(msg.sender).transfer(polOut);
}
function reserve() external view returns (uint256) {
return address(this).balance;
}
function _prices() internal view returns (uint256 f, uint256 p) {
require(oracle.isFresh(maxAge), "price is old");
(f,) = oracle.fuelPrice();
(p,) = oracle.polPrice();
}
}
oracleè l'indirizzo dell'oracolo del capitolo 4, fissato alla pubblicazione (immutable);maxAgeè l'età massima del dato, 3 giorni.buy()èpayable: accetta POL.msg.valueè quanti POL sono arrivati con la chiamata._mintcrea nuovi token a favore di chi chiama (msg.sender): è la formula della tabella.sellcalcola i POL dovuti e controlla che nel cassetto ce ne siano abbastanza._burndistrugge i token restituiti etransferinvia i POL a chi vende.reserve()mostra il saldo in POL del contratto, cioè il cassetto._prices()rifiuta tutto se l'oracolo non è fresco, poi legge i due prezzi.
Il progetto completo aggiunge funzioni di anteprima (quoteBuy, quoteSell) per mostrare il risultato prima di firmare, una pausa di emergenza, errori personalizzati più chiari e il controllo che il pagamento in POL vada a buon fine.
Perché una riserva
I token in circolazione sono promesse di restituire POL. Se il cassetto è vuoto, sell deve fallire invece di inventare moneta: per questo il controllo reserve too low. In questa demo la riserva è fatta dai POL di chi compra; un prezzo che sale può rendere il cassetto insufficiente. È una proprietà da conoscere, ed è il motivo per cui un prodotto reale ha regole molto più severe (vedi le domande frequenti).
Prova tu
Rifai il conto con altri numeri: benzina a 1,80 € e POL a 0,09 €. Quanti token ottieni con 2 POL? (2 × 0,09 ÷ 1,80 = 0,10 token.) Poi calcola quanti POL rivedi vendendoli.
Cosa hai imparato
- Comprare è POL × prezzoPOL ÷ prezzoBenzina; vendere è la proporzione inversa.
- Con benzina 2,00 € e POL 0,10 €, 1 POL vale 0,05 token.
- La riserva di POL è il cassetto che permette di rivendere: se è vuoto,
sellsi ferma.