Michelson: מדוע חוזים על Tezos נשברים בבדיקה פורמלית
Michelson הוא שפה ברמה נמוכה עם מכונת מחסנית, שבה אין משתנים ולולאות מוכרים: התוכנית נבנית מרצף של הוראות, שכל אחת מהן מסירה ערכים מהמחסנית ומחזירה את התוצאה בחזרה. טעות בסדר האופרנדים או באנוטציית טיפוס לא "צצה" בזמן ריצה — החוזה просто לא עובר בדיקה פורמלית ולא מפורסם לרשת. פיתוח חוזים חכמים על Tezos מתחיל משום כך לא מהקוד, אלא ממודל מדויק של טיפוסים וזרימת נתונים.
הסיבה הנפוצה ביותר לנפילה בבדיקה היא שגיאות טיפוס ב-Michelson. מכונת המחסנית עוקבת בקפדנות אחרי מה שנמצא בראש המחסנית ודורשת אנוטציות מפורשות (:int, :nat, :address) במקומות שבהם הטיפוס אינו נגזר באופן חד-משמעי.
אם האנוטציה סוטה מהערך בפועל — לדוגמה, nat מועבר למקום שבו מצפים ל-int — הבדיקה דוחה את החוזה כולו, ולא ענף בודד.
הקטגוריה השנייה — תופעות לוואי שלא נלקחו בחשבון. פעולות כמו TRANSFER_TOKENS או CREATE_CONTRACT משנות את מצב הרשת, ואסור לקרוא להן במקום שרירותי: סדר הפעולות וטיפול בכשלים (FAILWITH) חייבים להיות מתוכננים כך שהרצה חלקית לא תשאיר את החוזה במצב לא עקבי.
בנפרד מכה בפרויקט עלות הגז של Tezos. מבני נתונים לא אופטימליים — map ו-big_map גדולים, עותקים ביניים מיותרים, לולאות לא מדויקות דרך ITER — מנפחים את צריכת הגז, והעסקה עלולה לא לעבור את המגבלה. מפרקים כיצד זה נסגר בשלב תכנון הטיפוסים והמבנים.
מה נותנת ארכיטקטורה נכונה של חוזה ב-Michelson
ארכיטקטורת אחסון ועלות פעולות
ב-Michelson אין מבני נתונים דינמיים בצורה המוכרת: big-map מוצב באחסון נפרד, ועלות הפעולה נקבעת לפי נפח הרשומות ב-storage ומספר צעדי המפרש.
לכן פריסת המצב על פני כמה big-map עם מפתחות בצורה קבועה במקום עץ מקונן אחד מפחיתה את מחיר כל טרנזקציה: המפרש אינו צריך לפרוש קינון עמוק, והעדכון נוגע בענף אחד. בחוזים אמיתיים ארגון מחדש כזה צמצם את צריכת הגז בפעולות מרובות היקף פי כמה — עם אותה פונקציונליות.
אימות פורמלי כחלק מהארכיטקטורה
חוזה שבו האינווריאנטים מוצאים לפונקציות נבדקות נפרדות, ולא מרוחות על פני ענפי IF, עובר אימות פורמלי מהר יותר ועם מספר קטן יותר של הנחות ידניות. אנו מתכננים כך שתכונות מפתח — שמירת יתרה, אי-אפשרות משיכה חוזרת, תקינות הרשאות — ינוסחו כלמות נפרדות.
זה מסיר מחלקה שלמה של כשלים שאינם נתפסים בבדיקות: מרוצים בין קריאות, ענפי FAILWITH לא מטופלים, הצפות מרומזות.
השוואת גישות לפיתוח
| גישה | מתי מתאימה | מגבלות |
|---|---|---|
| Michelson ידני | דרישות נוקשות לגז, ביקורת של מקטעים קריטיים | יקר יותר בתחזוקה, סף כניסה גבוה יותר לצוות |
| קומפילציה משפה ברמה גבוהה | הוצאת מוצר מהירה, לוגיקה תכופה | פחות שליטה על פריסת storage וצריכת גז |
| היברידי: ליבה ב-Michelson, מעטפת מקומפלת | חוזים מורכבים עם ליבה קריטית | דורש משמעת בממשק בין השכבות |
אבטחה וניתנות לבדיקה
שכבה נפרדת — ניתוח סטטי ובדיקת טיפוסים לפני דיפלוי: היא חוסמת שגיאות שבסביבת בדיקות מתגלות לעיתים רחוקות, וברשת הראשית מובילות לאובדן כספים. אנו מקבעים אינווריאנטים בקוד ובמפרט, כדי שהמבקר יבדוק תכונות ולא ישחזר את הכוונה.
Michelson — שפת סטאק ברמה נמוכה של Tezos, שבה עלות ואבטחת החוזה מונחות בשלב פריסת המצב, ולא מייעלות בדיעבד.
שלבי פיתוח והטמעה של חוזה על Michelson
פיתוח חוזה על Michelson מתחיל לא בקוד, אלא בפורמליזציה של הכללים: מי קורא לנקודות הכניסה, אילו מצבים מותרים, מה קורה בעת התנגשות תנאים. בשלב זה אנו מקבעים את סכימת האחסון — פריסה לפי big_map ושדות רגילים של storage, מכיוון שממנה תלויה עלות הפעולות ופשטות השינויים העתידיים. טעות במודל הנתונים היא היקרה מכולן: תיקונה לאחר ההרצה מחייב מיגרציה ובדיקה חוזרת.
לאחר מכן באים תכנון, מימוש ובדיקה — כל שלב עם תוצאה משוחזרת שניתן להעביר לצוות אחר.
- אפיון המשימה וגבולות החוזה. מנתחים את הלוגיקה העסקית, תפקידי המשתתפים, הקריאות החיצוניות והמגבלות. בפלט — מפרט של נקודות כניסה ואינווריאנטים שהחוזה מחויב לשמור בכל תרחיש.
- תכנון סכימת האחסון וזרימות הקריאות. מגדירים את מבנה ה-storage, טיפוסי הנתונים של Michelson, מיקום האוספים ב-big_map. בודקים שהפעולות אינן נתקלות במגבלות גז עם גידול במספר הרשומות.
- מימוש ב-Michelson. כותבים את החוזה ונקודות view עזר, שומרים על לוגיקה שטוחה ומפורשת — הסתעפויות מורכבות מסבכות את הביקורת ומעלות את מחיר הטעות. את הקומפילציה מבצעים בענף הכלים העדכני.
- בדיקות. מריצים תרחישי יחידה לקריאות מוצלחות וכושלות, בודקים מצבי קצה והרשאות גישה. בנפרד מדמים התנהגות בקריאות מקבילות ובפעולות חוזרות.
- ביקורת החוזה. בדיקה בלתי תלויה של הלוגיקה, ההרשאות וטיפול בחריגים לפני הפרסום. את כל ההערות סוגרים עם תיעוד השינויים, כדי שהתיקונים לא ישברו אינווריאנטים שכבר נבדקו.
- פריסה ברשת בדיקות. מריצים תרחיש מלא של מחזור החיים על עותק של נתונים אמיתיים, מודדים צריכת גז והתנהגות בעת כשלים.
- מסירת החוזה ללקוח. מוסרים קוד מקור, סכימת storage, דוח ביקורת והוראות לקריאות. צוות הלקוח יכול לתחזק את החוזה בלעדינו.
תארו את המשימה — נחזור עם הערכה ותוכנית לכל שלב.
במה שונה Michelson משפות עיליות עבור Tezos?
Michelson היא שפת מחסנית ברמה נמוכה, שאליה בסופו של דבר מהודר כל חוזה עבור Tezos. שפות עיליות (LIGO, SmartPy, Archetype) מספקות תחביר, טיפוסים והפשטות, אך לשרשרת מגיע אותו בייט-קוד בדיוק.
מכאן המסקנה המעשית: michelson vs liquidity — זה לא עניין של "חזק יותר או חלש יותר", אלא באיזה שכבה הצוות רוצה לשלוט בתוצאה.
הידור ל-Michelson אינו בחינם מבחינת צפיות. המהדר עשוי לפרוס לולאה, להוסיף בדיקות טיפוסים או לפרוס אחרת את האחסון — ותקבלו חוזה שעובד, אך צורך גז לא כפי שחישבתם.
פיתוח ישיר ב-Michelson מסיר את השכבה הזו: אתם רואים כל הוראה, כל גישה למחסנית, ויודעים בדיוק את גודל האחסון ואת עלות הקריאה. זה קריטי עבור חוזים עם מגבלת גז קשיחה או עבור ביקורת, שבה חשוב להוכיח היעדר נתיבי ביצוע נסתרים.
הצד השני — מחיר התחזוקה. ב-Michelson אין פונקציות מסדר גבוה רגילות, מילונים ומחרוזות בצורה שאליה מפתחים רגילים. לוגיקה כמו "לחלק מניות בין המשתתפים" הופכת לעשרות שורות של ניהול מחסנית ידני, שבו קל להתבלבל בסדר האופרנדים. טעות כאן עולה ביוקר: הטיפוסים לא יתפסו בלבול סמנטי, ואי אפשר להחזיר לאחור חוזה שכבר נפרס.
לכן השוואה של שפות חוזים חכמים עבור Tezos בפועל מצטמצמת לבחירת נקודת כניסה. שפה עילית נלקחת עבור לוגיקה עסקית, שבה חשובים מהירות הפיתוח והקריאות: טוקנים, הצבעות, מרשמים.
Michelson — כאשר נדרש שליטה ידנית בגז, קריפטוגרפיה לא סטנדרטית, או כאשר החוזה נכתב כאמת מידה לביקורת ולמימושים עוקבים.
בחירת שפת Tezos לעתים רחוקות היא "או-או". הסכמה העובדת: את הלוגיקה העיקרית כותבים בשפה עילית, ואת הקטעים הקריטיים — אימות חתימות, עבודה עם נתונים בינאריים, אופטימיזציה של אחסון — מוציאים לחוזים נפרדים ב-Michelson וקוראים להם מהחוזה הראשי. כך הצוות מקבל גם את מהירות הפיתוח וגם את הצפיות היכן שהיא חשובה.
מקרה בוחן: כיצד חיסלנו מחלקת כשלים בחוזה על Michelson
הלקוח הגיע עם חוזה על Michelson שעבד "בעיקר תקין": ביקורת עבר, בדיקות ירוקות, אבל בפרודקשן חלק מהקריאות נפלו עם שגיאת ביצוע, והעמלה על פעולות מוצלחות נעה בטווח רחב. הניתוח הראה קלאסיקה — חישובים ב-storage בלי התחשבות בסדר הפעולות ובענפים, שבהם FAILWITH הופעל כבר לאחר חיוב הגז. התנהגות החוזה הייתה תלויה במצב שאף אחד לא בדק לפני הכניסה לענף הקריטי.
מה שינינו
כתבנו מחדש את נקודת הכניסה: בדיקות התנאים המקדימים (amount, הרשאות, גבולות ערכים) הועברו להתחלה, לפני כל שינוי ב-storage. הלוגיקה פוצלה לפונקציות פנימיות קטנות, כך שכל ענף נקרא ונבדק בנפרד. הסרנו חישובים כפולים — ערכי ביניים מחושבים פעם אחת ומשמשים מחדש.
מדדים לפני ואחרי
| מדד | לפני | אחרי |
|---|---|---|
| שיעור העסקאות הכושלות | ניכר | מקרים בודדים על נתוני גבול |
| פיזור צריכת הגז | רחב | צר, צפוי |
| זמן ניתוח תקלה | שעות | דקות לפי לוגים |
כשלים מסוג "נפל לאחר חיוב" נעלמו לחלוטין — כעת כל שגיאה מוחזרת לפני שינוי המצב, והצד הקורא רואה את הסיבה, ולא את עובדת החיוב.
מה זה נתן
החוזה הפך לצפוי: אותו קלט נותן אותה צריכת גז, וההתנהגות על ערכי גבול מתוארת בבדיקות, ולא מתבררת בפרודקשן. נשענו על מודל ביצוע פורמלי Michelson — הוא מאפשר לבדוק אינווריאנטים לפני דיפלוי, ולא אחריו. עבור הלקוח זה означало פחות ניתוחים ידניים ותקציב עמלות מובן לכל פעולה.
אופטימיזציה של חוזה על Tezos בלי לאבד קריאות — זה איזון בין קפדנות הבדיקות לעלות הביצוע. אנחנו שומרים עליו באמצעות בדיקות לכל ענף ותנאים מקדימים מפורשים.
ספקות לפני ההתחלה: תאימות, מיגרציה, הרשאות
התאימות של Michelson לתשתית קיימת היא השאלה הראשונה שאנחנו מסירים לפני ההתחלה. חוזה ב-Michelson מקומפל לאותו פורמט כמו חוזים קיימים ברשת Tezos, ולכן קריאות משירותים חיצוניים, אינדקסרים וארנקים נשארות עובדות.
אם כבר יש לכם חוזים ב-LIGO או SmartPy, המיגרציה לא דורשת כתיבה מחדש מאפס: מעבירים את הלוגיקה ל-Michelson, שומרים על ממשקי נקודות הכניסה כתואמים, ומעבירים את המצב בסקריפט נפרד עם בדיקה ברשת בדיקות.
הרשאות ותפקידים מתכננים לפני הדיפלוי, לא אחריו. נקודות כניסה אדמיניסטרטיביות (עדכון לוגיקה, השהיה, משיכת כספים) סוגרים בחתימה מרובה, ותפעוליות — במפתחות נפרדים עם סט הרשאות מוגבל. זה מסיר סוג של כשלים שבו מפתח אחד שנפרץ פותח את כל החוזה.
מה אם החוזה כבר deployed ואי אפשר לשנות אותו?
Michelson לא מאפשר לערוך קוד של חוזה בלתי ניתן לשינוי, אבל עדכון לוגיקה מוטמע מראש — דרך חוזה proxy או נקודת כניסה עם בדיקת הרשאות. אם אפשרות כזו לא קיימת, מנתחים את המצב הנוכחי, מעבירים את הלוגיקה לחוזה חדש ומכינים תוכנית מעבר עם שמירת נתונים. חזרה לגרסה קודמת נשארת אפשרית כל עוד החוזה הישן לא הוצא משימוש.
מה אם יש לנו צוות משלנו ואנחנו רוצים לתחזק את הקוד בעצמנו? מעבירים לא רק את קוד המקור, אלא גם את הלוגיקה של ההחלטות: למה נבחרה структура אחסון כזו, איפה צווארי הבקבוק בצריכת gas, אילו בדיקות חובה בעת עדכון. המהנדסים של הלקוח יוצאים לתחזוקה בלעדינו.
מה אם צריך audit לפני ההרצה? מכינים את החוזה לבדיקה חיצונית: מסירים נקודות כניסה שאינן בשימוש, מתעדים אינווריאנטים, מצרפים תרחישי בדיקות — זה מקצר את זמן ה-audit ומפחית את מספר ההערות.
הלקוח מקבל לידיו:
- קוד מקור של החוזים ב-Michelson עם הערות לכל נקודת כניסה;
- תרשים תפקידים ומטריצת הרשאות: מי ומה יכול לקרוא;
- סקריפטים של דיפלוי ומיגרציה של מצב עם בדיקה ברשת בדיקות;
- סט בדיקות שמכסות תרחישי מפתח ומקרי קצה;
- הוראות לעדכון לוגיקה וסדר פעולות במקרה של חזרה לגרסה קודמת;
- תיעוד לאינטראקציה של החוזה עם שירותים חיצוניים.
תארו את המשימה — נחזור עם הערכה ותוכנית: מה מעבירים, אילו תפקידים סוגרים, ובאיזה סדר יוצאים לדיפלוי.
לוחות זמנים לפיתוח חוזה ב-Michelson לפי שלבים
לוחות הזמנים ב-Michelson נקבעים לא לפי נפח הקוד, אלא לפי פורמליזציה של אינוריאנטים: חוזה ב-Tezos מתאר מצב ומעברים, וכל אי-בהירות בדרישות עולה בשלב הבדיקות, ולא בעת כתיבת הקוד. לכן את לוח הזמנים בונים מהמודל של האחסון ומרשימת התנאים, ולא מ"מספר השורות". להלן משכי זמן משוערים לשלבים; הם אינם קשורים לתאריכים קלנדריים ומתחדדים לאחר ניתוח המשימה.
את הפיזור בלוחות הזמנים יוצר דווקא שלב הבדיקות: ככל שיש יותר קריאות חיצוניות ותנאים בחוזה, כך נמשך יותר הניתוח של מקרי קצה. אם מודל הנתונים והמעברים מקובעים מראש, המימוש מתנהל בצורה צפויה, והתיקונים לאחר המסירה מצטמצמים למינימום.
אנו עובדים על הענף העדכני של כלי Tezos — Michelson ואמצעי הפיתוח הנלווים, ולכן החוזה נכתב מיד בהתאם לדרישות הנוכחיות של הרשת, בלי לכתוב מחדש לפי תחביר מיושן. תארו את המשימה — נחזור עם הערכה ותוכנית לפי שלבים.







