Chapter 4 of 9

The price contract

Goal. Write a minimal oracle: one number, one date and a single person allowed to update it.

Our oracle is a contract that stores three things: the petrol price, the POL price and the time of the last update. Anyone can read; only someone with a specific role can write.

Analogy. It is a blackboard in the square: everyone can see it, but only one person holds the chalk. Next to the price the date is always written.

Two rules about numbers. First: the blockchain has no decimals, so prices are integers multiplied by one million: 1.989 € becomes 1989000. Second: the time comes from the blockchain itself (block.timestamp); the writer does not choose it.

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);
    }
}

The full project adds the function for the POL price and the freshness check, which the token will use in the next chapters, plus an event (PricesUpdated) that records every update:

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;
}

Try it

Open the Remix site (remix.ethereum.org), paste the contract with the OpenZeppelin import lines and pragma solidity ^0.8.24; at the top, compile it and deploy it to Remix's test environment. Then try calling setPrices without the role and watch it get rejected.

What you learned

Going deeper