// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/**
 * @title AereCancunCanary
 * @notice On-chain canary proving Cancun EVM support on AERE (chain 2800):
 *         - TSTORE / TLOAD (EIP-1153), including auto-reset between
 *           transactions and a transient reentrancy lock,
 *         - MCOPY (EIP-5656) round-trip verified by keccak256,
 *         - PUSH0 (EIP-3855), implicit in solc >=0.8.20 shanghai+ output,
 *         - BLOBHASH (EIP-4844) and BLOBBASEFEE (EIP-7516) opcode execution.
 *
 *         Compiled with solc 0.8.24, evmVersion = cancun, via a per-file
 *         hardhat override (the repo global stays 0.8.23 / shanghai-default).
 *
 *         The constructor self-tests every opcode, so a successful deploy,
 *         or even a read-only eth_estimateGas / eth_call of the creation
 *         bytecode, is itself proof that the live chain executes Cancun
 *         opcodes: any TSTORE/TLOAD/MCOPY/BLOBHASH/BLOBBASEFEE failure
 *         reverts deployment.
 *
 *         PUSH0 note for verifiers: the deployed runtime bytecode contains
 *         genuine PUSH0 (0x5f) opcodes; the canonical solc revert stub
 *         `5f80fd` (PUSH0 DUP1 REVERT) appears in the runtime code. To check
 *         rigorously, walk the bytecode from offset 0 skipping PUSH1..PUSH32
 *         immediates and confirm 0x5f at an opcode position. Every
 *         successful call to this contract executes PUSH0 on-chain.
 *
 *         Blob note: AERE is a QBFT L1 that does not accept blob (type-3)
 *         transactions, so blobhash(0) == bytes32(0) in every execution
 *         context. blobbasefee is whatever the node's Cancun fee market
 *         reports (protocol minimum is 1 wei); it is recorded, not asserted.
 */
contract AereCancunCanary {
    /// @dev keccak256("aere.cancun.canary.tslot"); literal so assembly can use it.
    uint256 private constant T_SLOT =
        0x600b3c753bdea90810db30bcdc9015ebc00429a1e131fa35c272b23785c573b1;

    /// @dev keccak256("aere.cancun.canary.lock"); literal so assembly can use it.
    uint256 private constant LOCK_SLOT =
        0xe4d0d997dd2753c27700e5fe7c65208afc32dd89178d805290c63adf8ca6331e;

    /// @notice true iff the constructor's TSTORE/TLOAD + MCOPY + blob-opcode
    ///         self-test passed on the deploying chain.
    bool public immutable deploySelfTest;

    event TransientWithinTx(uint256 written, uint256 readBack);
    event TransientCrossTx(uint256 observed);
    event ReentrancyLockProbe(bool attemptedReentry, bool innerCallReverted);
    event McopyChecked(uint256 dataLen, bytes32 inputHash, bytes32 copyHash, bool ok);
    event BlobProbed(bytes32 blobHash0, uint256 blobBaseFee);

    error TransientLockActive();

    /// @dev Reentrancy guard using EIP-1153 transient storage. Costs no
    ///      cold-SSTORE gas and needs no post-tx cleanup guarantees beyond
    ///      the EVM's own end-of-transaction transient reset.
    modifier transientLock() {
        uint256 lockV;
        assembly {
            lockV := tload(LOCK_SLOT)
        }
        if (lockV != 0) revert TransientLockActive();
        assembly {
            tstore(LOCK_SLOT, 1)
        }
        _;
        assembly {
            tstore(LOCK_SLOT, 0)
        }
    }

    constructor() {
        // EIP-1153 round-trip.
        uint256 v;
        assembly {
            tstore(T_SLOT, 0xCA9A17)
            v := tload(T_SLOT)
        }
        require(v == 0xCA9A17, "TSTORE/TLOAD failed");
        assembly {
            tstore(T_SLOT, 0)
        }

        // EIP-5656 round-trip.
        bytes memory src = abi.encodePacked(
            keccak256("aere.cancun.canary.mcopy.src"),
            uint256(2800)
        );
        bytes memory dst = new bytes(src.length);
        uint256 len = src.length;
        assembly {
            mcopy(add(dst, 0x20), add(src, 0x20), len)
        }
        require(keccak256(dst) == keccak256(src), "MCOPY failed");

        // EIP-4844 / EIP-7516 opcodes must execute (no blob txs on QBFT, so
        // blobhash(0) is zero; blobbasefee is recorded, not asserted).
        bytes32 h;
        uint256 f;
        assembly {
            h := blobhash(0)
            f := blobbasefee()
        }
        require(h == bytes32(0), "unexpected blob hash");
        emit BlobProbed(h, f);

        deploySelfTest = true;
    }

    // ---------------------------------------------------------------------
    // EIP-1153 transient storage
    // ---------------------------------------------------------------------

    /// @notice Method A: TSTOREs `value` and TLOADs it back within the same
    ///         transaction; emits both as proof.
    function transientWriteAndRead(uint256 value) external returns (uint256 readBack) {
        assembly {
            tstore(T_SLOT, value)
            readBack := tload(T_SLOT)
        }
        emit TransientWithinTx(value, readBack);
    }

    /// @notice Method B: TLOADs the same slot in a LATER transaction. Must
    ///         observe 0, proving the EVM auto-resets transient storage
    ///         between transactions.
    function transientReadOnly() external returns (uint256 observed) {
        assembly {
            observed := tload(T_SLOT)
        }
        emit TransientCrossTx(observed);
    }

    /// @notice View twin of transientReadOnly for eth_call checks.
    function transientPeek() external view returns (uint256 observed) {
        assembly {
            observed := tload(T_SLOT)
        }
    }

    /// @notice Current value of the transient reentrancy lock slot (always 0
    ///         when read from a fresh transaction).
    function lockPeek() external view returns (uint256 v) {
        assembly {
            v := tload(LOCK_SLOT)
        }
    }

    /// @notice Transient reentrancy lock demonstration. With
    ///         `attemptReentry = true` the function re-enters itself via an
    ///         external self-call; the inner call MUST revert with
    ///         TransientLockActive, and `innerCallReverted` is emitted true.
    ///         A subsequent plain call (`attemptReentry = false`) succeeds,
    ///         showing the lock cleared.
    function guardedEnter(bool attemptReentry)
        external
        transientLock
        returns (bool innerCallReverted)
    {
        if (attemptReentry) {
            (bool ok, ) = address(this).call(
                abi.encodeWithSelector(this.guardedEnter.selector, false)
            );
            innerCallReverted = !ok;
        }
        emit ReentrancyLockProbe(attemptReentry, innerCallReverted);
    }

    // ---------------------------------------------------------------------
    // EIP-5656 MCOPY
    // ---------------------------------------------------------------------

    /// @notice Copies `data` with assembly `mcopy` and returns keccak256 of
    ///         both the input and the copy; equal hashes prove a faithful
    ///         MCOPY round-trip.
    function mcopyHash(bytes calldata data)
        public
        pure
        returns (bytes32 inputHash, bytes32 copyHash)
    {
        bytes memory input = data;
        inputHash = keccak256(input);
        uint256 len = input.length;
        bytes memory copyBuf = new bytes(len);
        assembly {
            mcopy(add(copyBuf, 0x20), add(input, 0x20), len)
            copyHash := keccak256(add(copyBuf, 0x20), len)
        }
    }

    /// @notice Transaction variant of mcopyHash; emits the comparison so the
    ///         proof lives in a receipt.
    function mcopyCheck(bytes calldata data) external returns (bool ok) {
        (bytes32 inputHash, bytes32 copyHash) = mcopyHash(data);
        ok = inputHash == copyHash;
        emit McopyChecked(data.length, inputHash, copyHash, ok);
    }

    // ---------------------------------------------------------------------
    // EIP-4844 BLOBHASH / EIP-7516 BLOBBASEFEE
    // ---------------------------------------------------------------------

    /// @notice Returns blobhash(0) and blobbasefee via assembly. On AERE
    ///         (QBFT, no blob transactions) blobhash(0) == bytes32(0);
    ///         blobbasefee is the node-reported blob base fee (min 1 wei).
    function blobView() public view returns (bytes32 blobHash0, uint256 blobBaseFee) {
        assembly {
            blobHash0 := blobhash(0)
            blobBaseFee := blobbasefee()
        }
    }

    /// @notice Transaction variant of blobView; emits the values.
    function blobProbe() external returns (bytes32 blobHash0, uint256 blobBaseFee) {
        (blobHash0, blobBaseFee) = blobView();
        emit BlobProbed(blobHash0, blobBaseFee);
    }
}
