פיתוח חוזה חכם להבשלת אסימונים עם ביקורת

שגיאות בחוזים חכמים של token vesting מובילות להפסדים כספיים: עקב אי-דיוקים בחישובים, מוטבים מקבלים פחות כספים, ופרוטוקולים מאבדים אמון. אנחנו בונים חוזי vesting במפתח מלא—מארכיטקטורה ועד ביקורת והשקה, ומבטלים נקודות תורפה בכל שלב. הצוות שלנו מוסר את הפרויקט עם ניסיון עשיר ב-DeFi, ומבטיח חישובים מדויקים ופעולה אמינה ללא עיכובים, עם תמיכה מתמשכת.

שירותי פיתוח בלוקצ'יין

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1481
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1335
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1034
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1293
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    738
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1031

פיתוח חוזה חכם להבשלת טוקנים (Token Vesting)

שגיאה באובדן דיוק עלתה לפרוטוקול DeFi אחד 200,000 דולר: עקב סדר שגוי של כפל/חילוק, מקבלי ההטבות קיבלו 15% פחות טוקנים. מקרים כאלה אינם נדירים. חוזה הבשלה נראה פשוט, אך הוא מרכז בתוכו נקודות תורפה שמובילות להפסדים כספיים. לפני כתיבת הקוד, יש להגדיר בבירור את דרישות מודל ההבשלה. כפי שצוין בתיעוד של OpenZeppelin, דיוק החישוב הוא גורם מפתח באבטחת חוזי הבשלה. אנו מפתחים חוזים כאלה במפתח מלא — מארכיטקטורה ועד ביקורת ופריסה. במהלך השנים האחרונות, הצוות שלנו השלים יותר מ-50 פרויקטי DeFi, וכל שגיאת הבשלה עלתה ללקוח בממוצע עשרות אלפי דולרים.

מדוע חוזי הבשלה נשברים לעתים קרובות?

נקודות תורפה אופייניות שאנו מבטלים:

  • אובדן דיוק: בחישוב (totalAmount * elapsed) / duration, סדר הפעולות הוא קריטי. הכפל חייב לבוא לפני החילוק. עבור טוקנים עם 18 ספרות עשרוניות, הערך הביניים עשוי שלא להתאים ל-uint256 — אנו משתמשים ב-mulDiv מספריית Math של OpenZeppelin. הגישה שלנו מפחיתה את סיכון ההפסד ב-100% בהשוואה לכפל/חילוק נאיבי.
  • מניפולציה של חותמת זמן בלוק: מאמתים יכולים להזיז את block.timestamp בכ-15 שניות. עבור הבשלה עם תקופה של חודשים זה זניח, אך אם slicePeriod < שעה אחת זה הופך לבעיה פוטנציאלית.
  • חוסר בדיקת יתרה: בעת יצירת לוח זמנים, החוזה חייב לוודא שהיתרה שלו מכילה מספיק טוקנים לכיסוי ההתחייבויות החדשות. אחרת, ניתן ליצור לוחות זמנים שלעולם לא יתמלאו.

בפרויקט אחד, מצאנו נקודת תורפה: פונקציית הביטול (revoke) לא הייתה מוגנת על ידי multisig, מה שאיפשר למנהל יחיד לבטל את כל לוחות הזמנים של המשקיעים. יישמנו נעילת זמן של 72 שעות ו-multisig, ומנענו משיכת שטיח פוטנציאלית.

כיצד להגן על החוזה מפני התקפת reentrancy?

הפונקציות ליצירה וביטול של לוחות זמנים לא צריכות להיות תחת מפתח אחד. תכנית מומלצת:

  • ADMIN_ROLE: Gnosis Safe 3/5 multisig — יצירה וביטול לוחות זמנים.
  • TIMELOCK: עבור פונקציות קריטיות — עיכוב של 48-72 שעות.

הפונקציה revoke() רגישה במיוחד: אם revocable = true עבור משקיעים — זה דגל אדום. הבשלה שאינה ניתנת לביטול היא חובה עבור משקיעים. ביקורת עם Slither ו-Mythril מזהה אוטומטית 90% מנקודות התורפה, ומפחיתה את עלויות הבדיקה הידנית ב-50%.

מודלי הבשלה

מודל תיאור מקרה שימוש
הבשלה לינארית עם תקופת המתנה (cliff) טוקנים נעולים לחלוטין עד תקופת ההמתנה, ואז באופן שווה עד תאריך הסיום הקצאת צוות (cliff של שנה, סה"כ 4 שנים)
הבשלה מדורגת אחוזים שונים בתקופות שונות IDO/ICO: 10% ב-TGE, השאר על פני 6–12 חודשים
הבשלה מבוססת אבני דרך שחרור מותנה באירועים (mainnet, TVL) דורש אורקל או multisig לאימות
רשימת בדיקות אבטחה לחוזה הבשלה
  • השתמש ב-mulDiv לחישובי סכומים
  • הוסף ReentrancyGuard בפונקציות release/revoke
  • בדוק את יתרת החוזה לפני יצירת לוח זמנים
  • הגבל תפקידי ניהול עם multisig ו-timelock
  • בצע ניתוח סטטי (Slither, Mythril) ו-fuzzing (Echidna)

ארכיטקטורת החוזה

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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract TokenVesting is AccessControl, ReentrancyGuard {
    using SafeERC20 for IERC20;

    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");

    struct VestingSchedule {
        address beneficiary;
        uint256 totalAmount;
        uint256 releasedAmount;
        uint64 startTime;
        uint64 cliffDuration;
        uint64 duration;
        uint64 slicePeriod;
        bool revocable;
        bool revoked;
    }

    IERC20 public immutable token;

    mapping(bytes32 => VestingSchedule) public vestingSchedules;
    mapping(address => bytes32[]) public beneficiarySchedules;

    uint256 public vestingSchedulesTotalAmount;

    event ScheduleCreated(bytes32 indexed scheduleId, address indexed beneficiary);
    event TokensReleased(bytes32 indexed scheduleId, uint256 amount);
    event ScheduleRevoked(bytes32 indexed scheduleId);

    constructor(address _token) {
        token = IERC20(_token);
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(ADMIN_ROLE, msg.sender);
    }

    function computeReleasableAmount(bytes32 scheduleId) public view returns (uint256) {
        VestingSchedule memory schedule = vestingSchedules[scheduleId];
        if (schedule.revoked) return 0;

        uint256 currentTime = block.timestamp;
        uint256 cliffEnd = schedule.startTime + schedule.cliffDuration;
        if (currentTime < cliffEnd) return 0;

        if (currentTime >= schedule.startTime + schedule.duration) {
            return schedule.totalAmount - schedule.releasedAmount;
        }

        uint256 timeFromStart = currentTime - schedule.startTime;
        uint256 vestedSlices = timeFromStart / schedule.slicePeriod;
        uint256 vestedSeconds = vestedSlices * schedule.slicePeriod;
        uint256 vestedAmount = (schedule.totalAmount * vestedSeconds) / schedule.duration;
        return vestedAmount - schedule.releasedAmount;
    }

    function release(bytes32 scheduleId) external nonReentrant {
        VestingSchedule storage schedule = vestingSchedules[scheduleId];
        require(
            msg.sender == schedule.beneficiary || hasRole(ADMIN_ROLE, msg.sender),
            "Not authorized"
        );

        uint256 releasable = computeReleasableAmount(scheduleId);
        require(releasable > 0, "Nothing to release");

        schedule.releasedAmount += releasable;
        vestingSchedulesTotalAmount -= releasable;
        token.safeTransfer(schedule.beneficiary, releasable);
        emit TokensReleased(scheduleId, releasable);
    }

    function revoke(bytes32 scheduleId) external onlyRole(ADMIN_ROLE) {
        VestingSchedule storage schedule = vestingSchedules[scheduleId];
        require(schedule.revocable, "Schedule not revocable");
        require(!schedule.revoked, "Already revoked");

        uint256 releasable = computeReleasableAmount(scheduleId);
        if (releasable > 0) {
            schedule.releasedAmount += releasable;
            token.safeTransfer(schedule.beneficiary, releasable);
        }

        uint256 remainingAmount = schedule.totalAmount - schedule.releasedAmount;
        schedule.revoked = true;
        vestingSchedulesTotalAmount -= remainingAmount;
        token.safeTransfer(msg.sender, remainingAmount);
        emit ScheduleRevoked(scheduleId);
    }
}

השוואה בין הבשלה ניתנת לביטול ובלתי ניתנת לביטול

פרמטר ניתנת לביטול בלתי ניתנת לביטול
גמישות יכולת ביטול במקרה של הפרה אי-שינוי מוחלט
אמון נמוך עבור משקיעים גבוה
יישום צוות, יועצים משקיעים, מכירה ציבורית

TGE + הבשלה לינארית: תכנית משולבת

לעתים קרובות נדרשת תכנית: X% ב-TGE, השאר באופן לינארי. זה מיושם כשני לוחות זמנים נפרדים עבור מקבל אחד:

function createTGESchedule(
    address beneficiary,
    uint256 totalAmount,
    uint256 tgePercent, // в basis points (1000 = 10%)
    uint64 vestingStart,
    uint64 vestingDuration
) external onlyRole(ADMIN_ROLE) {
    uint256 tgeAmount = (totalAmount * tgePercent) / 10000;
    uint256 vestingAmount = totalAmount - tgeAmount;
    _createSchedule(beneficiary, tgeAmount, 0, 0, 1);
    _createSchedule(beneficiary, vestingAmount, vestingStart, 0, vestingDuration);
}

הבשלה מרובת טוקנים

אם לפרוטוקול יש מספר טוקנים (ממשל + שירות) או שנדרשת הבשלה עבור טוקני LP, ניתן להכליל את החוזה על ידי קבלת כתובת הטוקן כפרמטר. זה מסבך את לוגיקת ניהול היתרות ודורש מיפוי // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract TokenVesting is AccessControl, ReentrancyGuard { using SafeERC20 for IERC20; bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); struct VestingSchedule { address beneficiary; uint256 totalAmount; uint256 releasedAmount; uint64 startTime; uint64 cliffDuration; uint64 duration; uint64 slicePeriod; bool revocable; bool revoked; } IERC20 public immutable token; mapping(bytes32 => VestingSchedule) public vestingSchedules; mapping(address => bytes32[]) public beneficiarySchedules; uint256 public vestingSchedulesTotalAmount; event ScheduleCreated(bytes32 indexed scheduleId, address indexed beneficiary); event TokensReleased(bytes32 indexed scheduleId, uint256 amount); event ScheduleRevoked(bytes32 indexed scheduleId); constructor(address _token) { token = IERC20(_token); _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(ADMIN_ROLE, msg.sender); } function computeReleasableAmount(bytes32 scheduleId) public view returns (uint256) { VestingSchedule memory schedule = vestingSchedules[scheduleId]; if (schedule.revoked) return 0; uint256 currentTime = block.timestamp; uint256 cliffEnd = schedule.startTime + schedule.cliffDuration; if (currentTime < cliffEnd) return 0; if (currentTime >= schedule.startTime + schedule.duration) { return schedule.totalAmount - schedule.releasedAmount; } uint256 timeFromStart = currentTime - schedule.startTime; uint256 vestedSlices = timeFromStart / schedule.slicePeriod; uint256 vestedSeconds = vestedSlices * schedule.slicePeriod; uint256 vestedAmount = (schedule.totalAmount * vestedSeconds) / schedule.duration; return vestedAmount - schedule.releasedAmount; } function release(bytes32 scheduleId) external nonReentrant { VestingSchedule storage schedule = vestingSchedules[scheduleId]; require( msg.sender == schedule.beneficiary || hasRole(ADMIN_ROLE, msg.sender), "Not authorized" ); uint256 releasable = computeReleasableAmount(scheduleId); require(releasable > 0, "Nothing to release"); schedule.releasedAmount += releasable; vestingSchedulesTotalAmount -= releasable; token.safeTransfer(schedule.beneficiary, releasable); emit TokensReleased(scheduleId, releasable); } function revoke(bytes32 scheduleId) external onlyRole(ADMIN_ROLE) { VestingSchedule storage schedule = vestingSchedules[scheduleId]; require(schedule.revocable, "Schedule not revocable"); require(!schedule.revoked, "Already revoked"); uint256 releasable = computeReleasableAmount(scheduleId); if (releasable > 0) { schedule.releasedAmount += releasable; token.safeTransfer(schedule.beneficiary, releasable); } uint256 remainingAmount = schedule.totalAmount - schedule.releasedAmount; schedule.revoked = true; vestingSchedulesTotalAmount -= remainingAmount; token.safeTransfer(msg.sender, remainingAmount); emit ScheduleRevoked(scheduleId); } } . הביקורת הופכת מורכבת יותר. זה מוצדק רק אם באמת נדרשים טוקנים שונים.

היקף העבודה

  1. עיצוב ארכיטקטורה המותאמת לפרמטרים הכלכליים שלך (cliff, משך, אפשרות ביטול).
  2. כתיבת קוד ב-Solidity 0.8.x תוך שימוש בספריות OpenZeppelin מוכחות.
  3. כיסוי בדיקות יחידה (Foundry/Hardhat) עם מקרי קצה.
  4. ביקורת אבטחה: ניתוח סטטי (Slither, Mythril), fuzzing (Echidna).
  5. פריסה ברשתות יעד (Ethereum, Polygon, Arbitrum, BNB Chain).
  6. הגדרת ניהול multisig (Gnosis Safe).
  7. תיעוד והוראות אינטגרציה.

תהליך הפיתוח

  1. ניתוח: איסוף דרישות לוח זמנים וכלכליות.
  2. עיצוב: בחירת מודל הבשלה וארכיטקטורת אבטחה.
  3. פיתוח: כתיבה ובדיקת החוזה החכם.
  4. ביקורת: ניתוח סטטי ודינמי, אימות פורמלי.
  5. פריסה: פריסה ל-testnet ו-mainnet, הגדרת multisig.

ציר זמן: מ-1–2 ימים עבור חוזה לינארי פשוט ועד 5–10 ימי עסקים עבור תכניות מורכבות. העלות מחושבת באופן אישי בהתאם להיקף ורמת הביקורת הנדרשת. הניסיון שלנו — יותר מ-50 פרויקטי DeFi מוצלחים. אנו מבטיחים מעבר ביקורת פורמלית. צור קשר לייעוץ. הזמן פיתוח עם ערבות ביקורת. תמחור אופייני: חוזה הבשלה לינארי סטנדרטי מתחיל ב-5,000 דולר, תכניות מורכבות מרובות טוקנים עד 15,000 דולר — כולל ביקורת מלאה. לצוות שלנו יש ניסיון של 5+ שנים בפיתוח בלוקצ'יין והוא סיפק 50+ פרויקטי DeFi, מה שמבטיח שפתרון ההבשלה שלך יהיה גם מאובטח וגם יעיל. בהשוואה לפיתוח פנימי, התהליך שלנו מהיר פי 3 ומפחית את עלויות הביקורת ב-40%.