الورقة البيضاء · 07
الخطافات والوحدات المرجعية
كيف تُضاف الوظائف إلى رمز مطابق دون المساس بالمعيار: عقد الخطاف، والخطافات المرجعية، والامتدادات المرافقة، وكيفية كتابة خطافك الخاص واختباره وتسجيله.
الوحدات عروض توضيحية، وليست المعيار
العلاقة بين A-REX Core ووحداتها هي العلاقة نفسها بين ERC-3643 ووحدات الامتثال في T-REX: الواجهة التي تتصل بها الوحدات موحَّدة؛ أما الوحدات فليست كذلك. والرمز المطابق الذي لا خطاف له رمز A-REX صالح. والخطاف الذي يكتبه أي شخص متوافق مع كل رمز مطابق. وهذا ما يجعل المنظومة تنمو دون أن يتغير المعيار.
graph LR
T["ARexToken<br/>رمز مطابق"] -->|"check / onExecuted / canSend / canReceive"| H1[EligibilityHook]
T --> H2[ExpiryHook]
T --> H3[AttestationHook]
T --> H4["خطافك الخاص"]
H3 --> A[ARexAttestations]عقد الخطاف
interface IARexHook is IERC165 {
function hookFlags() external view returns (uint8); // CHECK=1 · EXECUTED=2 · ELIGIBILITY=4
function check(uint8 op, address operator, address from, address to, uint256 id, uint256 amount, bytes calldata data)
external view returns (bytes32 code); // 0 = allowed
function onExecuted(uint8 op, address operator, address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
function canSend(address account) external view returns (bool);
function canReceive(address account) external view returns (bool);
}
| الاستدعاء | متى | ما يجب عليه |
|---|---|---|
check | قبل تغيّر الأرصدة، لكل معرّف متأثر، بترتيب تسجيل الخطافات، مع التوقف عند أول قيمة غير صفرية | أن تكون دالة عرض؛ وألا تتراجع أبدًا لأسباب تتعلق بالسياسة |
onExecuted | بعد تغيّر الأرصدة، لكل معرّف، بالترتيب | ألا تستدعي دوال الرمز التي تغيّر الأرصدة (فالرمز مقفل) |
canSend / canReceive | عند كل تحويل ولدوال عرض ERC-7943 في الرمز | أن تكونا دالتَي عرض |
تكون op واحدة من OP_MINT (1) وOP_TRANSFER (2) وOP_BURN (3) وOP_FORCED_TRANSFER (4) وOP_TRANSFORM (5). وفي التحويل التصنيعي تصل المدخلات بـ to == 0 والمخرجات بـ from == 0. وdata هي بيانات تحويل ERC-1155، وهي abi.encode(processRef) في عمليات التحويل التصنيعي، وفارغة في التحويلات القسرية.
الرمز المستدعي هو msg.sender. والخطاف الذي يحتاج إلى الدفعة يقرأ IARexBatch(msg.sender).batchOf(id).
الخطافات المرجعية
EligibilityHook — السماح والحظر والعقوبات
يوضّح HOOK_ELIGIBILITY. ويحتفظ بقائمة سماح اختيارية وقائمة حظر وقائمة عقوبات؛ وتُرجع canSend/canReceive اقترانهما المنطقي؛ وتُرجع check القيمة SANCTIONED أو SENDER_INELIGIBLE أو RECEIVER_INELIGIBLE كي تتمكن واجهات المستخدم من تفسير الرفض. والتحويلات القسرية مسموحة دائمًا: فحجز السلع من حساب خاضع للعقوبات هو جوهر الإنفاذ. وفي بيئة الإنتاج تُغذَّى القوائم من سجل هوية (ONCHAINID أو EAS أو بيانات الاعتماد القابلة للتحقق) أو من أوراكل عقوبات؛ ولا يهتم الخطاف بأيهما.
ExpiryHook — قابلية التلف دون وحدة
بلا حالة. يقرأ expiresAt وproducedAt من الدفعة. يمنع سك الدفعات المنتهية الصلاحية وتحويلها (BATCH_EXPIRED) لكنه لا يمنع الحرق (الشطب) ولا التحويلات القسرية (الإنفاذ). ويمنع اختياريًا تحويل الدفعات التي لم تُنتَج بعد (BATCH_NOT_PRODUCED) — فالمنصة التي تموّل ما قبل الحصاد تضبط العلم للسماح بها؛ والسوق الفورية تضبطه لمنعها. ويغطي هذا الخطاف وحده جزء المنع من قابلية التلف؛ أما منحنيات التدهور فتصبح دالة عرض مساعدة أو تقييمًا خارج السلسلة.
AttestationHook — تقييد تنظيمي لكل سلعة
يربط كود السلعة بموضوع إفادة مطلوب. وقبل التحويل يقرأ الدفعة، ويبحث عن المتطلب، ويسأل سجل الإفادات isAttested(id, topic)، ويُرجع ATTESTATION_MISSING في غير ذلك. فعبارات "يحتاج البن إلى مرجع عناية واجبة وفق EUDR"، و"تحتاج الدفعات الموسومة بأنها عضوية إلى شهادة عضوية سارية"، و"تحتاج الماشية إلى شهادة صحية" يتحقق كل منها باستدعاء واحد إلى setRequirement.
PerishabilityHook — نماذج التدهور
ضبط التدهور لكل كود سلعة مع تجاوز لكل دفعة؛ والنماذج NONE وLINEAR (حتى حد أدنى عند انتهاء الصلاحية) وEXPONENTIAL (عمر النصف) وSTEP (انخفاض كل فترة زمنية). دوال العرض: valueBps وsecondsRemaining وisExpired. ويمنع سك الدفعات المنتهية الصلاحية وتحويلها (BATCH_EXPIRED) وتحويل الدفعات التي تساوي قيمتها المتبقية صفرًا (PERISH:NO_VALUE)؛ ولا يمنع الحرق ولا الإنفاذ أبدًا.
SeasonalHook — نوافذ الحصاد والتداول
نوافذ مطلقة [start, end] لكل سلعة وعملية (16 نافذة كحد أقصى لكل مفتاح). والسلعة/العملية التي لا نوافذ لها غير مقيّدة. الاستخدام المعتاد: تقييد OP_MINT بنافذة الحصاد، وOP_TRANSFER بنافذة التداول؛ وSEASON_CLOSED في غير ذلك.
DeliveryHook — الحيازة والتسوية
يجدول المُرسِل تسليم كمية amount إلى مستلم عبر ناقل؛ وتُقفل الكمية (بعدّاد O(1)، فلا تمرّ check على التاريخ أبدًا). ويسجّل الناقل والمستشعرات الاستلام والعبور وقراءات درجة الحرارة (مع علم المخالفة)؛ ويؤكد المستلم التسليم؛ وتُحال النزاعات إلى محكّم. والتسوية هي تحويل ERC-1155 نفسه: من المُرسِل إلى المستلم، بالمقدار الدقيق، مع data = abi.encode(deliveryId)؛ ولا تسمح به check إلا حين يكون التسليم DELIVERED، وتضع onExecuted علامة SETTLED عليه وتفتح القفل. وتظل الكمية الحرة (غير المقفلة) قابلة للحركة.
TraceabilityHook — سجل أحداث بصيغة EPCIS
يراقب كل عملية على الرمز ويصنّفها وفق خطوات الأعمال في CBV (commissioning وshipping وdestroying وtransforming) وحالات التصرف (active وin_transit وdestroyed وseized)؛ ويسجّل RECORDER_ROLE أحداث التتبع الحرجة غير المتعلقة بالرمز (التبريد، والتعبئة، والاستلام) مع readPoint وbizLocation. وله وضعان: الإصدار فقط (نحو +17 k غاز لكل تحويل؛ ويعيد المفهرس بناء السجل) أو الحفظ (نحو +184 k؛ وتجيب getEvents على السلسلة، مع ترقيم الصفحات).
الامتدادات المرافقة
ثلاثة جوانب لم تكن تحتاج قط إلى أن تكون قواعد تحويل؛ فهي عقود عادية تقرأ الإفادات أو تكتبها:
| الامتداد | ما يفعله |
|---|---|
CommodityPriceOracle | يرسل المُبلِّغون الأسعار لكل سلعة؛ وتُرقَّم الجولات وتُحسم عند اكتمال النصاب باعتماد الوسيط، أو يدويًا متى بُلغ الحد الأدنى؛ latestAnswer، isStale |
CarbonImmobilizationWrapper | يسجّل الأرصدة المحتجزة في سجل (السجل، ونطاق الأرقام التسلسلية، وسنة الإصدار، والأطنان، والإثبات)، ويربط الأطنان بالدفعات، ويعكس السحب من التداول والإفراج، ويكتب الإفادة AREX:CARBON:IMMOBILIZED؛ ولا يسك الأرصدة أبدًا |
ParametricInsurance | صندوق ERC-20؛ ووثيقة لكل دفعة مع موضوع محفّز؛ وتُصرف المطالبة عندما تُثبَت إفادة المحفّز قبل انتهاء الصلاحية |
كتابة خطافك الخاص
الخطاف أدناه هو contracts/examples/SeasonHook.sol؛ وهو يُترجَم في التكامل المستمر، ويختبره test/standard/ImplementationGuide.test.js على رمز. وللاستخدام الإنتاجي استخدم SeasonalHook الذي يدعم عدة نوافذ لكل سلعة ولكل عملية.
contract SeasonHook is ARexHookBase, Ownable {
mapping(bytes32 => uint64[2]) public window; // commodity → [open, close]
constructor(address owner_) Ownable(owner_) {}
function setWindow(bytes32 commodity, uint64 open, uint64 close) external onlyOwner {
window[commodity] = [open, close];
}
function hookFlags() public pure override returns (uint8) {
return ARexCodes.HOOK_CHECK;
}
function check(uint8 op, address, address, address, uint256 id, uint256, bytes calldata)
external
view
override
returns (bytes32)
{
if (op != ARexCodes.OP_TRANSFER) return ARexCodes.OK; // never block mints, burns or enforcement
IARexBatch.Batch memory b = IARexBatch(msg.sender).batchOf(id);
uint64[2] memory w = window[b.commodity];
if (w[1] == 0) return ARexCodes.OK; // no window configured
return (block.timestamp >= w[0] && block.timestamp <= w[1]) ? ARexCodes.OK : ARexCodes.SEASON_CLOSED;
}
}
قواعد عملية:
- أرجِع
OKللعمليةOP_FORCED_TRANSFERما لم يكن خطافك يحكم الإنفاذ بنفسه. - لا تمنع عمليات الحرق إلا إن كنت تقصد ذلك: فالحرق هو التسليم والشطب.
- أبقِ
checkرخيصة ومحدودة؛ فهي تعمل عند كل تحويل لكل معرّف. ولا تمرّ على التاريخ أبدًا. - حدّد لأكوادك فضاء أسماء:
ARexCodes.code("MYNS", "NAME"). - أبقِ الأعلام ثابتة؛ فالرمز يخزّنها مؤقتًا.
- ضع تغييرات الحالة في
onExecuted، لا فيcheckأبدًا.
التسجيل والترتيب
await token.registerHook(sanctionsHook); // evaluated first
await token.registerHook(expiryHook);
await token.registerHook(attestationHook); // evaluated last
await token.getHooks(); // ordered list
await token.removeHook(expiryHook); // order of the rest preserved
ضع القواعد الرخيصة ذات معدل الرفض المرتفع أولًا. وينبغي أن يوضع HOOK_ADMIN_ROLE خلف توقيع متعدد أو تأخير زمني: فمن يتحكم في المسار يتحكم في التحويلات الممكنة.
الاختبار
ينشر test/standard/helpers.js الحزمة المرجعية. ويسجّل RecordingHook كل استدعاء ويمكن توجيهه إلى الرفض بأي كود؛ ويحاول ReentrantHook إعادة دخول الرمز من onExecuted ويثبت فعالية القفل. ويغطي DomainHooks.test.js وExtensions.test.js الخطافات والمرافقات المنقولة؛ ويشغّل Invariants.test.js تسلسلًا عشوائيًا ببذرة محددة من العمليات ويتحقق من حفظ المعروض والحالة النهائية ومن أن canTransfer تتنبأ بكل نتيجة.
التشغيل البيني مع امتثال ERC-3643
تكشف وحدة امتثال ERC-3643 الدالة moduleCheck(from, to, value, compliance). والخطاف الذي يستدعيها للعملية OP_TRANSFER ويحوّل false إلى كود يجعل قيود الدول وسقوف الأرصدة والأقفال الزمنية المكتوبة لرموز الأوراق المالية قابلة لإعادة الاستخدام على رموز الدفعات. والعكس — أي إتاحة خطاف A-REX لرمز ERC-3643 — آلي بالقدر نفسه. ويتكامل Automated Compliance Engine من Chainlink عبر النمط نفسه: فيصبح محرك سياساته خطافًا واحدًا.