Reversible Commit Receipt

reversible-commit-receipt · receipt-attestation · 2670 B runtime · registry-info

scores

source

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

contract ReversibleCommitReceipt {
    struct Commitment {
        address committer;
        uint256 createdAt;
    }

    uint256 public immutable duration;
    uint256 public immutable fee;
    address public immutable owner;

    mapping(bytes32 => Commitment) private commitments;
    mapping(bytes32 => bool) public finalizedReceipts;
    bytes32[] private activeHashes;
    mapping(bytes32 => uint256) private hashToListIndex;

    event Committed(address indexed committer, bytes32 indexed hash, uint256 blockNumber);
    event Revoked(address indexed committer, bytes32 indexed hash, uint256 blockNumber);
    event Finalized(address indexed committer, bytes32 indexed hash, uint256 blockNumber);

    constructor(uint256 _duration, uint256 _fee) {
        duration = _duration;
        fee = _fee;
        owner = msg.sender;
    }

    function commit(bytes32 _hash) external {
        require(commitments[_hash].committer == address(0), "Hash already used");
        commitments[_hash] = Commitment(msg.sender, block.number);
        activeHashes.push(_hash);
        hashToListIndex[_hash] = activeHashes.length;
        emit Committed(msg.sender, _hash, block.number);
    }

    function revoke(bytes32 _hash) external payable {
        Commitment storage c = commitments[_hash];
        require(c.committer != address(0), "Commitment not found");
        require(msg.sender == c.committer, "Only committer");
        require(block.number < c.createdAt + duration, "Revocation window closed");
        require(msg.value == fee, "Revocation fee required");
        _removeHash(_hash);
        delete commitments[_hash];
        emit Revoked(msg.sender, _hash, block.number);
    }

    function finalize(bytes32 _hash) external {
        Commitment storage c = commitments[_hash];
        require(c.committer != address(0), "Commitment not found");
        require(block.number >= c.createdAt + duration, "Revocation window open");
        require(!finalizedReceipts[_hash], "Already finalized");
        finalizedReceipts[_hash] = true;
        _removeHash(_hash);
        emit Finalized(c.committer, _hash, block.number);
    }

    function collectExpired(uint256 _limit) external {
        uint256 processed = 0;
        uint256 i = 0;
        while (i < activeHashes.length && processed < _limit) {
            bytes32 h = activeHashes[i];
            Commitment storage c = commitments[h];
            if (block.number >= c.createdAt + duration) {
                _removeHash(h);
                delete commitments[h];
            } else {
                ++i;
            }
            ++processed;
        }
    }

    function withdraw() external {
        require(msg.sender == owner, "Not owner");
        payable(msg.sender).transfer(address(this).balance);
    }

    function activeCount() external view returns (uint256) {
        return activeHashes.length;
    }

    function getCommitment(bytes32 _hash) external view returns (address committer, uint256 createdAt, bool finalized) {
        Commitment storage c = commitments[_hash];
        return (c.committer, c.createdAt, finalizedReceipts[_hash]);
    }

    function _removeHash(bytes32 _hash) internal {
        uint256 idx = hashToListIndex[_hash] - 1;
        uint256 last = activeHashes.length - 1;
        if (idx != last) {
            bytes32 movedHash = activeHashes[last];
            activeHashes[idx] = movedHash;
            hashToListIndex[movedHash] = idx + 1;
        }
        activeHashes.pop();
        delete hashToListIndex[_hash];
    }
}