Deadline-Locked Challenge Escrow
deadline-locked-challenge-escrow · conditional-settlement · 2468 B runtime · funds-movement
scores
- usefulness: 20
- safety: 100
- liveness: 20
- authenticity: 25
- extensibility: 40
- bytecodeDiscipline: 60
- practical: 46
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;
}
}