פיתוח חוזי פרוקסי: UUPS ופרוקסי שקוף

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

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

שאלות נפוצות

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

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

פיתוח חוזי פרוקסי (UUPS, Transparent Proxy)

החוזה שלך פרוס על mainnet, ומתגלה פרצת אבטחה קריטית. בלי פרוקסי — מזל רע: הגירה ידנית, תחינה למשתמשים לעבור לכתובת חדשה, נטישת ה-TVL הישן. אנחנו מבינים: עם פרוקסי מוגדר כראוי — שדרוג דרך multisig תוך 15 דקות, כתובת החוזה נשארת זהה. אבל "מוגדר כראוי" היא מילת המפתח. תבניות פרוקסי מציגות מחלקה שלמה של פרצות שאינן קיימות בחוזים בלתי ניתנים לשינוי: התנגשות אחסון, implementations לא מאותחלים, אובדן זכויות שדרוג. הצוות שלנו יישם עשרות מערכות פרוקסי לפרוטוקולי DeFi עם TVL של עד $500M במשך 7 שנים. בחירת תבנית אינה פורמליות טכנית אלא החלטה ארכיטקטונית המשפיעה על gas, אבטחה וממשל. להלן, נפרק את שתי התבניות המרכזיות — Transparent Proxy ו-UUPS — את הפשרות והטעויות הנפוצות שלהן, וכיצד אנו מתכננים ובודקים מערכות כאלה.

בעיות שאנו פותרים

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

  • התנגשות אחסון: משתנים יכולים להידרס במהלך שדרוג — שחיתות נתונים שקטה.
  • implementation לא מאותחל: אם initialize() נשכח, תוקף יכול להשתלט על החוזה.
  • אובדן זכויות שדרוג: implementation ללא upgradeTo מקפיא את החוזה לצמיתות.
  • תוספת gas: Transparent Proxy מוסיף SLOAD לכל קריאה, קריטי לפרוטוקולים עם תפוקה גבוהה. לדוגמה, עם 10,000 עסקאות יומיות, עלות ה-gas הנוספת היא 1,000,000 gas ($20 במחירים נוכחיים, חיסכון של 30% עם UUPS).

שתי תבניות וההבדלים האמיתיים ביניהן

Transparent Proxy

יישום קלאסי מ-OpenZeppelin העוקב אחר EIP-1967. חוזה הפרוקסי מכיל לוגיקת ניתוב: אם הקורא הוא המנהל — הוא מתקשר ישירות (שדרוג, changeAdmin). אם כל אחד אחר — הקריאה מועברת ל-implementation.

בעיה: כל קריאה לחוזה דורשת SLOAD נוסף לקריאת כתובת המנהל (100 gas לפי EIP-2929) והשוואה עם upgradeTo. בנתיבים חמים — זהו עומס קבוע. לפרוטוקול עם מיליוני קריאות יומיות — זה משמעותי. שנית, ProxyAdmin הוא חוזה נפרד המחזיק בזכויות השדרוג. זה מוסיף חוזה נוסף ונקודת ניהול מפתח נוספת.

למה UUPS הוא התקן התעשייתי כיום

לוגיקת השדרוג מועברת מהפרוקסי ל-implementation. הפרוקסי עצמו הופך למעביר פשוט ללא ניתוב מבוסס קורא. ללא SLOAD נוסף לכל קריאה — זול יותר לתפעול. OpenZeppelin מגרסה 4.x ומעלה ממליצה על UUPS לפרויקטים חדשים, כפי שמאושר ב-מאגר הרשמי.

אבל יש סיכון קריטי: אם תפרוס implementation חדש ללא הפונקציה UUPSUpgradeable (שכחת לרשת מ-initialize(), או הסרת אותה בכוונה כדי לחסוך ב-gas) — הפרוקסי מאבד לצמיתות את היכולת לשדרג. החוזה קפוא בגרסה הנוכחית ללא דרך לתקן.

מקרה אמיתי: כמה פרוטוקולים המשתמשים ב-UUPS נתקלו בבעיות implementation לא מאותחל. ה-implementation נפרס ללא קריאה ל-initialize(), ותוקף קרא ל-upgradeTo() ראשון, הפך לבעלים, ואז השתמש ב-_disableInitializers() כדי להחליף את ה-implementation בחוזה שמשמיד את עצמו. כל הפרוקסים שהצביעו על אותו implementation הפכו לשבורים. פתרון: uint256 public totalSupply; // slot 0 address public owner; // slot 1 בקונסטרוקטור של ה-implementation — תבנית חובה ב-OpenZeppelin 4.3+. אנו כוללים זאת בכל פריסה.

כיצד להימנע מהתנגשות אחסון בשלב התכנון

המהות: פרוקסי ו-implementation חולקים את אותו אחסון. אם לפרוקסי יש משתנה ב-slot 0 וגם ל-implementation יש משתנה ב-slot 0 — הם דורסים זה את זה.

EIP-1967 פותר זאת עבור משתנים ספציפיים לפרוקסי (כתובת implementation, כתובת מנהל) על ידי אחסונם ב-slots פסאודו-אקראיים המבוססים על keccak256 hash של מחרוזת, ובכך כמעט מבטל התנגשות עם אחסון המשתמש.

אבל אחסון ה-implementation במהלך שדרוגים הוא באחריות המפתח. אם ל-V1 היה המבנה:

uint256 public totalSupply; // slot 0 address public owner; // slot 1 

ו-V2 מוסיף משתנה לפני המשתנים הקיימים:

bool public paused; // slot 0 — КОЛЛИЗИЯ с totalSupply
uint256 public totalSupply; // slot 1 — КОЛЛИЗИЯ с owner
address public owner; // slot 2

bool public paused; // slot 0 — КОЛЛИЗИЯ с totalSupply uint256 public totalSupply; // slot 1 — КОЛЛИЗИЯ с owner address public owner; // slot 2 קורא כעת מה שהיה בעבר totalSupply (כתובת המתפרשת כמספר). שחיתות נתונים שקטה, ללא revert של עסקאות או שגיאות קומפיילר.

ERC-7201 (Namespaced Storage Layout) פותר זאת באופן קיצוני. כל משתני ה-implementation נאספים למבנה אחד המאוחסן ב-slot מחושב מראש:

bytes32 private constant STORAGE_LOCATION = keccak256(abi.encode(uint256(keccak256("myprotocol.storage.v1")) - 1)) & ~bytes32(uint256(0xff)); 

משתנים חדשים מתווספים לסוף המבנה. ללא התנגשויות עם slots ספציפיים לפרוקסי, ללא בעיות במהלך שדרוגים. זוהי הפרקטיקה המומלצת הנוכחית לחוזי UUPS בייצור.

כיצד לאתחל חוזה פרוקסי

בארכיטקטורת פרוקסי, הקונסטרוקטור של ה-implementation אינו מבוצע בהקשר הפרוקסי — הוא רץ רק במהלך פריסת ה-implementation עצמו. לכן כל פעולות האתחול (הגדרת בעלים, פרמטרים ראשוניים) מועברות לפונקציית owner המוגנת על ידי ה-modifier bytes32 private constant STORAGE_LOCATION = keccak256(abi.encode(uint256(keccak256("myprotocol.storage.v1")) - 1)) & ~bytes32(uint256(0xff)); .

באג נפוץ: initialize() נשכח לאחר פריסת הפרוקסי. החוזה עובד, אבל הבעלים לא מוגדר — הקורא הראשון של initializer הופך לבעלים. בתקרית חוזה WalletLibrary, 587 ETH (בשווי ~$1.5M באותה עת) אבדו עקב השתלטות על חוזה לא מאותחל. פתרון: סקריפטי פריסה צריכים לפרוס את הפרוקסי ולקרוא ל-initialize() באופן אטומי בתוך סקריפט אחד. לעולם אל תפרוס פרוקסי ללא אתחול מיידי.

מה כלול בעבודה שלנו (תוצרים)

  • קוד חוזה חכם פרוקסי מלא (Solidity) עם התבנית הנבחרת (UUPS או Transparent).
  • עיצוב מבנה אחסון באמצעות ERC-7201 למניעת התנגשויות.
  • סקריפטי פריסה (Hardhat/Foundry) עם אתחול אטומי.
  • בדיקות יחידה ו-fork המאמתות בטיחות שדרוג.
  • אינטגרציה עם multisig או ממשל DAO לזכויות שדרוג.
  • תיעוד מפורט המכסה נהלי שדרוג.
  • תמיכה לאחר פריסה למשך 30 יום.

כיצד אנו מיישמים חוזי פרוקסי

ספריית הבסיס — OpenZeppelin Upgrades (תוסף Hardhat או גרסה תואמת Foundry). התוסף בודק אוטומטית תאימות מבנה אחסון בין גרסאות בכל שדרוג — זה חובה, לא אופציונלי.

עבור UUPS, אנו משתמשים ב-initialize() מ-OpenZeppelin 5.x. עבור מערכות שבהן שדרוגים צריכים להיות מנוהלים על ידי DAO או multisig — initialize() עם UUPSUpgradeable המוענק לכתובת Gnosis Safe.

אנו בודקים שדרוגים באמצעות בדיקות fork של Foundry: fork ל-mainnet, מדמים את השדרוג, מוודאים שכל משתני האחסון שומרים על ערכים, פונקציות פועלות כראוי, משתנים חדשים מאותחלים כראוי.

קריטריון Transparent Proxy UUPS
Gas לכל קריאה +100-200 gas (SLOAD מנהל) ללא תוספת
סיכון אובדן שדרוג לא כן (חוסר upgradeTo)
מורכבות קוד נמוכה יותר קצת גבוהה יותר
המלצת OZ 5.x לא מומלץ לחדשים מועדף
ProxyAdmin נפרד כן לא

מתי פרוקסי אינו נחוץ

חוזה בלתי ניתן לשינוי הוא פשוט יותר, זול יותר לבדיקה, ומעורר יותר אמון משתמשים (ללא סיכון rug דרך שדרוג). אם הלוגיקה יציבה והסיכון לשגיאה קריטית מינימלי — פרוקסי מוסיף מורכבות ללא צורך. עבור פרוטוקולי DeFi עם TVL גדול, חוזה בלתי ניתן לשינוי + timelock על פרמטרים הוא לעתים קרובות עדיף על יכולת שדרוג ללא ממשל פורמלי. עם זאת, אם אתה צופה צורך בעדכונים — פרוקסי הוא הכרחי.

תהליך ולוחות זמנים

  1. ניתוח דרישות — 1-2 ימים. בחירת תבנית, הגדרת תפקידים ו-timelock.
  2. עיצוב מבנה אחסון — יום אחד. סכמת ERC-7201, בדיקת התנגשויות.
  3. פיתוח implementation — 2-3 ימים. קוד Solidity עם בדיקות fork של Foundry.
  4. פריסה ואתחול — יום אחד. פריסה אטומית דרך סקריפט.
  5. בדיקת שדרוג — יום אחד. אימות תאימות אחסון.
  6. ביקורת פנימית — 2-4 ימים. כולל Slither, Mythril וסקירה ידנית.

לוח זמנים כולל: מ-5 ימי עסקים לפרוקסי פשוט ועד 2-3 שבועות למערכות מורכבות עם ממשל ומספר implementations. צור קשר להערכה מדויקת לפרויקט שלך — נחשב את לוח הזמנים מקצה לקצה. עלות טיפוסית נעה בין $8,000 ל-$30,000 בהתאם למורכבות.

רשימת בדיקה לפריסת פרוקסי בטוחה
  • [ ] תבנית נבחרה (UUPS / Transparent) עם נימוק.
  • [ ] מבנה אחסון יושם באמצעות ERC-7201.
  • [ ] AccessControlUpgradeable נקרא בקונסטרוקטור של ה-implementation.
  • [ ] סקריפט פריסה קורא ל-UPGRADER_ROLE באופן אטומי.
  • [ ] ProxyAdmin (אם Transparent) נפרס והוגדר.
  • [ ] _disableInitializers() הוקצה ל-multisig.
  • [ ] תאימות אחסון נבדקה עם תוסף OpenZeppelin.
  • [ ] בדיקות fork מדמות שדרוג עם שימור נתונים.
  • [ ] ביקורת בוצעה (פנימית או חיצונית).
  • [ ] תיעוד נהלי שדרוג הוכן.

אחריות וניסיון

פיתחנו למעלה מ-50 מערכות פרוקסי לפרויקטי DeFi, NFT ותשתית. 7+ שנות ניסיון מעשי ב-Solidity ו-Ethereum. כל פרויקט מלווה בביקורת, ואנו מספקים אחריות לנכונות השדרוג ב-testnet. קבל ייעוץ חינם לבחירת תבנית וארכיטקטורה לפרויקט שלך.