L2OutputOracle

Send the root of the L2 transaction tree, also known as the commitment, to L1, and verify the L2 state through ParaTonPortal.

The L2OutputOracle is a pivotal component within the TON and ParaTon ecosystems, serving as a repository for an array of L2 state outputs. Each entry in this array is a cryptographic commitment to the state of the L2 chain at a specific point in time. This mechanism ensures a high level of security and integrity for the data, making it a trusted source of truth for the state of Layer 2.

Contracts such as the ParaTonPortal leverage these state outputs to perform verifications and validate information about the L2 state. By utilizing the L2OutputOracle, these contracts can efficiently and accurately confirm the authenticity of transactions and states on the L2 chain. This functionality is crucial for maintaining the seamless operation and interoperability between L1 and L2 layers within the TON and ParaTon frameworks.

import "@stdlib/deploy";
import "@stdlib/ownable";

// Define the structure for OutputProposal
struct OutputProposal {
    outputRoot: Slice;
    timestamp: Int;
    l2BlockNumber: Int;
}

// Contract L2OutputOracle
contract L2OutputOracle with Deployable, Ownable {
    // State variables
    startingBlockNumber: Int;
    startingTimestamp: Int;
    l2Outputs: map<Int, OutputProposal>; // Array of L2 output proposals
    submissionInterval: Int;
    l2BlockTime: Int;
    challenger: Address;
    proposer: Address;
    finalizationPeriodSeconds: Int;
    version: String;
    owner: Address; // Required by Ownable trait
    outputCount: Int; // Counter to keep track of the number of outputs

    // Initial setup function
    // Initializes the contract with the given addresses
    init(submissionInterval: Int, l2BlockTime: Int, startingBlockNumber: Int, startingTimestamp: Int, proposer: Address, challenger: Address, finalizationPeriodSeconds: Int) {
        require(submissionInterval > 0, "L2OutputOracle: submission interval must be greater than 0");
        require(l2BlockTime > 0, "L2OutputOracle: L2 block time must be greater than 0");
        require(startingTimestamp <= now(), "L2OutputOracle: starting L2 timestamp must be less than current time");

        self.submissionInterval = submissionInterval;
        self.l2BlockTime = l2BlockTime;
        self.startingBlockNumber = startingBlockNumber;
        self.startingTimestamp = startingTimestamp;
        self.proposer = proposer;
        self.challenger = challenger;
        self.finalizationPeriodSeconds = finalizationPeriodSeconds;
        self.version = "1.0.0";
        self.l2Outputs = emptyMap();
        self.owner = sender(); // Initialize the owner as the sender of the deploy transaction
        self.outputCount = 0; // Initialize the output counter
    }

    // Placeholder function to simulate deleting L2 outputs
    receive("deleteL2Outputs") {
        // Only the challenger address can delete outputs
        let _l2OutputIndex: Int = 0; // Placeholder
        require(sender() == self.challenger, "L2OutputOracle: only the challenger address can delete outputs");
        require(_l2OutputIndex < self.outputCount, "L2OutputOracle: cannot delete outputs after the latest output index");
        require(now() - self.l2Outputs.get(_l2OutputIndex)!!.timestamp < self.finalizationPeriodSeconds, "L2OutputOracle: cannot delete outputs that have already been finalized");

        let prevNextL2OutputIndex: Int = self.nextOutputIndex();

        // Delete the array elements (Placeholder logic)
        self.l2Outputs.set(_l2OutputIndex, null);
        self.outputCount -= 1;

        emit(beginCell().storeUint(prevNextL2OutputIndex, 32).storeUint(_l2OutputIndex, 32).endCell());
    }

    // Function to propose L2 output
    receive("proposeL2Output") {
        let _outputRoot: Slice = beginCell().endCell().beginParse(); // Placeholder
        let _l2BlockNumber: Int = 0; // Placeholder
        let _l1BlockHash: Slice = beginCell().endCell().beginParse(); // Placeholder
        let _l1BlockNumber: Int = 0; // Placeholder

        require(sender() == self.proposer, "L2OutputOracle: only the proposer address can propose new outputs");
        require(_l2BlockNumber == self.nextBlockNumber(), "L2OutputOracle: block number must be equal to next expected block number");
        require(self.computeL2Timestamp(_l2BlockNumber) < now(), "L2OutputOracle: cannot propose L2 output in the future");
        require(!_outputRoot.empty(), "L2OutputOracle: L2 output proposal cannot be the zero hash");

        if (!_l1BlockHash.empty()) {
            // Check the block hash (Placeholder logic)
            require(true, "L2OutputOracle: block hash does not match the hash at the expected height");
        }

        emit(beginCell().storeSlice(_outputRoot).storeUint(self.nextOutputIndex(), 32).storeUint(_l2BlockNumber, 32).storeUint(now(), 32).endCell());

        let proposal: OutputProposal = OutputProposal {
            outputRoot: _outputRoot,
            timestamp: now(),
            l2BlockNumber: _l2BlockNumber
        };

        self.l2Outputs.set(self.nextOutputIndex(), proposal);
        self.outputCount += 1;
    }

    // Function to get an L2 output proposal by index
    get fun getL2Output(_l2OutputIndex: Int): OutputProposal {
        return self.l2Outputs.get(_l2OutputIndex)!!;
    }

    // Function to get the index of the L2 output that checkpoints a given L2 block number
    get fun getL2OutputIndexAfter(_l2BlockNumber: Int): Int {
        require(_l2BlockNumber <= self.latestBlockNumber(), "L2OutputOracle: cannot get output for a block that has not been proposed");
        require(self.outputCount > 0, "L2OutputOracle: cannot get output as no outputs have been proposed yet");

        return self.binarySearch(_l2BlockNumber, 0, self.outputCount);
    }

    // Helper function for binary search
    get fun binarySearch(target: Int, lo: Int, hi: Int): Int {
        if lo >= hi {
            return lo;
        }
        let mid: Int = (lo + hi) / 2;
        if self.l2Outputs.get(mid)!!.l2BlockNumber < target {
            return self.binarySearch(target, mid + 1, hi);
        } else {
            return self.binarySearch(target, lo, mid);
        }
    }

    // Function to get the latest output index
    get fun latestOutputIndex(): Int {
        return self.outputCount - 1;
    }

    // Function to get the next output index
    get fun nextOutputIndex(): Int {
        return self.outputCount;
    }

    // Function to get the latest block number
    get fun latestBlockNumber(): Int {
        return self.outputCount == 0 ? self.startingBlockNumber : self.l2Outputs.get(self.outputCount - 1)!!.l2BlockNumber;
    }

    // Function to get the next block number
    get fun nextBlockNumber(): Int {
        return self.latestBlockNumber() + self.submissionInterval;
    }

    // Function to compute the L2 timestamp for a given block number
    get fun computeL2Timestamp(_l2BlockNumber: Int): Int {
        return self.startingTimestamp + ((_l2BlockNumber - self.startingBlockNumber) * self.l2BlockTime);
    }
}

Last updated