ריפקטורינג חוזים חכמים: ביקורת, אופטימיזציית גז ואבטחה

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

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

שאלות נפוצות

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

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1375
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1349
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    794
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1087
  • פיתוח לוגו חברת AIDER
    פיתוח לוגו חברת AIDER
    1032
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1129

ריפקטורינג חוזים חכמים: היכן דולף גז והיכן הלוגיקה

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

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

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

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

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

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

מה משתנה אחרי העיבוד מחדש: מהירות הקריאות וצריכת הגז

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

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

אנחנו סוגרים אותם בתבניות מוכחות (למשל בספרייה OpenZeppelin) ובבדיקות אינווריאנטיות ממש בתוך הפונקציות, כדי שהתנאי יתקיים בכל סדר של קריאות.

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

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

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

גישות לעיבוד מחדש: החלפה, תיקון חלקי, עטיפה

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

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

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

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

שלבי העבודה: מניתוח הלוגיקה ועד מסירה ללקוח

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

  1. סקירת קוד והרשאות. משרטטים מפת אינטראקציה בין החוזים: קריאות חיצוניות, בדיקות תפקידים, נקודות שינוי פרמטרים ומשיכת כספים. מקומות מסוכנים — קריאות חיצוניות בלתי מוגבלות, היעדר בדיקות כתובות — נכנסים לדוח עם הערכת ההשלכות.
  2. תכנון הסכמה. מתארים כיצד החוזים יהיו מקושרים אחרי העיבוד מחדש: מה מוציאים לאחסון נפרד, אילו תפקידים מקבלים גישה לפונקציות ניהוליות. את הסכמה מתאמים לפני התיקון הראשון.
  3. עיבוד הקוד מחדש. משנים את הלוגיקה בחלקים, כל שינוי הוא תיקון נפרד עם תיאור. קטעים סטנדרטיים מכסים בספריות מוכחות (OpenZeppelin), ולא בפתרונות בהרכבה עצמית.
  4. בדיקת השינויים. מכסים בתרחישים כל מסלול שנכתב מחדש: פעולות שגרתיות, סכומים גבוליים, קריאות חוזרות, כשל של כתובת חיצונית. על אותם נתונים הלוגיקה החדשה צריכה להניב את אותה תוצאה כמו הישנה.
  5. בדיקת הרשאות הגישה. מריצים כל מתודה בשם כל תפקיד — אורח, משתמש, מפעיל, מנהל. פעולות אסורות צריך לדחות החוזה, ולא הממשק.
  6. בדיקה בלתי תלויה. את הקוד שהשתנה בוחן מהנדס שלא השתתף בעיבוד מחדש, בתוספת ניתוח סטטי לסוגי פרצות אופייניים.
  7. קבלת העבודה. מוסרים קוד, סכמה, דוח בדיקות ונוהל שחזור — כיצד להחזיר את המצב הקודם אם אחרי השחרור משהו ישתבש.

מה נשאר אצל הלקוח: קוד, סכמות ותקנות

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

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

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

מקרה בוחן: עיבוד מחדש של לוגיקת חלוקת התשלומים

הלקוח — שירות תשלומים המוניים לשותפים: חוזה אחד ב-Solidity החזיק מרשם של מקבלים, ובהלולאה חילק את התשלומים עבור תקופת החיוב. כל עוד הכתובות היו כמה מאות, הקריאה עברה, אבל ב-1,200 משתתפים היא הפסיקה להיכנס למגבלת הגז.

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

מה שינו בלוגיקה

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

מדדים לפני ואחרי

לפני: תקרה של 90–110 מקבלים לקריאה, סבב של עד 12 הרצות ידניות, וביטול של כל הרצה ביטל את הקודמות. אחרי: אצווה של 250 כתובות עוברת ביציבות, העלות של תשלום בודד ירדה ב-45%, הסבב נסגר במעבר אחד, ושיעור הביטולים בגלל מגבלת גז — אפס בהרצה של 1,200 משתתפים.

יומן האירועים נותן עקבה מלאה לכל תשלום, ומודול המרשם משתנה כעת בנפרד מהלוגיקה החישובית ואינו נוגע במעגל התשלומים.

במה ריפקטורינג שונה מכתיבה מאפס

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

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

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

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

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

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

התלבטויות טכניות לפני תחילת העבודה

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

החוזה כבר פרוס — האם עדכון עוד אפשרי?

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

כיצד לעדכן את הלוגיקה בלי להעביר מצב?

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

כיצד לבדוק שההתנהגות לא השתנתה?

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

למי יש הרשאה לעדכן את החוזה?

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

היכן נגמר תחום האחריות של הקבלן?

אנחנו אחראים על הסקירה, התיקונים והבדיקה החוזרת לפי מודל איומים מוסכם, עם דוח לכל סוג של פרצה שנמצאה. מחוץ לתחום — תרחישים שלא תוארו במודל זה, ושירותים חיצוניים שהחוזה פונה אליהם.

נדון במשימה: נחזור עם הערכה ותוכנית עבודה

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

כדי לחזור עם הערכה ענייניית, ולא בטווח של «פחות או יותר», די לשלוח:

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

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

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