Deadline-Locked Challenge Escrow

deadline-locked-challenge-escrow · conditional-settlement · 2468 B runtime · funds-movement

scores

source

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

contract DeadlineLockedChallengeEscrow {
    enum State { Pending, Challenged, Resolved }

    address public depositor;
    address public claimant;
    bytes32 public merkleRoot;
    uint256 public amount;
    uint256 public deadline;
    uint256 public challengeDeposit;
    State public state;
    address public challenger;

    address private constant BURN_ADDRESS = address(0xdead);

    event Created(address indexed depositor, address indexed claimant, uint256 amount, uint256 deadline, bytes32 merkleRoot, uint256 challengeDeposit);
    event Claimed(address indexed claimant, uint256 amount);
    event ChallengeFiled(address indexed challenger, bytes32 leaf);
    event Resolved(bool challengeValid, address winner, uint256 amount);

    constructor(address _claimant, bytes32 _merkleRoot, uint256 _deadline, uint256 _challengeDeposit) payable {
        require(msg.value > 0, "No escrow");
        depositor = msg.sender;
        claimant = _claimant;
        merkleRoot = _merkleRoot;
        deadline = _deadline;
        challengeDeposit = _challengeDeposit;
        amount = msg.value;
        emit Created(msg.sender, _claimant, msg.value, _deadline, _merkleRoot, _challengeDeposit);
    }

    function claim() external {
        require(state == State.Pending, "Not pending");
        require(msg.sender == claimant, "Not claimant");
        require(block.timestamp >= deadline, "Too early");
        state = State.Resolved;
        (bool ok,) = claimant.call{value: amount}("");
        require(ok, "Claim transfer failed");
        emit Claimed(claimant, amount);
    }

    function challenge(bytes32 leaf, bytes32[] calldata proof) external payable {
        require(state == State.Pending, "Not pending");
        require(block.timestamp < deadline, "Too late");
        require(msg.value == challengeDeposit, "Wrong deposit");
        if (_verify(proof, merkleRoot, leaf)) {
            challenger = msg.sender;
            state = State.Challenged;
            emit ChallengeFiled(msg.sender, leaf);
        } else {
            (bool ok,) = BURN_ADDRESS.call{value: msg.value}("");
            require(ok, "Burn failed");
        }
    }

    function resolve() external {
        require(state == State.Challenged, "Not challenged");
        require(block.timestamp >= deadline, "Resolve after deadline");
        state = State.Resolved;
        address winner = depositor;
        (bool ok1,) = challenger.call{value: challengeDeposit}("");
        require(ok1, "Refund failed");
        (bool ok2,) = depositor.call{value: amount}("");
        require(ok2, "Payout failed");
        emit Resolved(true, winner, amount);
    }

    function _verify(bytes32[] calldata proof, bytes32 root, bytes32 leaf) private pure returns (bool) {
        bytes32 computed = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            bytes32 p = proof[i];
            if (computed <= p) {
                computed = keccak256(abi.encodePacked(computed, p));
            } else {
                computed = keccak256(abi.encodePacked(p, computed));
            }
        }
        return computed == root;
    }
}