// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /* =========================================================================== EPCAgreement.sol قرارداد هوشمند نیمه‌اتوماسیون بر پایه موافقت‌نامه پیمان EPC مرجع یگانه: «نمونه قرارداد موافقت‌نامه پیمان‌های مهندسی، تأمین کالا و تجهیزات، ساختمان و نصب» — هجده ماده. هیچ ماده، بند، شماره یا تعهدی خارج از این هجده ماده در این کد وجود ندارد. هر بخش کد با شماره ماده متناظر خود علامت‌گذاری شده است. طبقه‌بندی حاصل از مدل مفهومی پژوهش: هوشمند (Smart) — مواد ۲، ۵، ۶، ۷، ۱۰، ۱۱، ۱۶، ۱۸ نیمه‌هوشمند (Semi-Smart) — مواد ۳، ۴، ۸، ۹، ۱۴ سنتی (Traditional) — مواد ۱، ۱۲، ۱۳، ۱۵، ۱۷ =========================================================================== */ contract EPCAgreement { /* ====================================================================== بخش پایه — نقش‌های تعریف‌شده در موافقت‌نامه طرفین: کارفرما و پیمانکار (سرصفحه موافقت‌نامه) مشاور کارفرما: ماده ۱۱ | داور مرضی‌الطرفین: ماده ۱۷ ====================================================================== */ address public governor; // راهبر سامانه — جزء تکمیلی پیاده‌سازی address public employer; // کارفرما address public contractor; // پیمانکار address public consultant; // مشاور کارفرما — ماده ۱۱ address public arbitrator; // داور مرضی‌الطرفین — ماده ۱۷ string public projectTitle; // ماده ۱ — موضوع پیمان bool public halted; // توقف اضطراری — جزء تکمیلی uint32 public timeUnit = 1 days; // برای نمایش می‌توان روی ثانیه گذاشت /* مهلت‌های مصرح در متن موافقت‌نامه */ uint16 public constant EFFECTIVE_WINDOW_UNITS = 90; // ماده ۵ — نود روز uint16 public constant START_NOTICE_UNITS = 30; // ماده ۶ — سی روز error NotAuthorized(); error SystemHalted(); error NotEffective(); error GateNotReady(); error TimelockActive(uint64 executableAt); error CapExceeded(uint16 bps, uint16 capBps); function _is(address role) internal view returns (bool) { return role == address(0) ? msg.sender == governor : msg.sender == role; } modifier onlyGovernor() { if (msg.sender != governor) revert NotAuthorized(); _; } modifier onlyEmployer() { if (!_is(employer)) revert NotAuthorized(); _; } modifier onlyContractor() { if (!_is(contractor)) revert NotAuthorized(); _; } modifier whenActive() { if (halted) revert SystemHalted(); _; } modifier whenEffective() { if (effectiveDate == 0) revert NotEffective(); _; } function _isApprover(address a) internal view returns (bool) { if (employer == address(0) && consultant == address(0) && arbitrator == address(0)) return a == governor; return a == employer || a == consultant || a == arbitrator; } event AgreementDeployed(string projectTitle, address employer, address contractor); event GlobalHalt(address indexed by, string reason); event GlobalResume(address indexed by); constructor(string memory _projectTitle) { governor = msg.sender; projectTitle = _projectTitle; emit AgreementDeployed(_projectTitle, address(0), address(0)); } function setParties(address _employer, address _contractor, address _consultant, address _arbitrator) external onlyGovernor { employer = _employer; contractor = _contractor; consultant = _consultant; arbitrator = _arbitrator; } function setTimeUnit(uint32 secondsPerUnit) external onlyGovernor { timeUnit = secondsPerUnit; } function haltAll(string calldata reason) external { require(_is(employer) || _is(consultant) || _is(arbitrator), "not authorized"); halted = true; emit GlobalHalt(msg.sender, reason); } function resumeAll() external onlyGovernor { halted = false; emit GlobalResume(msg.sender); } /* ====================================================================== ماده ۲ — اسناد و مدارک پیمان وضعیت: هوشمند ---------------------------------------------------------------------- متن ماده پنج جزء را برمی‌شمارد: موافقت‌نامه، پیوست‌ها، شرایط عمومی، شرایط خصوصی، و «سایر اسناد و مدارکی که در مدت اجرای کار تنظیم می‌شود و به تأیید دو طرف پیمان می‌رسد». چهار جزء نخست ثابت‌اند و ثبت می‌شوند؛ جزء پنجم مشروط به تأیید دو طرف است و تا تأیید هر دو، اثر قراردادی ندارد. ====================================================================== */ enum DocKind { Agreement, Annexes, GeneralConditions, SpecialConditions, Additional } struct ContractDoc { bytes32 docHash; string title; DocKind kind; uint64 filedAt; bool employerApproved; bool contractorApproved; bool effective; } uint256 public docCount; mapping(uint256 => ContractDoc) public contractDocs; event BaseDocRegistered(uint256 indexed id, DocKind kind, bytes32 docHash, string title); event AdditionalDocProposed(uint256 indexed id, bytes32 docHash, string title, address by); event AdditionalDocApproved(uint256 indexed id, address by); event AdditionalDocEffective(uint256 indexed id, uint64 at); /// @notice ثبت چهار سند پایه موضوع ماده ۲ — اجرای خودکار، بدون تأیید function registerBaseDoc(DocKind kind, bytes32 docHash, string calldata title) external onlyGovernor returns (uint256 id) { require(kind != DocKind.Additional, "use proposeAdditionalDoc"); id = ++docCount; contractDocs[id] = ContractDoc(docHash, title, kind, uint64(block.timestamp), true, true, true); emit BaseDocRegistered(id, kind, docHash, title); } /// @notice پیشنهاد سند تکمیلی در مدت اجرای کار — جزء پنجم ماده ۲ function proposeAdditionalDoc(bytes32 docHash, string calldata title) external whenActive returns (uint256 id) { require(_is(employer) || _is(contractor), "not a party"); id = ++docCount; contractDocs[id] = ContractDoc(docHash, title, DocKind.Additional, uint64(block.timestamp), _is(employer), _is(contractor), false); emit AdditionalDocProposed(id, docHash, title, msg.sender); } /// @notice تأیید سند تکمیلی؛ با تأیید هر دو طرف، سند جزو پیمان می‌شود function approveAdditionalDoc(uint256 id) external whenActive { ContractDoc storage d = contractDocs[id]; require(d.kind == DocKind.Additional && !d.effective, "bad document"); if (_is(employer)) d.employerApproved = true; else if (_is(contractor)) d.contractorApproved = true; else revert NotAuthorized(); emit AdditionalDocApproved(id, msg.sender); if (d.employerApproved && d.contractorApproved) { d.effective = true; emit AdditionalDocEffective(id, uint64(block.timestamp)); } } /* ====================================================================== ماده ۵ — تاریخ تنفیذ وضعیت: هوشمند ---------------------------------------------------------------------- تاریخ نافذ شدن پیمان پس از امضا و مبادله پیمان، تسلیم ضمانت‌نامه انجام تعهدات، و تحقق شرایط مندرج در ماده است. در صورت عدم حصول این شرایط ظرف حداکثر نود روز، دو طرف می‌توانند درباره تاریخ نافذ شدن توافق کنند؛ و اگر پیمانکار توافق نکند، تضمین‌ها بازگردانده و تسویه می‌شود. ====================================================================== */ bool public agreementExchanged; // امضا و مبادله پیمان bool public performanceBondFiled; // تسلیم ضمانت‌نامه انجام تعهدات uint8 public conditionsTotal; // تعداد شرایط مندرج در ماده ۵ uint8 public conditionsMet; uint64 public exchangeDate; // مبدأ مهلت نود روزه uint64 public effectiveDate; // تاریخ تنفیذ event AgreementExchanged(uint64 at); event PerformanceBondFiled(uint64 at); event EffectiveConditionMet(uint8 index, uint8 metSoFar, uint8 total); event ContractEffective(uint64 at); event EffectiveWindowLapsed(uint64 deadline, uint8 metSoFar, uint8 total); event GuaranteesReturned(uint64 at, string note); function setEffectiveConditionsCount(uint8 n) external onlyGovernor { conditionsTotal = n; } function markAgreementExchanged() external onlyGovernor { require(!agreementExchanged, "already exchanged"); agreementExchanged = true; exchangeDate = uint64(block.timestamp); emit AgreementExchanged(exchangeDate); } function markPerformanceBondFiled() external onlyEmployer { require(!performanceBondFiled, "already filed"); performanceBondFiled = true; emit PerformanceBondFiled(uint64(block.timestamp)); } function meetEffectiveCondition(uint8 index) external onlyEmployer whenActive { require(conditionsMet < conditionsTotal, "all conditions met"); conditionsMet += 1; emit EffectiveConditionMet(index, conditionsMet, conditionsTotal); if (agreementExchanged && performanceBondFiled && conditionsMet >= conditionsTotal) { effectiveDate = uint64(block.timestamp); emit ContractEffective(effectiveDate); } } /// @notice اعلام انقضای مهلت نود روزه ماده ۵ — صرفاً اعلان، بدون تصمیم function flagEffectiveWindowLapsed() external whenActive { require(exchangeDate != 0 && effectiveDate == 0, "not applicable"); uint64 deadline = exchangeDate + uint64(EFFECTIVE_WINDOW_UNITS) * timeUnit; require(block.timestamp > deadline, "window still open"); emit EffectiveWindowLapsed(deadline, conditionsMet, conditionsTotal); } /// @notice بازگرداندن تضمین‌ها در صورت عدم توافق پیمانکار — ماده ۵، بند پایانی function returnGuarantees(string calldata note) external onlyEmployer { require(effectiveDate == 0, "contract already effective"); performanceBondFiled = false; emit GuaranteesReturned(uint64(block.timestamp), note); } /* ====================================================================== ماده ۶ — تاریخ شروع کار وضعیت: هوشمند ---------------------------------------------------------------------- کارفرما حداکثر ظرف سی روز پس از تاریخ تنفیذ، شروع کار را ابلاغ می‌کند. «در غیر این صورت، پیمانکار در پایان مهلت سی روز، کار را شروع می‌نماید.» این جمله یک قاعده زمانی قطعی است و هیچ قضاوتی نمی‌طلبد؛ از این‌رو شاخص‌ترین ماده قابل اتوماسیون کامل در این موافقت‌نامه است. ====================================================================== */ uint64 public workStartDate; bool public startedByNotice; // آیا با ابلاغ کارفرما آغاز شد یا با انقضای مهلت event StartOfWorkNotified(uint64 at, address by); event StartOfWorkByLapse(uint64 at, uint64 deadline); /// @notice ابلاغ شروع کار توسط کارفرما در مهلت سی روزه function notifyStartOfWork() external onlyEmployer whenActive whenEffective { require(workStartDate == 0, "already started"); require(block.timestamp <= effectiveDate + uint64(START_NOTICE_UNITS) * timeUnit, "notice window closed"); workStartDate = uint64(block.timestamp); startedByNotice = true; emit StartOfWorkNotified(workStartDate, msg.sender); } /// @notice شروع خودکار کار با انقضای مهلت سی روزه — بدون هیچ مداخله انسانی function startWorkByLapse() external whenActive whenEffective { require(workStartDate == 0, "already started"); uint64 deadline = effectiveDate + uint64(START_NOTICE_UNITS) * timeUnit; require(block.timestamp > deadline, "window still open"); workStartDate = deadline; // مطابق متن ماده: «در پایان مهلت سی روز» startedByNotice = false; emit StartOfWorkByLapse(workStartDate, deadline); } /* ====================================================================== ماده ۷ — مدت پیمان وضعیت: هوشمند ---------------------------------------------------------------------- مدت تکمیل کار و تحویل موقت از زمان شروع کار، برابر عدد مندرج در ماده. جزئیات مقاطع زمانی قسمت‌های اصلی کار در پیوست ۱۴ مشخص شده است. ====================================================================== */ uint32 public durationUnits; // مدت پیمان بر حسب واحد زمان struct Milestone { string title; uint32 offsetUnits; uint16 weightBps; uint64 reachedAt; } uint256 public milestoneCount; mapping(uint256 => Milestone) public milestones; event DurationSet(uint32 units); event MilestoneRegistered(uint256 indexed id, string title, uint32 offsetUnits, uint16 weightBps); event MilestoneReached(uint256 indexed id, uint64 at, bool onTime); function setDuration(uint32 units) external onlyGovernor { durationUnits = units; emit DurationSet(units); } /// @notice ثبت مقاطع زمانی قسمت‌های اصلی کار موضوع پیوست ۱۴ function registerMilestone(string calldata title, uint32 offsetUnits, uint16 weightBps) external onlyGovernor returns (uint256 id) { id = ++milestoneCount; milestones[id] = Milestone(title, offsetUnits, weightBps, 0); emit MilestoneRegistered(id, title, offsetUnits, weightBps); } /// @notice تاریخ برنامه‌ای تکمیل کار = تاریخ شروع + مدت پیمان function scheduledCompletion() public view returns (uint64) { if (workStartDate == 0) return 0; return workStartDate + uint64(durationUnits) * timeUnit; } function reachMilestone(uint256 id) external whenActive { require(_is(consultant) || _is(employer), "not authorized"); Milestone storage m = milestones[id]; require(m.reachedAt == 0, "already reached"); m.reachedAt = uint64(block.timestamp); bool onTime = block.timestamp <= workStartDate + uint64(m.offsetUnits) * timeUnit; emit MilestoneReached(id, m.reachedAt, onTime); } /* ====================================================================== ماده ۱۰ — حد مسئولیت مالی پیمانکار وضعیت: هوشمند ---------------------------------------------------------------------- حداکثر مسئولیت پیمانکار در برابر کارفرما معادل درصد مشخصی از مبلغ پیمان است. این یک سقف عددی قطعی است و پایش آن هیچ قضاوتی نمی‌طلبد. ====================================================================== */ uint16 public maxLiabilityBps; // درصد مندرج در ماده ۱۰، پایه ده هزار uint256 public liabilityCharged; // مجموع مسئولیت اعمال‌شده تا کنون event MaxLiabilitySet(uint16 bps); event LiabilityCharged(uint256 amount, uint256 total, uint256 cap); event LiabilityCapReached(uint256 cap); function setMaxLiability(uint16 bps) external onlyGovernor { maxLiabilityBps = bps; emit MaxLiabilitySet(bps); } function liabilityCap() public view returns (uint256) { return totalContractAmount() * uint256(maxLiabilityBps) / 10000; } /// @dev هر بار که مسئولیتی بر پیمانکار بار می‌شود از این مسیر می‌گذرد و سقف بررسی می‌گردد function _chargeLiability(uint256 amount) internal { uint256 cap = liabilityCap(); require(liabilityCharged + amount <= cap, "exceeds maximum liability"); liabilityCharged += amount; emit LiabilityCharged(amount, liabilityCharged, cap); if (liabilityCharged >= cap) emit LiabilityCapReached(cap); } /* ====================================================================== ماده ۱۱ — مشاور کارفرما وضعیت: هوشمند ---------------------------------------------------------------------- نام و نشانی مشاور کارفرما و حدود اختیارات او طبق پیوست ۸. ====================================================================== */ string public consultantLegalAddress; bytes32 public consultantAuthorityHash; // هش پیوست ۸ event ConsultantAssigned(address indexed who, string legalAddress, bytes32 authorityHash); function assignConsultant(address who, string calldata legalAddress, bytes32 authorityHash) external onlyEmployer { consultant = who; consultantLegalAddress = legalAddress; consultantAuthorityHash = authorityHash; emit ConsultantAssigned(who, legalAddress, authorityHash); } /* ====================================================================== ماده ۱۶ — تغییر نشانی طرفین پیمان وضعیت: هوشمند ---------------------------------------------------------------------- «تا وقتی که نشانی جدید به طرف دیگر اعلام نشده است، کلیه نامه‌ها، اوراق و مکاتبات به نشانی قانونی ارسال و تمام آنها ابلاغ‌شده تلقی خواهد شد.» این قاعده ابلاغ فرضی، دقیقاً قابل کدنویسی است. ====================================================================== */ struct PartyAddress { string legalAddress; uint64 notifiedAt; } mapping(address => PartyAddress) public partyAddress; uint256 public noticeCount; struct Notice { bytes32 docHash; string subject; address from; address to; string servedAddress; uint64 at; } mapping(uint256 => Notice) public notices; event AddressChangeNotified(address indexed party, string newAddress, uint64 at); event NoticeServed(uint256 indexed id, address indexed from, address indexed to, string servedAddress, uint64 at); function notifyAddressChange(string calldata newAddress) external whenActive { require(_is(employer) || _is(contractor), "not a party"); partyAddress[msg.sender] = PartyAddress(newAddress, uint64(block.timestamp)); emit AddressChangeNotified(msg.sender, newAddress, uint64(block.timestamp)); } /// @notice ثبت ابلاغ؛ نشانی مورد استفاده همان نشانی ثبت‌شده فعلی طرف مقابل است function serveNotice(address to, bytes32 docHash, string calldata subject) external whenActive returns (uint256 id) { require(_is(employer) || _is(contractor), "not a party"); string memory addr = partyAddress[to].legalAddress; id = ++noticeCount; notices[id] = Notice(docHash, subject, msg.sender, to, addr, uint64(block.timestamp)); emit NoticeServed(id, msg.sender, to, addr, uint64(block.timestamp)); } /* ====================================================================== ماده ۱۸ — تعداد نسخ، امضای طرفین و تاریخ وضعیت: هوشمند ---------------------------------------------------------------------- ثبت مشخصات انعقاد: تعداد ماده، تاریخ، شهر، تعداد نسخه با اعتبار واحد، و امضای طرفین با اطلاع کامل از مفاد. ====================================================================== */ struct Execution { uint8 articles; string city; uint8 copies; uint64 signedAt; bool employerSigned; bool contractorSigned; } Execution public execution; event AgreementSigned(address indexed by, uint64 at); event ExecutionCompleted(uint8 articles, string city, uint8 copies, uint64 at); function setExecutionTerms(uint8 articles, string calldata city, uint8 copies) external onlyGovernor { execution.articles = articles; execution.city = city; execution.copies = copies; } function signAgreement() external { if (_is(employer)) execution.employerSigned = true; else if (_is(contractor)) execution.contractorSigned = true; else revert NotAuthorized(); emit AgreementSigned(msg.sender, uint64(block.timestamp)); if (execution.employerSigned && execution.contractorSigned && execution.signedAt == 0) { execution.signedAt = uint64(block.timestamp); emit ExecutionCompleted(execution.articles, execution.city, execution.copies, execution.signedAt); } } /* ====================================================================== سازوکار گیت تأیید انسانی — مورد استفاده مواد نیمه‌هوشمند این سازوکار جزء تکمیلی پیاده‌سازی است و در متن موافقت‌نامه نیامده؛ اما تحقق الزام «توافق کتبی و قبلی طرفین» ماده ۱۴ و مداخله انسانی مورد نیاز مواد ۳، ۴، ۸ و ۹ را ممکن می‌سازد. ====================================================================== */ struct Gate { uint8 articleNo; string label; uint8 need; uint8 got; uint64 openedAt; uint64 executableAt; uint8 state; } // وضعیت: ۱ در انتظار، ۲ تأییدشده، ۳ مردود، ۴ معترض‌عنه، ۵ اجرا شده uint256 public gateCount; mapping(uint256 => Gate) public gates; mapping(uint256 => mapping(address => bool)) public gateSigned; event GateOpened(uint256 indexed id, uint8 indexed articleNo, string label, uint8 need, uint64 executableAt); event GateSignedBy(uint256 indexed id, address indexed by, uint8 got, uint8 need); event GateObjected(uint256 indexed id, address indexed by, string reason); event GateResolved(uint256 indexed id, bool allowed, address by); function _openGate(uint8 articleNo, string memory label, uint8 need, uint32 lockUnits) internal returns (uint256 id) { id = ++gateCount; gates[id] = Gate(articleNo, label, need, 0, uint64(block.timestamp), uint64(block.timestamp) + uint64(lockUnits) * timeUnit, 1); emit GateOpened(id, articleNo, label, need, gates[id].executableAt); } function signGate(uint256 id) external whenActive { if (!_isApprover(msg.sender)) revert NotAuthorized(); Gate storage g = gates[id]; require(g.state == 1, "gate not pending"); require(!gateSigned[id][msg.sender], "already signed"); gateSigned[id][msg.sender] = true; g.got += 1; emit GateSignedBy(id, msg.sender, g.got, g.need); if (g.got >= g.need) g.state = 2; } /// @notice ثبت اعتراض توسط هر یک از طرفین — اجرای خودکار را قفل می‌کند function objectToGate(uint256 id, string calldata reason) external { require(_is(employer) || _is(contractor), "not a party"); Gate storage g = gates[id]; require(g.state == 1 || g.state == 2, "not objectable"); g.state = 4; emit GateObjected(id, msg.sender, reason); } /// @notice ماده ۱۷ — تصمیم داور مرضی‌الطرفین، تنها راه خروج از وضعیت اعتراض function resolveObjection(uint256 id, bool allow) external { if (!_is(arbitrator)) revert NotAuthorized(); Gate storage g = gates[id]; require(g.state == 4, "no objection"); g.state = allow ? 2 : 3; emit GateResolved(id, allow, msg.sender); } function _consumeGate(uint256 id, uint8 articleNo) internal { Gate storage g = gates[id]; if (g.state != 2) revert GateNotReady(); if (block.timestamp < g.executableAt) revert TimelockActive(g.executableAt); require(g.articleNo == articleNo, "article mismatch"); g.state = 5; } /* ====================================================================== ماده ۳ — مبلغ پیمان وضعیت: نیمه‌هوشمند ---------------------------------------------------------------------- بند ۳-۱ مبلغ ریالی و ارزی؛ تفکیک طبق پیوست ۱. بند ۳-۲ مبلغ مقطوع است جز در چهار حالت: تغییر کار، کارهای فهرست‌بهایی، تعدیل، و مبالغ مشروط. بند ۳-۳ شامل لوازم یدکی دوره‌های پیش راه‌اندازی و بهره‌برداری. محاسبه و انباشت قابل خودکارسازی است، اما اعمال هر تغییر نیازمند تأیید انسانی است زیرا مبنای آن سند بیرونی است. ====================================================================== */ uint256 public rialAmount; // بند ۳-۱ uint256 public currencyAmount; // بند ۳-۱ uint256 public sparePartsYears; // بند ۳-۳ enum PriceChangeReason { ChangeOrder, UnitPriceItems, Adjustment, ProvisionalSum } struct PriceChange { PriceChangeReason reason; int256 rialDelta; int256 currencyDelta; bytes32 evidenceHash; bool executed; } uint256 public priceChangeCount; mapping(uint256 => PriceChange) public priceChanges; event ContractAmountSet(uint256 rial, uint256 currency, uint256 spareYears); event PriceChangeProposed(uint256 indexed id, PriceChangeReason reason, int256 rialDelta, int256 currencyDelta); event PriceChangeExecuted(uint256 indexed id, uint256 newRial, uint256 newCurrency); function setContractAmount(uint256 rial, uint256 currency, uint256 spareYears) external onlyGovernor { rialAmount = rial; currencyAmount = currency; sparePartsYears = spareYears; emit ContractAmountSet(rial, currency, spareYears); } function totalContractAmount() public view returns (uint256) { return rialAmount + currencyAmount; } function proposePriceChange(PriceChangeReason reason, int256 rialDelta, int256 currencyDelta, bytes32 evidenceHash) external onlyEmployer whenActive returns (uint256 id, uint256 gateId) { id = ++priceChangeCount; priceChanges[id] = PriceChange(reason, rialDelta, currencyDelta, evidenceHash, false); emit PriceChangeProposed(id, reason, rialDelta, currencyDelta); gateId = _openGate(3, "Article 3 - contract amount change", 2, 48); } function executePriceChange(uint256 id, uint256 gateId) external whenActive { if (!_isApprover(msg.sender)) revert NotAuthorized(); _consumeGate(gateId, 3); PriceChange storage p = priceChanges[id]; require(!p.executed, "already executed"); rialAmount = p.rialDelta >= 0 ? rialAmount + uint256(p.rialDelta) : rialAmount - uint256(-p.rialDelta); currencyAmount = p.currencyDelta >= 0 ? currencyAmount + uint256(p.currencyDelta) : currencyAmount - uint256(-p.currencyDelta); p.executed = true; emit PriceChangeExecuted(id, rialAmount, currencyAmount); } /* ====================================================================== ماده ۴ — نحوه پرداخت وضعیت: نیمه‌هوشمند ---------------------------------------------------------------------- بند ۴-۱ پرداخت طبق پیوست ۵؛ بند ۴-۲ گشایش اعتبار ارزی طبق پیوست ۷. پرریسک‌ترین ماده قابل هوشمندسازی پس از ماده ۸، زیرا خطای آن هم‌زمان نقدینگی، کیفیت و اعتماد را فعال می‌کند. سه امضا و بلندترین قفل زمانی. ====================================================================== */ uint256 public escrowBalance; uint256 public totalPaid; struct PaymentRequest { uint256 amount; bytes32 evidenceHash; uint64 requestedAt; uint64 paidAt; } uint256 public paymentCount; mapping(uint256 => PaymentRequest) public payments; event EscrowFunded(address indexed by, uint256 amount, uint256 balance); event PaymentRequested(uint256 indexed id, uint256 amount, bytes32 evidenceHash, uint256 gateId); event PaymentExecuted(uint256 indexed id, uint256 amount, uint64 at); event PaymentHalted(uint256 indexed id, string reason); function fundEscrow() external payable { require(_is(employer) || msg.sender == governor, "not authorized"); escrowBalance += msg.value; emit EscrowFunded(msg.sender, msg.value, escrowBalance); } function requestPayment(uint256 amount, bytes32 evidenceHash) external onlyContractor whenActive whenEffective returns (uint256 id, uint256 gateId) { id = ++paymentCount; payments[id] = PaymentRequest(amount, evidenceHash, uint64(block.timestamp), 0); gateId = _openGate(4, "Article 4 - payment to contractor", 3, 72); emit PaymentRequested(id, amount, evidenceHash, gateId); } function executePayment(uint256 id, uint256 gateId) external whenActive { if (!_isApprover(msg.sender)) revert NotAuthorized(); PaymentRequest storage p = payments[id]; if (p.paidAt != 0) { emit PaymentHalted(id, "already paid"); revert GateNotReady(); } if (p.amount > escrowBalance) { emit PaymentHalted(id, "insufficient escrow"); revert GateNotReady(); } _consumeGate(gateId, 4); escrowBalance -= p.amount; totalPaid += p.amount; p.paidAt = uint64(block.timestamp); (bool ok, ) = payable(contractor == address(0) ? governor : contractor).call{value: p.amount}(""); require(ok, "transfer failed"); emit PaymentExecuted(id, p.amount, p.paidAt); } /* ====================================================================== ماده ۸ — خسارت تأخیر در تکمیل به‌موقع کار وضعیت: نیمه‌هوشمند ---------------------------------------------------------------------- «هرگاه به دلیل قصور پیمانکار، در اتمام طبق برنامه کار یا قسمت‌های اصلی آن تأخیر پیش آید، خسارت به میزان تعیین‌شده در شرایط خصوصی وصول می‌شود. مجموع مبلغ این تأخیرها از درصد معینی از مبلغ پیمان بیشتر نمی‌شود.» محاسبه روزهای تأخیر و مبلغ، ریاضی محض و قابل اتوماسیون است؛ اما احراز «قصور پیمانکار» قضاوت انسانی است. از این‌رو محاسبه از اعمال جدا شد. ====================================================================== */ uint256 public ldPerUnit; // میزان مندرج در شرایط خصوصی uint16 public ldCapBps; // درصد مندرج در ماده ۸ uint256 public ldApplied; event DelayTermsSet(uint256 perUnit, uint16 capBps); event DelayComputed(uint32 unitsLate, uint256 computed, uint256 cap); event DelayDamagesApplied(uint256 amount, uint256 total, uint256 gateId); function setDelayTerms(uint256 perUnit, uint16 capBps) external onlyGovernor { ldPerUnit = perUnit; ldCapBps = capBps; emit DelayTermsSet(perUnit, capBps); } /// @notice محاسبه — سطح هوشمند، بدون هیچ اثر مالی function computeDelayDamages() public view returns (uint32 unitsLate, uint256 computed) { uint64 due = scheduledCompletion(); if (due == 0 || block.timestamp <= due) return (0, 0); unitsLate = uint32((block.timestamp - due) / timeUnit); computed = uint256(unitsLate) * ldPerUnit; uint256 cap = totalContractAmount() * uint256(ldCapBps) / 10000; if (computed > cap) computed = cap; } function flagDelay() external whenActive { (uint32 late, uint256 computed) = computeDelayDamages(); require(late > 0, "not late"); emit DelayComputed(late, computed, totalContractAmount() * uint256(ldCapBps) / 10000); } function openDelayGate() external whenActive returns (uint256 gateId) { require(_is(employer) || _is(consultant), "not authorized"); (uint32 late, ) = computeDelayDamages(); require(late > 0, "not late"); gateId = _openGate(8, "Article 8 - liquidated damages", 3, 72); } /// @notice اعمال — سطح نیمه‌هوشمند، پس از سه امضا و قفل زمانی function applyDelayDamages(uint256 gateId) external whenActive { if (!_isApprover(msg.sender)) revert NotAuthorized(); _consumeGate(gateId, 8); (, uint256 computed) = computeDelayDamages(); require(computed > ldApplied, "no new damages"); uint256 amount = computed - ldApplied; ldApplied = computed; _chargeLiability(amount); // ماده ۱۰ — بررسی سقف مسئولیت emit DelayDamagesApplied(amount, ldApplied, gateId); } /* ====================================================================== ماده ۹ — هزینه تسریع کار وضعیت: نیمه‌هوشمند ---------------------------------------------------------------------- «هرگاه پیش از سپری شدن مدت تکمیل کار، پیمانکار کارهای موضوع پیمان را تکمیل کند، به ازای هر روز تسریع، هزینه تسریع به پیمانکار پرداخت می‌شود.» محاسبه قطعی است، اما احراز «تکمیل کار» و پرداخت وجه، تأیید می‌طلبد. ====================================================================== */ uint256 public accelerationPerUnit; // میزان مندرج در شرایط خصوصی uint64 public actualCompletion; uint256 public accelerationPaid; event AccelerationTermsSet(uint256 perUnit); event CompletionRecorded(uint64 at, uint32 unitsEarly); event AccelerationPaid(uint256 amount, uint256 gateId); function setAccelerationTerms(uint256 perUnit) external onlyGovernor { accelerationPerUnit = perUnit; emit AccelerationTermsSet(perUnit); } function recordCompletion() external whenActive { require(_is(consultant) || _is(employer), "not authorized"); require(actualCompletion == 0, "already recorded"); actualCompletion = uint64(block.timestamp); (uint32 early, ) = computeAccelerationBonus(); emit CompletionRecorded(actualCompletion, early); } function computeAccelerationBonus() public view returns (uint32 unitsEarly, uint256 bonus) { uint64 due = scheduledCompletion(); uint64 done = actualCompletion == 0 ? uint64(block.timestamp) : actualCompletion; if (due == 0 || done >= due) return (0, 0); unitsEarly = uint32((due - done) / timeUnit); bonus = uint256(unitsEarly) * accelerationPerUnit; } function openAccelerationGate() external onlyContractor whenActive returns (uint256 gateId) { (uint32 early, ) = computeAccelerationBonus(); require(early > 0 && actualCompletion != 0, "not eligible"); gateId = _openGate(9, "Article 9 - acceleration bonus", 1, 24); } function payAcceleration(uint256 gateId) external whenActive { if (!_isApprover(msg.sender)) revert NotAuthorized(); _consumeGate(gateId, 9); (, uint256 bonus) = computeAccelerationBonus(); require(bonus > accelerationPaid, "already paid"); uint256 amount = bonus - accelerationPaid; require(amount <= escrowBalance, "insufficient escrow"); accelerationPaid = bonus; escrowBalance -= amount; totalPaid += amount; (bool ok, ) = payable(contractor == address(0) ? governor : contractor).call{value: amount}(""); require(ok, "transfer failed"); emit AccelerationPaid(amount, gateId); } /* ====================================================================== ماده ۱۴ — تغییر و اصلاح وضعیت: نیمه‌هوشمند ---------------------------------------------------------------------- «هرگونه تغییر در مفاد پیمان، صرفاً با توافق کتبی و قبلی طرفین امکان‌پذیر است.» الزام دو امضا، خودِ متن ماده است و نه افزوده مدل. ====================================================================== */ struct Amendment { bytes32 docHash; string description; bool employerSigned; bool contractorSigned; bool effective; uint64 effectiveAt; } uint256 public amendmentCount; mapping(uint256 => Amendment) public amendments; event AmendmentProposed(uint256 indexed id, bytes32 docHash, string description, address by); event AmendmentSigned(uint256 indexed id, address indexed by); event AmendmentEffective(uint256 indexed id, uint64 at); function proposeAmendment(bytes32 docHash, string calldata description) external whenActive returns (uint256 id) { require(_is(employer) || _is(contractor), "not a party"); id = ++amendmentCount; amendments[id] = Amendment(docHash, description, _is(employer), _is(contractor), false, 0); emit AmendmentProposed(id, docHash, description, msg.sender); } /// @notice تا امضای هر دو طرف، اصلاحیه هیچ اثری بر مفاد پیمان ندارد function signAmendment(uint256 id) external whenActive { Amendment storage a = amendments[id]; require(!a.effective, "already effective"); if (_is(employer)) a.employerSigned = true; else if (_is(contractor)) a.contractorSigned = true; else revert NotAuthorized(); emit AmendmentSigned(id, msg.sender); if (a.employerSigned && a.contractorSigned) { a.effective = true; a.effectiveAt = uint64(block.timestamp); emit AmendmentEffective(id, a.effectiveAt); } } /* ====================================================================== مواد سنتی — ۱، ۱۲، ۱۳، ۱۵ و ۱۷ ---------------------------------------------------------------------- این پنج ماده وارد منطق اجرایی نمی‌شوند. برای هیچ‌یک کد ساختگی تولید نشده است. تنها کاری که سامانه انجام می‌دهد، ثبت اثر انگشت سند تصمیم انسانی است تا حسابرسی‌پذیری حفظ شود. ====================================================================== */ struct TraditionalRecord { uint8 articleNo; bytes32 docHash; string note; address filedBy; uint64 at; } uint256 public traditionalCount; mapping(uint256 => TraditionalRecord) public traditionalRecords; event TraditionalDecisionAnchored(uint256 indexed id, uint8 indexed articleNo, bytes32 docHash, address by); function anchorTraditionalDecision(uint8 articleNo, bytes32 docHash, string calldata note) external returns (uint256 id) { require(articleNo == 1 || articleNo == 12 || articleNo == 13 || articleNo == 15 || articleNo == 17, "article is not traditional"); require(_is(employer) || _is(contractor) || _is(consultant) || _is(arbitrator), "not authorized"); id = ++traditionalCount; traditionalRecords[id] = TraditionalRecord(articleNo, docHash, note, msg.sender, uint64(block.timestamp)); emit TraditionalDecisionAnchored(id, articleNo, docHash, msg.sender); } /* ====================================================================== شاخص ریسک باقیمانده — تحقق کدیِ تابع هدف پژوهش بار ریسک هر ماده از خروجی مدل مفهومی گرفته شده و در کد ثبت می‌شود. ====================================================================== */ struct ArticleModel { uint16 sriE4; uint16 rbiE4; uint16 autoE4; uint8 level; } // ۲ هوشمند، ۱ نیمه، ۰ سنتی mapping(uint8 => ArticleModel) public articleModel; event ArticleModelSet(uint8 indexed articleNo, uint16 sriE4, uint16 rbiE4, uint16 autoE4, uint8 level); function setArticleModel(uint8 articleNo, uint16 sriE4, uint16 rbiE4, uint16 autoE4, uint8 level) external onlyGovernor { require(articleNo >= 1 && articleNo <= 18, "article out of range"); articleModel[articleNo] = ArticleModel(sriE4, rbiE4, autoE4, level); emit ArticleModelSet(articleNo, sriE4, rbiE4, autoE4, level); } /// @notice میانگین بار ریسک ضربدر یک منهای درجه اتوماسیون، روی مواد غیرسنتی function residualRiskIndex() external view returns (uint256 idxE4) { uint256 acc; uint256 n; for (uint8 i = 1; i <= 18; i++) { ArticleModel memory m = articleModel[i]; if (m.sriE4 == 0 || m.level == 0) continue; acc += uint256(m.rbiE4) * (10000 - uint256(m.autoE4)) / 10000; n++; } idxE4 = n == 0 ? 0 : acc / n; } receive() external payable { escrowBalance += msg.value; emit EscrowFunded(msg.sender, msg.value, escrowBalance); } }