Prevent replayable Finalized events with a finalized state flag

prevent-replayable-finalized-events-with-a-finalized-state-f · receipt-attestation · 1111 B runtime · funds-movement

scores

source

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

contract ReceiptNotary {
    uint256 private data;

    event AnchorSet(bytes32 indexed anchor, address indexed submitter, uint32 deadline);
    event Disputed(bytes32 anchor, address indexed submitter);
    event Finalized(bytes32 anchor);

    uint256 private constant WINDOW = 2 days;
    uint256 private constant HASH_BITS = 63;
    uint256 private constant HASH_MASK = (1 << HASH_BITS) - 1;
    uint256 private constant ADDR_MASK = (1 << 160) - 1;
    uint256 private constant DEADLINE_MASK = (1 << 32) - 1;

    function submit(bytes32 anchor) external {
        require(data == 0);
        uint32 deadline = uint32(block.timestamp + WINDOW);
        uint64 hashPart = uint64(uint256(anchor) & HASH_MASK);
        data = (uint256(hashPart) << 192) | (uint256(uint160(msg.sender)) << 32) | deadline;
        emit AnchorSet(anchor, msg.sender, deadline);
    }

    function dispute(bytes calldata preimage) external {
        require(block.timestamp <= deadline());
        require(!disputed());
        bytes32 anchor = keccak256(preimage);
        require((uint256(anchor) & HASH_MASK) == ((data >> 192) & HASH_MASK));
        data |= DEADLINE_MASK;
        emit Disputed(anchor, submitter());
    }

    function finalize() external {
        require(block.timestamp > deadline());
        require(!disputed());
        require((data & (uint256(1) << 255)) == 0);
        data |= uint256(1) << 255;
        emit Finalized(anchor());
    }

    function anchor() public view returns (bytes32) {
        return bytes32((data >> 192) & HASH_MASK);
    }

    function submitter() public view returns (address) {
        return address(uint160((data >> 32) & ADDR_MASK));
    }

    function deadline() public view returns (uint32) {
        return uint32(data & DEADLINE_MASK);
    }

    function disputed() public view returns (bool) {
        return (data & DEADLINE_MASK) == DEADLINE_MASK;
    }
}