Negative-Proof Attestation with Challenge Bond

negative-proof-attestation-with-challenge-bond · receipt-attestation · 2912 B runtime · funds-movement

scores

source

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

contract NegativeProofAttestation {
    enum Status { Active, Rebutted, Finalized }

    struct Receipt {
        address client;
        address agent;
        uint256 bond;
        uint256 deadline;
        Status status;
    }

    mapping(bytes32 => Receipt) public receipts;
    mapping(address => bytes32) public workHash;
    mapping(address => uint256) public stake;

    uint256 public constant BOND_BPS = 200;
    uint256 public constant BOND_CAP = 1 ether;
    uint256 public constant CHALLENGE_PERIOD = 7 days;

    event WorkCommitted(address indexed agent, bytes32 workHash, uint256 stake);
    event ComplaintFiled(bytes32 indexed complaintId, address indexed client, address indexed agent, uint256 bond, uint256 deadline);
    event Rebutted(bytes32 indexed complaintId, address indexed agent);
    event Finalized(bytes32 indexed complaintId, address indexed client);

    function commitWork(bytes32 workHash_, uint256 stake_) external {
        require(stake_ > 0, "stake must be positive");
        workHash[msg.sender] = workHash_;
        stake[msg.sender] = stake_;
        emit WorkCommitted(msg.sender, workHash_, stake_);
    }

    function fileComplaint(bytes32 complaintId, address agent) external payable {
        require(workHash[agent] != bytes32(0), "agent not committed");
        require(receipts[complaintId].client == address(0), "complaintId exists");
        uint256 requiredBond = _calculateBond(stake[agent]);
        require(msg.value == requiredBond, "incorrect bond");
        receipts[complaintId] = Receipt({
            client: msg.sender,
            agent: agent,
            bond: msg.value,
            deadline: block.timestamp + CHALLENGE_PERIOD,
            status: Status.Active
        });
        emit ComplaintFiled(complaintId, msg.sender, agent, msg.value, receipts[complaintId].deadline);
    }

    function rebut(bytes32 complaintId, bytes calldata preimage) external {
        Receipt storage r = receipts[complaintId];
        require(r.status == Status.Active, "not active");
        require(r.agent == msg.sender, "not agent");
        require(block.timestamp < r.deadline, "challenge period over");
        require(keccak256(preimage) == workHash[msg.sender], "preimage mismatch");
        r.status = Status.Rebutted;
        (bool success,) = payable(msg.sender).call{value: r.bond}("");
        require(success, "transfer failed");
        emit Rebutted(complaintId, msg.sender);
    }

    function finalize(bytes32 complaintId) external {
        Receipt storage r = receipts[complaintId];
        require(r.status == Status.Active, "not active");
        require(block.timestamp >= r.deadline, "challenge period not over");
        r.status = Status.Finalized;
        (bool success,) = payable(r.client).call{value: r.bond}("");
        require(success, "transfer failed");
        emit Finalized(complaintId, r.client);
    }

    function _calculateBond(uint256 stakeAmount) internal pure returns (uint256) {
        uint256 bond = (stakeAmount * BOND_BPS) / 10000;
        if (bond > BOND_CAP) {
            bond = BOND_CAP;
        }
        return bond;
    }
}