Time travel

Actively in Development

Currently, it’s possible to time travel to any past block. However, this will change once the proving code is fully developed; limits may be introduced on how far back you can travel. Until this update is complete, a malicious prover could potentially create fake time travel proofs.

Access to historical data

Unfortunately, direct access to the historical state from within smart contracts is not possible. Smart contracts only have access to the current state of the current block.

To overcome this limitation, vlayer introduced the setBlock(uint blockNo) function, available in our Prover contracts. This function allows switching context of subsequent call to the desired block number.

This allows aggregating data from multiple blocks in a single call to a function.

Example

Prover

The following is an example of Prover code that calculates the average USDC balance at specific block numbers.

contract AverageBalance is Prover {
    IERC20 immutable token;
    uint256 immutable startingBlock;
    uint256 immutable endingBlock;
    uint256 immutable step;

    constructor() {
        token = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); // USDC 
        startingBlock = 6600000;
        endingBlock = 6700000;
        step = 10000;
    }

    function averageBalanceOf(address _owner) public returns (Proof, address, uint256) {
        uint256 totalBalance = 0;
        uint256 iterations = 0;

        for (uint256 blockNo = startingBlock; blockNo <= endingBlock; blockNo += step) {
            setBlock(blockNo);
            totalBalance += token.balanceOf(_owner); // USDC balance
            iterations += 1;
        }
        return (proof(), _owner, totalBalance / iterations);
    }
}

First call to the setBlock(blockNo) function sets the Prover context for the startingBlock (6600000 configured in the constructor). This means that the next call to the token.balanceOf function will read data in the context of the 6600000 block.

Next call to setBlock() sets the Prover context to block numbered 6610000 when step is configured to 10000. The subsequent call to token.balanceOf checks again total balance, but this time in block 6610000.

Each call to token.balanceOf can return different results if the account balance changes between blocks due to token transfers.

The for loop manages the balance checks, and the function’s final output is the average balance across multiple blocks.

Verifier

After proving is complete, the generated proof and public inputs can be used for on-chain verification.

contract AverageBalanceVerifier is Verifier {
    address public prover;
    mapping(address => bool) public claimed;
    HodlerBadgeNFT public reward;

    constructor(address _prover, HodlerBadgeNFT _nft) {
        prover = _prover;
        reward = _nft;
    }

    function claim(Proof calldata, address claimer, uint256 average)
        public
        onlyVerified(prover, AverageBalance.averageBalanceOf.selector)
    {
        require(!claimed[claimer], "Already claimed");

        if (average >= 10_000_000) {
            claimed[claimer] = true;
            reward.mint(claimer);
        }
    }
}

In this Verifier contract, the claim function allows users to mint an NFT if their average balance is at least 10,000,000. The onlyVerified modifier ensures the correctness of the proof and the provided public inputs (claimer and average).

If the proof is invalid or the public inputs are incorrect, the transaction will revert.

💡 Try it Now

To run the above example on your computer, type the following command in your terminal:

vlayer init --template simple-time-travel

This command will download all the necessary artefacts into your current directory (which must be empty). Make sure you have Bun and Foundry installed on your system.