תארו לעצמכם חוזה חכם שלכם בייצור עם מאות אלפי USDT בנזילות. מתגלה פגיעות—reentrancy—בקוד. אתם צריכים לעדכן את הלוגיקה, אבל כתובת החוזה חייבת להישאר ללא שינוי—משתמשים ואינטגרציות קשורים אליה. הדרך היחידה היא תבנית ה-proxy לחוזים הניתנים לשדרוג. יישמנו פתרונות כאלה עבור 30+ פרויקטים ב-Ethereum, Polygon ו-Arbitrum. הניסיון שלנו מבטיח שתמנעו ממלכודות נפוצות—התנגשות אחסון, אתחול לא תקין, ואובדן זכויות שדרוג.
למה התנגשות אחסון היא האיום המרכזי על proxies
proxy קלאסי עובד דרך DELEGATECALL: חוזה ה-proxy קורא ליישום אבל מבצע קוד בהקשר האחסון של ה-proxy. האחסון ב-EVM הוא מערך של 2²⁵⁶ משבצות של 32 בתים כל אחת. אם ה-proxy מאחסן את כתובת היישום במשבצת 0, והיישום מאחסן, לדוגמה, owner במשבצת 0, מתרחשת התנגשות אחסון: משתנה ה-owner ביישום דורס את כתובת היישום ב-proxy. תוקף שיכול לשנות את ה-owner ביישום מקבל שליטה על ה-proxy.
EIP-1967 פותר זאת באופן קיצוני: הוא מאחסן את כתובת היישום במשבצת פסאודו-אקראית המחושבת כ-constructor(). ההסתברות להתנגשות עם משתנים המוגדרים על ידי המשתמש ביישום היא נמוכה באופן אסטרונומי. ERC1967Proxy של OpenZeppelin מיישם בדיוק תקן זה.
איזו תבנית proxy לבחור לפרויקט שלכם?
Transparent Proxy (TUP). התבנית הקלאסית של OpenZeppelin. שני סוגי קוראים: admin (מנהל שדרוגים) ומשתמשים (קוראים ללוגיקה). admin לא יכול לקרוא לפונקציות היישום—רק לשדרג. תקורה לכל קריאה: קריאת אחסון נוספת אחת (SLOAD) לבדיקת msg.sender.
UUPS (EIP-1822). לוגיקת השדרוג מועברת לחוזה היישום עצמו. ה-proxy הופך לדק יותר—פחות גז לכל קריאה. אבל כאן יש מלכודת קריטית: אם תפרסו יישום חדש ללא הפונקציה upgradeTo, החוזה מאבד לצמיתות את היכולת לשדרג. UUPSUpgradeable של OpenZeppelin מוסיף בדיקה ב-initializer—זו ההגנה היחידה. UUPS יכול לחסוך עד 30% גז בהשוואה ל-Transparent, מה שעשוי להפחית עלויות באלפי דולרים בחודש.
Beacon Proxy. חוזה beacon יחיד מאחסן את כתובת היישום. חוזי proxy רבים מצביעים על beacon זה. שדרוג אחד של ה-beacon מעדכן את כל ה-proxies בו זמנית. אידיאלי עבור factories (תבנית מפעל), שבהם צריך ליצור חוזים זהים רבים (לדוגמה, pools ב-AMM). Beacon Proxy עולה על UUPS בגמישות עבור factories ביותר מפי 2 בפריסה המונית.
| תבנית | גז לכל קריאה | גמישות | סיכונים |
|---|---|---|---|
| Transparent | +2100 גז (SLOAD) | גבוהה | התנגשות אחסון עם פריסה לא נכונה |
| UUPS | מינימלי | גבוהה | אובדן יכולת שדרוג אם שגיאה |
| Beacon | בינוני | מקסימלית עבור factories | נקודת כשל יחידה (beacon) |
עבור פרויקטים בקנה מידה גדול עם עומסי עסקאות גבוהים, חיסכון בגז באמצעות UUPS במקום Transparent יכול להגיע ל-$5,000 בחודש, מה שהופך תבנית זו לאופטימלית עבור פרוטוקולי DeFi בעלי נפח גבוה.
למה אתחול במקום constructor הוא קריטי
initialize() ב-Solidity מבוצע פעם אחת בפריסה. בתבנית ה-proxy, היישום נפרס בנפרד—ה-constructor שלו רץ בהקשר של היישום, לא של ה-proxy. כל המשתנים המוגדרים ב-constructor נשארים ביישום ואינם נגישים דרך ה-proxy.
פתרון: החליפו את ה-constructor בפונקציית initialize() באמצעות ה-modifier _disableInitializers() של OpenZeppelin. היא נקראת פעם אחת דרך ה-proxy וכותבת נתונים לאחסון של ה-proxy.
טעות נפוצה היא לשכוח לקרוא ל-initialize() ב-constructor של היישום. בלעדיו, תוקף יכול לקרוא ל-initializer ישירות על היישום (לא דרך ה-proxy) ולהפוך לבעלים שלו. זה לא משפיע ישירות על ה-proxy, אבל פותח וקטורים להתקפה דרך DELEGATECALL.
| גישה | הקשר ביצוע | אבטחה | שימוש |
|---|---|---|---|
| constructor | יישום | נמוכה (לא נגיש דרך proxy) | רק עבור משתנים בלתי ניתנים לשינוי |
| initialize | Proxy | גבוהה (modifier initializer) | חוזים הניתנים לשדרוג |
איך אנחנו עושים זאת: מחסנית וכלים
אנחנו משתמשים במחסנית מודרנית: Foundry לפיתוח ובדיקות, OpenZeppelin Upgrades Plugin לאימות פריסת אחסון, ו-OpenZeppelin Upgrades ליישום בטוח. גרסאות Solidity 0.8.x, תמיכה בכל ה-L2s (Arbitrum, Optimism, Base).
לפריסה, אנחנו משתמשים ב-Gnosis Safe multisig. אין מפתחות פרטיים בסקריפטים. כל השדרוגים עוברים דרך TimelockController עם עיכוב של 3 ימים.
מה כלול בעבודה
- ביקורת של פריסת האחסון הנוכחית וזיהוי סיכונים.
- בחירת התבנית האופטימלית (Transparent/UUPS/Beacon) עם נימוק.
- יישום החוזה עם initialize() ובדיקות (Foundry/Hardhat).
- הערכת חיסכון פוטנציאלי בגז (עד 30%, שווה ערך ל-$5,000 בחודש עבור פרויקט ממוצע).
- הכנת סקריפטים לפריסה דרך Safe Transaction Builder.
- אימות חוזה ב-Etherscan.
- תיעוד על שדרוג והדרכת צוות.
- תמיכה של שבועיים לאחר הפריסה.
תהליך העבודה
- ניתוח. לימוד החוזה הנוכחי (או הדרישות לחוזה חדש), פריסת אחסון, פונקציות רצויות.
- עיצוב. בחירת התבנית, עיצוב מבנה האחסון תוך התחשבות בשינויים עתידיים אפשריים.
- יישום. כתיבת הקוד עם initialize(), בדיקות על fork של mainnet.
- בדיקות. ביצוע פרופיל גז, אימות שאין התנגשות אחסון דרך OpenZeppelin Upgrades Plugin.
- פריסה. פריסת היישום וה-proxy דרך multisig. קריאה ל-initialize().
- תמיכה. לאחר הפריסה, מתן סקריפטים לשדרוג וניטור.
רשימת בדיקה לפני פריסת חוזה הניתן לשדרוג
- [ ]
_disableInitializers()נקרא ב-constructor של היישום. - [ ]
initialize()מוגן על ידי ה-modifierinitializer. - [ ] פריסת האחסון אומתה דרך OpenZeppelin Upgrades Plugin (
validate). - [ ] בעל ProxyAdmin הוא multisig, לא EOA.
- [ ] Timelock מוגדר עבור ייצור.
- [ ] היישום החדש אומת ב-Etherscan לפני העברת זכויות השדרוג.
- [ ] בדיקה: fork של mainnet, שדרוג, אימות אחסון.
לוחות זמנים ויצירת קשר
יישום תבנית proxy עבור חוזה חדש לוקח 2-3 ימי עסקים. העברת חוזה קיים שאינו proxy לארכיטקטורה הניתנת לשדרוג (עם שימור נתונים דרך סקריפט העברה) לוקחת 3 עד 7 ימים בהתאם למורכבות האחסון. התמחור נקבע באופן אישי—צרו קשר להערכת פרויקט; נציע את הפתרון האופטימלי כולל. חיסכון בגז בבחירת UUPS יכול להגיע ל-30%, כלומר עד $5,000 חודשיים עבור חוזים פופולריים. קבלו ייעוץ על פריסת האחסון שלכם ובחירת התבנית.
תבנית Proxy — הבסיס הרעיוני.







