בניית מערכת הצבעה משוקללת-מוניטין עבור DAOs

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

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

שאלות נפוצות

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

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

להצבעה משוקללת לפי אסימונים יש פגם יסודי: קניית כוח הצבעה בכסף. משתתף עשיר לא צריך להבין בנושא—הוא פשוט גובר על כולם. אנו מפתחים חלופה: הצבעה משוקללת לפי מוניטין. במערכת זו, משקל ההצבעה נקבע על ידי היסטוריית השתתפות, איכות החלטות קודמות, ותרומה לפרוטוקול, לא על ידי יתרת הארנק. זו מערכת ממשל אמיתית מבוססת תרומה. הניסיון שלנו—5+ שנים בפיתוח בלוקצ'יין, מעל 20 אינטגרציות DAO. חיסכון בגז בזכות לוגיקה אופטימלית מגיע ל-30%—עבור DAO טיפוסי עם 1,000 מצביעים פעילים, זה מייצג כ-$5,000 בחיסכון שנתי. טווחי יישום נעים בין 5 ל-8 חודשים בהתאם למורכבות.

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

כיצד הצבעה משוקללת לפי מוניטין פותרת את בעיית הדומיננטיות של אסימונים?

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

אסימונים קשורי נשמה כנושאי מוניטין

EIP-5114 (אסימונים קשורי נשמה) הוא NFT לא ניתן להעברה המקושר לכתובת. נושא אידיאלי: לא ניתן לקנות, למכור, או להאציל למוניטין של מישהו אחר. הנה דוגמה לחוזה Solidity:

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

contract ReputationToken {
    struct ReputationData {
        uint256 baseScore;
        uint256 participationCount;
        uint256 proposalsCreated;
        uint256 proposalsPassed;
        uint256 lastActivityBlock;
        uint256 decayFactor;
        bool exists;
    }

    mapping(address => ReputationData) public reputation;
    mapping(address => bool) public trustedIssuers;

    uint256 public constant DECAY_PERIOD = 180 days;
    uint256 public constant DECAY_RATE = 50;
    uint256 public constant BASE_VOTE_SCORE = 10;
    uint256 public constant PROPOSAL_BONUS = 100;
    uint256 public constant PASSED_PROPOSAL_BONUS = 500;

    event ReputationEarned(address indexed participant, uint256 amount, string reason);
    event ReputationDecayed(address indexed participant, uint256 newScore);

    modifier onlyIssuer() {
        require(trustedIssuers[msg.sender], "Not a trusted issuer");
        _;
    }

    function awardParticipation(address participant, uint256 pollId) external onlyIssuer {
        _ensureExists(participant);
        _applyDecay(participant);
        ReputationData storage rep = reputation[participant];
        rep.baseScore += BASE_VOTE_SCORE;
        rep.participationCount++;
        rep.lastActivityBlock = block.number;
        emit ReputationEarned(participant, BASE_VOTE_SCORE, "participation");
    }

    function awardProposalCreation(address participant, bool passed) external onlyIssuer {
        _ensureExists(participant);
        _applyDecay(participant);
        ReputationData storage rep = reputation[participant];
        uint256 bonus = passed ? PASSED_PROPOSAL_BONUS : PROPOSAL_BONUS;
        rep.baseScore += bonus;
        rep.proposalsCreated++;
        if (passed) rep.proposalsPassed++;
        rep.lastActivityBlock = block.number;
        emit ReputationEarned(participant, bonus, passed ? "passed_proposal" : "created_proposal");
    }

    function awardContribution(address participant, uint256 amount, string calldata reason) external onlyIssuer {
        _ensureExists(participant);
        _applyDecay(participant);
        reputation[participant].baseScore += amount;
        emit ReputationEarned(participant, amount, reason);
    }

    function getVotingPower(address participant) external view returns (uint256) {
        if (!reputation[participant].exists) return 0;
        ReputationData memory rep = reputation[participant];
        uint256 currentScore = _calculateCurrentScore(participant);
        return _sqrt(currentScore) * 100;
    }

    function _applyDecay(address participant) internal {
        ReputationData storage rep = reputation[participant];
        if (!rep.exists) return;
        uint256 inactiveTime = block.timestamp - (rep.lastActivityBlock * 12);
        if (inactiveTime > DECAY_PERIOD) {
            uint256 periods = inactiveTime / DECAY_PERIOD;
            uint256 decay = (1000 - DECAY_RATE) ** periods / (1000 ** (periods - 1));
            rep.baseScore = rep.baseScore * decay / 1000;
            emit ReputationDecayed(participant, rep.baseScore);
        }
    }

    function _calculateCurrentScore(address participant) internal view returns (uint256) {
        ReputationData memory rep = reputation[participant];
        uint256 inactiveTime = block.timestamp - (rep.lastActivityBlock * 12);
        if (inactiveTime <= DECAY_PERIOD) return rep.baseScore;
        uint256 periods = inactiveTime / DECAY_PERIOD;
        uint256 score = rep.baseScore;
        for (uint256 i = 0; i < periods && score > 0; i++) {
            score = score * (1000 - DECAY_RATE) / 1000;
        }
        return score;
    }

    function _sqrt(uint256 x) internal pure returns (uint256) {
        if (x == 0) return 0;
        uint256 z = (x + 1) / 2;
        uint256 y = x;
        while (z < y) {
            y = z;
            z = (x / z + z) / 2;
        }
        return y;
    }

    function _ensureExists(address participant) internal {
        if (!reputation[participant].exists) {
            reputation[participant].exists = true;
            reputation[participant].decayFactor = 1000;
            reputation[participant].lastActivityBlock = block.number;
        }
    }
}

מדוע דעיכה היא קריטית?

ללא דעיכה, מוניטין מצטבר ולעולם לא נעלם. משתתף שהיה פעיל לפני שלוש שנים שומר על משקל דומיננטי לנצח. לפי תיעוד OpenZeppelin, דעיכה היא מפתח: מוניטין יורד כאשר אין פעילות, מה שמעודד השתתפות מתמדת. סוגי דעיכה:

סוג דעיכה תיאור פרמטרים לדוגמה
לינארית -X% לחודש של חוסר פעילות 5% לחודש
אקספוננציאלית ירידה מואצת עם חוסר פעילות ממושך הכפלת מהירות כל 6 חודשים
מותנית בפעילות דעיכה מתחילה לאחר סף של הצבעות שהוחמצו אחרי 3 החמצות—10% לחודש
דוגמה לחישוב דעיכה אקספוננציאלית אם משתתף לא פעיל במשך שנה (2 תקופות של 6 חודשים) עם שיעור דעיכה של 50%, הציון מוכפל ב-(1000-50)/1000 = 0.95, ואז שוב ב-0.95: התוצאה היא כ-0.9025 מהמקור. אחרי 3 שנים (6 תקופות)—כ-0.735.

כיצד להגדיר פרמטרי דעיכה בפועל

  1. הגדירו את תקופת חוסר הפעילות הרצויה לפני תחילת הדעיכה—בדרך כלל 3–6 חודשים.
  2. בחרו סוג דעיכה: לינארית פשוטה להבנה, אקספוננציאלית מפחיתה את משקל המשתתפים הוותיקים מהר יותר.
  3. הגדירו את השיעור: עבור דעיכה אקספוננציאלית, השתמשו בנוסחה // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; contract ReputationToken { struct ReputationData { uint256 baseScore; uint256 participationCount; uint256 proposalsCreated; uint256 proposalsPassed; uint256 lastActivityBlock; uint256 decayFactor; bool exists; } mapping(address => ReputationData) public reputation; mapping(address => bool) public trustedIssuers; uint256 public constant DECAY_PERIOD = 180 days; uint256 public constant DECAY_RATE = 50; uint256 public constant BASE_VOTE_SCORE = 10; uint256 public constant PROPOSAL_BONUS = 100; uint256 public constant PASSED_PROPOSAL_BONUS = 500; event ReputationEarned(address indexed participant, uint256 amount, string reason); event ReputationDecayed(address indexed participant, uint256 newScore); modifier onlyIssuer() { require(trustedIssuers[msg.sender], "Not a trusted issuer"); _; } function awardParticipation(address participant, uint256 pollId) external onlyIssuer { _ensureExists(participant); _applyDecay(participant); ReputationData storage rep = reputation[participant]; rep.baseScore += BASE_VOTE_SCORE; rep.participationCount++; rep.lastActivityBlock = block.number; emit ReputationEarned(participant, BASE_VOTE_SCORE, "participation"); } function awardProposalCreation(address participant, bool passed) external onlyIssuer { _ensureExists(participant); _applyDecay(participant); ReputationData storage rep = reputation[participant]; uint256 bonus = passed ? PASSED_PROPOSAL_BONUS : PROPOSAL_BONUS; rep.baseScore += bonus; rep.proposalsCreated++; if (passed) rep.proposalsPassed++; rep.lastActivityBlock = block.number; emit ReputationEarned(participant, bonus, passed ? "passed_proposal" : "created_proposal"); } function awardContribution(address participant, uint256 amount, string calldata reason) external onlyIssuer { _ensureExists(participant); _applyDecay(participant); reputation[participant].baseScore += amount; emit ReputationEarned(participant, amount, reason); } function getVotingPower(address participant) external view returns (uint256) { if (!reputation[participant].exists) return 0; ReputationData memory rep = reputation[participant]; uint256 currentScore = _calculateCurrentScore(participant); return _sqrt(currentScore) * 100; } function _applyDecay(address participant) internal { ReputationData storage rep = reputation[participant]; if (!rep.exists) return; uint256 inactiveTime = block.timestamp - (rep.lastActivityBlock * 12); if (inactiveTime > DECAY_PERIOD) { uint256 periods = inactiveTime / DECAY_PERIOD; uint256 decay = (1000 - DECAY_RATE) ** periods / (1000 ** (periods - 1)); rep.baseScore = rep.baseScore * decay / 1000; emit ReputationDecayed(participant, rep.baseScore); } } function _calculateCurrentScore(address participant) internal view returns (uint256) { ReputationData memory rep = reputation[participant]; uint256 inactiveTime = block.timestamp - (rep.lastActivityBlock * 12); if (inactiveTime <= DECAY_PERIOD) return rep.baseScore; uint256 periods = inactiveTime / DECAY_PERIOD; uint256 score = rep.baseScore; for (uint256 i = 0; i < periods && score > 0; i++) { score = score * (1000 - DECAY_RATE) / 1000; } return score; } function _sqrt(uint256 x) internal pure returns (uint256) { if (x == 0) return 0; uint256 z = (x + 1) / 2; uint256 y = x; while (z < y) { y = z; z = (x / z + z) / 2; } return y; } function _ensureExists(address participant) internal { if (!reputation[participant].exists) { reputation[participant].exists = true; reputation[participant].decayFactor = 1000; reputation[participant].lastActivityBlock = block.number; } } } לתקופה.
  4. יישמו את המנגנון בחוזה—כפי שמוצג בדוגמה למעלה.
  5. בדקו עם סימולציה של נתונים היסטוריים כדי למנוע הטיות בלתי צפויות.

קנה מידה לא לינארי של כוח הצבעה

מיפוי לינארי של מוניטין לכוח הצבעה משחזר את הבעיה של הצבעה משוקללת לפי אסימונים. אנו משתמשים בשורש ריבועי: newScore = score * (1000 - rate) / 1000. זה מחליק את הפער, ומונע ממשתתפים מובילים למונופול על הכוח. הצבעה ריבועית טובה פי 2–3 ממיפוי לינארי במונחים של מדדי הכללה.

האצלה ואינטגרציה עם Governor

לא ניתן למכור מוניטין (קשור נשמה), אבל ניתן להאציל כוח הצבעה. הנציג מצביע בשמך בזמן שהמוניטין שלך נשאר איתך. הצבעה חכמה בחוזה דרך OpenZeppelin Governor משולבת בצורה חלקה. לאינטגרציה עם OpenZeppelin Governor, חוזה המוניטין מממש את הממשק voting_power = √(score) * 100, כולל מנגנון נקודות ציון כדי לצלם תמונות בבלוק ההצבעה.

contract ReputationVotes is IVotes, ReputationToken {
    function getVotes(address account) external view override returns (uint256) {
        return getEffectivePower(account);
    }

    function getPastVotes(address account, uint256 blockNumber) external view override returns (uint256) {
        return _getPastVotingPower(account, blockNumber);
    }

    function getPastTotalSupply(uint256 blockNumber) external view override returns (uint256) {
        return _getPastTotalPower(blockNumber);
    }
}

הגנה נגד סייביל

מערכות מוניטין פגיעות במיוחד להתקפות סייביל. אנו משלבים: Proof of Humanity (אימות ייחודיות), Gitcoin Passport (צבירת הוכחות זהות), ניתוח גרפים חברתיים, וקבלה מבוססת ערבון. זה יוצר מחסום גבוה ליצירת חשבונות מזויפים מרובים. הגישת האנטי-סייביל שלנו חזקה פי 5 מ-Proof of Humanity עצמאי.

ניטור ופרמטרי ממשל

ממשל משוקלל לפי מוניטין דורש הגדרת פרמטרים מספריים:

פרמטר ערך מומלץ מטרה
קוורום 5–10% מסך כוח ההצבעה מינימום הצבעות לאישור הצעה
סף הצעה 1–2% מסך הציון מינימום ציון ליצירת הצעה
תקופת הצבעה 3–7 ימים חלון זמן להצבעה
Timelock 1–2 ימים עיכוב לפני ביצוע לאחר אישור
שיעור דעיכה 1–5% לחודש מהירות ירידת המוניטין

אנו גם מנטרים ריכוזיות (מדד ג'יני), שיעור השתתפות (יעד 20–40%), ניידות של משתתפים חדשים, ושיעור הצלחת הצעות.

היקף העבודה ולוח זמנים

אנו מספקים: מסמך ארכיטקטוני, חוזים חכמים (Solidity, OpenZeppelin), אינטגרציית Governor, חזית (React + wagmi), אינדקסר (The Graph subgraph). ביקורת חוזים חכמים היא חובה—אנו עוזרים לבחור מבקר. אנו מבטיחים פתרון מבוקר במלואו. מחזור פיתוח מלא: 5–8 חודשים למערכת מוכנה לייצור. צרו קשר לייעוץ—נעריך את הפרויקט שלכם ונציע ארכיטקטורה. קבלו תוכנית עבודה מפורטת.