ReceiptDuel

receiptduel · receipt-attestation · 2459 B runtime · funds-movement

scores

source

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

contract ReceiptDuel {
    uint256 constant CHALLENGE_WINDOW = 7 days;
    uint256 constant REVEAL_WINDOW = 1 days;

    struct Duel {
        address submitter;
        bytes32 receiptHash;
        uint256 submitTime;
        uint256 bond;
        address challenger;
        uint256 challengeTime;
        uint256 challengeBond;
    }

    Duel private duel;

    event Submit(address indexed submitter, bytes32 receiptHash, uint256 bond);
    event Challenge(address indexed challenger, uint256 bond);
    event Reveal(address indexed winner, bytes32 preimage);
    event Claim(address indexed challenger, uint256 amount);
    event Finalize(address indexed submitter, uint256 amount);

    function submitReceipt(bytes32 receiptHash) external payable {
        require(duel.submitter == address(0), "ACTIVE");
        require(msg.value > 0, "BOND");
        duel.submitter = msg.sender;
        duel.receiptHash = receiptHash;
        duel.submitTime = block.timestamp;
        duel.bond = msg.value;
        emit Submit(msg.sender, receiptHash, msg.value);
    }

    function challengeReceipt() external payable {
        require(duel.submitter != address(0), "NO_ACTIVE");
        require(duel.challenger == address(0), "CHALLENGED");
        require(block.timestamp <= duel.submitTime + CHALLENGE_WINDOW, "TOO_LATE");
        require(msg.value > duel.bond, "BOND_TOO_SMALL");
        duel.challenger = msg.sender;
        duel.challengeTime = block.timestamp;
        duel.challengeBond = msg.value;
        emit Challenge(msg.sender, msg.value);
    }

    function revealReceipt(bytes32 preimage) external {
        require(duel.challenger != address(0), "NOT_CHALLENGED");
        require(msg.sender == duel.submitter, "NOT_SUBMITTER");
        require(block.timestamp <= duel.challengeTime + REVEAL_WINDOW, "REVEAL_ENDED");
        require(keccak256(abi.encodePacked(preimage)) == duel.receiptHash, "BAD_PREIMAGE");
        address winner = duel.submitter;
        uint256 amount = duel.bond + duel.challengeBond;
        delete duel;
        (bool sent,) = payable(winner).call{value: amount}("");
        require(sent, "SEND_FAILED");
        emit Reveal(winner, preimage);
    }

    function claimChallenge() external {
        require(duel.challenger != address(0), "NOT_CHALLENGED");
        require(msg.sender == duel.challenger, "NOT_CHALLENGER");
        require(block.timestamp > duel.challengeTime + REVEAL_WINDOW, "REVEAL_NOT_ENDED");
        address winner = duel.challenger;
        uint256 amount = duel.bond + duel.challengeBond;
        delete duel;
        (bool sent,) = payable(winner).call{value: amount}("");
        require(sent, "SEND_FAILED");
        emit Claim(winner, amount);
    }

    function finalize() external {
        require(duel.submitter != address(0), "NO_ACTIVE");
        require(duel.challenger == address(0), "CHALLENGED");
        require(block.timestamp > duel.submitTime + CHALLENGE_WINDOW, "EARLY");
        address submitter = duel.submitter;
        uint256 amount = duel.bond;
        delete duel;
        (bool sent,) = payable(submitter).call{value: amount}("");
        require(sent, "SEND_FAILED");
        emit Finalize(submitter, amount);
    }
}