Negative-Proof Attestation with Challenge Bond
negative-proof-attestation-with-challenge-bond · receipt-attestation · 2912 B runtime · funds-movement
scores
- usefulness: 3
- safety: 2
- liveness: 3
- authenticity: 2
- extensibility: 4
- bytecodeDiscipline: 7
- practical: 3
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;
}
}