website/design/solidity-lab/generated/FairSettlement.sol

258 lines
14 KiB
Solidity

// SPDX-License-Identifier: MIT
// Generated by the OSS Singularity contract lab (roadmap stage 06 first slice).
// SYNTHETIC EXAMPLE — Synthetic fair settlement example. Placeholder addresses, a digest
// over the public synthetic artifact and future deadlines; no funds, no
// deployment, no wallet. This contract records settlement states only: it is
// not an escrow, never receives or holds anything, and is deliberately
// not payable. The lab explains every requested signature; it never requests
// one. Nothing signs or deploys merely because this file exists.
pragma solidity ^0.8.24;
/// @title FairSettlement — a readable fair-settlement state machine.
/// @notice Mirrors the accepted settlement design note: the agreement comes
/// before the work and before any funds move. The happy path is
/// funded → delivered → accepted → released; either party may open a
/// dispute (delivered → disputed → resolved → released or refunded),
/// and cancellation plus every timeout end in a DEFINED outcome. The
/// real budget sits with a SEPARATELY AUTHORIZED holder — an escrow
/// instance or named human authority — that reads these records and
/// executes transfers in its own audited system. This contract never
/// holds, receives, or moves value of any kind. The Commons never
/// holds funds and never gains custody.
contract FairSettlement {
/// The settlement states, named exactly as the design note names them.
enum State {
none, // agreement recorded, funding not yet declared
funded, // the budget sits with the separately authorized holder
delivered, // a delivery receipt links the digest to the work
accepted, // the coordinator accepted the newest revision (release CONDITION)
disputed, // one party named the dispute path
resolved, // the holder recorded the dispute resolution
released, // terminal: settled to the contributor
refunded // terminal: settled back to the coordinator
}
// ---- Roles from the agreement (synthetic placeholders) ----
address public immutable contributor = 0x0000000000000000000000000000000000000001;
address public immutable coordinator = 0x0000000000000000000000000000000000000002;
// Placeholder for the separately authorized holder of the real budget.
// This contract records what the holder declares; custody itself is a
// separate, audited system with its own threat model.
address public immutable holder = 0x0000000000000000000000000000000000000003;
// ---- Delivery reference: sha256 over the delivered bytes ----
// The manifest digest of the synthetic artifact this example stands for.
bytes32 public constant DECLARED_DIGEST = 0x74aebc353c86c6269f80cc8509528ab19b3a1612ac3291bab40f32701454f69b;
// ---- Named deadlines: every waiting state has one ----
uint256 public constant REVIEW_DEADLINE = 1830000000; // delivery not reviewed → dispute path, never silent release
uint256 public constant DISPUTE_DEADLINE = 1832592000; // dispute unresolved → the agreement's fallback
uint256 public constant OUTER_DEADLINE = 1835184000; // from it (inclusive) the refund path has EXCLUSIVE priority
// ---- The fallback outcome the agreement fixed for a deadlocked dispute ----
bool public constant DISPUTE_FALLBACK_RELEASES = false; // refund
// ---- Settlement state: one path at a time, terminal states are final ----
State public state = State.none;
uint16 public currentRevision; // last delivered revision; 0 = none yet
uint16 public acceptedRevision; // 0 = nothing accepted yet
bool public resolutionReleases; // the holder's recorded dispute decision
event FundingRecorded(address indexed who);
event DeliveryRecorded(uint16 indexed revision, bytes32 digest, address indexed who);
event Accepted(uint16 indexed revision, address indexed who);
event DisputeOpened(string reason, address indexed who);
event DisputeResolved(bool releases, address indexed who);
event Released(address indexed who);
event Refunded(string reason, address indexed who);
error NotContributor();
error NotCoordinator();
error NotHolder();
error NotParty(address sender);
error WrongState(State current);
error DuplicateRevision(uint16 revision);
error NotNewestRevision(uint16 newest);
error DeadlinePassed(uint256 now_, uint256 deadline);
error DisputeWindowStillOpen(uint256 now_, uint256 deadline);
error OuterDeadlineNotPassed(uint256 now_, uint256 deadline);
error OuterDeadlinePassed(uint256 now_, uint256 deadline);
modifier onlyContributor() {
if (msg.sender != contributor) revert NotContributor();
_;
}
modifier onlyCoordinator() {
if (msg.sender != coordinator) revert NotCoordinator();
_;
}
modifier onlyHolder() {
if (msg.sender != holder) revert NotHolder();
_;
}
modifier onlyParty() {
if (msg.sender != contributor && msg.sender != coordinator) revert NotParty(msg.sender);
_;
}
/// @notice The holder records that the agreed budget sits with the
/// separately authorized holder. This declares funding; it never
/// receives anything.
/// @dev Signature (): no arguments. Reverts for anyone but the holder
/// or when funding was already declared.
function recordFunding() external onlyHolder {
if (state != State.none) revert WrongState(state);
state = State.funded;
emit FundingRecorded(msg.sender);
}
/// @notice The contributor records the next immutable delivery revision,
/// referencing the declared digest — the same delivery shape as
/// the coordination protocol.
/// @dev Signature (uint16): the new revision number, one above the
/// current. Reverts for anyone but the contributor, for a repeat
/// or skip, or when the settlement is not waiting for delivery.
function recordDelivery(uint16 revision) external onlyContributor {
if (state != State.funded) revert WrongState(state);
if (revision != currentRevision + 1) revert DuplicateRevision(revision);
currentRevision = revision;
state = State.delivered;
emit DeliveryRecorded(revision, DECLARED_DIGEST, msg.sender);
}
/// @notice The coordinator accepts exactly the newest revision before the
/// review deadline. Acceptance is the release CONDITION, never the
/// release itself. After the deadline the dispute path is the only
/// way forward — never a silent release.
/// @dev Signature (uint16): the revision being accepted. Reverts for
/// non-coordinators, stale revisions, a passed review deadline, or
/// when the settlement is not in the delivered state.
function accept(uint16 revision) external onlyCoordinator {
if (state != State.delivered) revert WrongState(state);
if (revision != currentRevision) revert NotNewestRevision(currentRevision);
if (block.timestamp > REVIEW_DEADLINE) revert DeadlinePassed(block.timestamp, REVIEW_DEADLINE);
acceptedRevision = revision;
state = State.accepted;
emit Accepted(revision, msg.sender);
}
/// @notice The holder executes the release its custody rules require once
/// acceptance has satisfied the release condition — before the
/// outer deadline. From the deadline instant itself on (the
/// moment the refund path opens), no new release can succeed:
/// refund has priority. The transfer itself happens in the
/// holder's separate, audited system; this record only marks
/// the outcome.
/// @dev Signature (): no arguments. Reverts for anyone but the holder,
/// when the settlement has not been accepted, or from the outer
/// deadline instant on (OuterDeadlinePassed — the refund window
/// and the release window are disjoint).
function release() external onlyHolder {
if (state != State.accepted) revert WrongState(state);
if (block.timestamp >= OUTER_DEADLINE) revert OuterDeadlinePassed(block.timestamp, OUTER_DEADLINE);
state = State.released;
emit Released(msg.sender);
}
/// @notice Either party opens the dispute path named in the agreement,
/// stating why. The holder then applies the agreed rules; the
/// service records outcomes only.
/// @dev Signature (string calldata): the reason the dispute was opened.
/// Reverts for anyone but the two parties or when nothing is
/// awaiting review.
function openDispute(string calldata reason) external onlyParty {
if (state != State.delivered) revert WrongState(state);
state = State.disputed;
emit DisputeOpened(reason, msg.sender);
}
/// @notice The holder records the resolution decided under the agreed
/// dispute rules, inside the dispute window. releases=true means
/// the work settles as delivered; false means refund.
/// @dev Signature (bool): which way the resolution goes. Reverts for
/// anyone but the holder, outside the disputed state, or after the
/// dispute deadline.
function resolveDispute(bool releases) external onlyHolder {
if (state != State.disputed) revert WrongState(state);
if (block.timestamp > DISPUTE_DEADLINE) revert DeadlinePassed(block.timestamp, DISPUTE_DEADLINE);
resolutionReleases = releases;
state = State.resolved;
emit DisputeResolved(releases, msg.sender);
}
/// @notice The holder executes the recorded resolution: released or
/// refunded, exactly as resolved. A releasing execution is
/// refused from the outer deadline instant on — the refund path
/// has priority from then on; a refunding execution still
/// reaches its (already refund) outcome. The transfer itself
/// stays in the holder's custody system.
/// @dev Signature (): no arguments. Reverts for anyone but the holder,
/// when no resolution has been recorded, or when the resolution
/// releases from the outer deadline instant on.
function executeResolution() external onlyHolder {
if (state != State.resolved) revert WrongState(state);
if (resolutionReleases && block.timestamp >= OUTER_DEADLINE) revert OuterDeadlinePassed(block.timestamp, OUTER_DEADLINE);
if (resolutionReleases) {
state = State.released;
emit Released(msg.sender);
} else {
state = State.refunded;
emit Refunded("dispute resolution", msg.sender);
}
}
// ---- Timeouts: every waiting state names a deadline and a default ----
// A timeout produces a DEFINED outcome; it never mints authority. The two
// paths below are therefore deliberately permissionless: they can only
// ever reach the outcome the agreement already fixed. From the outer
// deadline instant (inclusive) the refund path has EXCLUSIVE priority:
// every release-minting transition is refused exactly when the refund
// becomes available, so a release can never race the refund for the
// same state.
/// @notice Past the dispute window the agreed fallback replaces a
/// silent holder: the settlement takes the outcome fixed in the
/// agreement (refund) without needing anyone's cooperation —
/// unless that outcome is a release and the outer deadline has
/// been reached: the refund priority outranks even the agreed
/// fallback.
/// @dev Signature (): no arguments. Reverts while the settlement is not
/// disputed, while the dispute window is still open, or when the
/// fallback releases from the outer deadline instant on.
function applyDisputeFallback() external {
if (state != State.disputed) revert WrongState(state);
if (block.timestamp <= DISPUTE_DEADLINE) revert DisputeWindowStillOpen(block.timestamp, DISPUTE_DEADLINE);
if (DISPUTE_FALLBACK_RELEASES && block.timestamp >= OUTER_DEADLINE) revert OuterDeadlinePassed(block.timestamp, OUTER_DEADLINE);
if (DISPUTE_FALLBACK_RELEASES) {
state = State.released;
emit Released(msg.sender);
} else {
state = State.refunded;
emit Refunded("dispute fallback", msg.sender);
}
}
/// @notice From the outer deadline instant on (inclusive), funds
/// unreleased take the refund path from every non-terminal
/// state: the budget flows back rather than sitting withheld
/// indefinitely. From that moment on this is the only outcome —
/// every release path is closed.
/// @dev Signature (): no arguments. Reverts before the outer deadline
/// and on terminal states (nothing left to refund).
function refundAfterOuterDeadline() external {
if (state == State.none || state == State.released || state == State.refunded) revert WrongState(state);
if (block.timestamp < OUTER_DEADLINE) revert OuterDeadlineNotPassed(block.timestamp, OUTER_DEADLINE);
state = State.refunded;
emit Refunded("outer deadline", msg.sender);
}
/// @notice Cancellation before delivery: the agreement named cancellation,
/// and the holder records the budget flowing back unused.
/// @dev Signature (): no arguments. Reverts for anyone but the holder
/// or when delivery has already begun.
function refundOnCancellation() external onlyHolder {
if (state != State.funded) revert WrongState(state);
state = State.refunded;
emit Refunded("cancelled before delivery", msg.sender);
}
}