Capitolo 6 di 9

Comprare e vendere

Obiettivo. Capire il conto che trasforma POL in litri e viceversa, e perché serve una riserva di POL.

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:

OperazioneFormulaEsempio
Compro con POLtoken = POL × prezzoPOL ÷ prezzoBenzina1 POL × 0,10 ÷ 2,00 = 0,05 token
Vendo tokenPOL = token × prezzoBenzina ÷ prezzoPOL0,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.

Analogia. Il contratto è un cambiavalute con un cassetto. Quando compri, mette i tuoi POL nel cassetto e ti stampa una ricevuta (i token). Quando rivendi, ritira la ricevuta e ti ridà i POL dal cassetto.
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();
    }
}

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