השפה של GCULpy

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

בדף הזה מופיע חומר עזר למפרט השפה GCULpy, שכולל את מושגי הליבה ואת מחזור החיים של חוזה ברשת Universal Ledger.

מושגי ליבה

החוזה הבא מגדיר אסימון ERC20 לדוגמה ב-GCULpy:

import gcul

class ERC20Token(gcul.Contract):
    """Sample ERC20 implementation for the Universal Ledger."""

    symbol: str
    total_supply: int
    balance: dict[gcul.Account, int]

    def __init__(self, symbol: str):
        self.symbol = symbol

    def mint(self, beneficiary: gcul.Account, value: int) -> int:
        """Mints tokens to the given beneficiary."""
        assert self.is_owner(gcul.sender), "Only the owner can mint"
        assert value >= 0, "Mint amount must be non-negative"
        self.total_supply += value
        self.balance[beneficiary] += value
        return value

    def transfer(self, beneficiary: gcul.Account, value: int) -> int:
        """Transfers tokens from the sender to the given beneficiary."""
        assert value >= 0, "Transfer amount must be non-negative"
        assert (
            value <= self.balance[gcul.sender]
        ), "Sender does not have enough balance"
        self.balance[gcul.sender] -= value
        self.balance[beneficiary] += value
        return value

חוזה GCULpy הוא מחלקה שעוברת בירושה מ-gcul.Contract. הוא מכיל שדות (לאחסון מצב) וmethods (לוגיקת עיבוד שפועלת על שדות).

שדות

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

  • שדות חוזה מכילים ערך יחיד שמאוחסן בחוזה עצמו. בדוגמה ERC20Token, השדות symbol: str ו-total_supply: int הם שדות חוזה.

  • שדות החשבון שומרים ערך נפרד לכל חשבון משתמש שמבצע אינטראקציה עם חוזה. הם תמיד מוצהרים כמילון (dict) עם gcul.Account כמפתח, כמו balance: dict[gcul.Account, int]. כדי שחוזה יוכל לכתוב לחשבון של משתמש, המשתמש צריך להעניק לחוזה הרשאת אחסון באופן מפורש. אחרי שהנתונים נשמרים, רק מופע החוזה יכול לשנות או למחוק אותם – המשתמש לא יכול.

Methods

שיטות מגדירות את הלוגיקה הניתנת להרצה של חוזה. הם מתנהגים כמו פונקציות (methods) של Python ויכולים לקרוא או לשנות את השדות של החוזה.

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

  • שיטות פרטיות: שיטות שמתחילות בקו תחתון (לדוגמה, _internal_logic) הן פרטיות ואפשר להפעיל אותן רק על ידי שיטות אחרות באותו חוזה. המגבלה הזו נאכפת על ידי המפענח של Universal Ledger.

  • ‫methods ציבוריות: כל method שלא מתחילה בקו תחתון (_) היא ציבורית. כל משתמש עם הרשאת הגישה ROLE_CONTRACT_PARTICIPANT יכול לקרוא לשיטות ציבוריות על ידי שליחת טרנזקציה מסוג InvokeContractMethod.

מחזור החיים של חוזה

בקטעים הבאים מפורטות הפעולות האופייניות שמתבצעות במהלך מחזור החיים של חוזה GCULpy.

פריסת חוזה

קודם, קומפלו את קוד המקור של GCULpy באמצעות הקומפיילר gculpyc. לאחר מכן, משתמש עם ROLE_CONTRACT_CREATOR יכול לשלוח טרנזקציה של CreateContract כדי לפרוס את קוד הבייט המהודר ברשת Universal Ledger. הוראות מפורטות זמינות במדריך בנושא פריסת חוזה ניתן לתכנות.

עסקה כזו תיראה כך:

client_transaction {
  sender_id: "OWNER_ACCOUNT_ID"
  app {
    [type.googleapis.com/google.cloud.universalledger.v1.CreateContract] {
      contract_bytes: "COMPILED_BYTECODE"
      arguments {
        key: "symbol"
        value: { str_value: "US02079K1079" }
      }
    }
  }
}

כשהרשת מעבדת את העסקה הזו:

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

מתן הרשאות

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

client_transaction {
  sender_id: "PARTICIPANT_ACCOUNT_ID"
  app {
    [type.googleapis.com/google.cloud.universalledger.v1.GrantContractPermissions] {
      contract_id: "CONTRACT_ID"
      permissions: CONTRACT_PERMISSION_STORAGE
    }
  }
}

כשהרשת מעבדת את העסקה הזו:

  • אם בחוזה לא מוגדרים שדות של חשבון, העסקה נדחית.
  • אם החוזה מגדיר שדות של חשבון, כולם מאוכלסים בערכי ברירת מחדל (לדוגמה, contract.balance[gcul.sender] = 0). הערכים האלה מאוחסנים במצב העולמי כחלק מנתוני החשבון, ושולח העסקה נרשם כמשתתף במופע החוזה הספציפי הזה.

הפעלת methods של חוזים

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

client_transaction {
  sender_id: "PARTICIPANT_ACCOUNT_ID"
  app {
    [type.googleapis.com/google.cloud.universalledger.v1.InvokeContractMethod] {
      contract_id: "CONTRACT_ID"
      method_name: "mint"
      arguments {
        key: "beneficiary"
        value: { account_id: "BENEFICIARY_ID" }
      }
      arguments {
        key: "value"
        value: { int_value: 10 }
      }
    }
  }
}

כשהרשת מעבדת את העסקה הזו:

  • מתבצע אחזור של מופע החוזה שמשויך ל-CONTRACT_ID שצוין.
  • השיטה mint(beneficiary=Account("BENEFICIARY_ID"), value=10) מופעלת. אובייקט Account של המוטב נוצר ומאומת על ידי זמן הריצה. הלוגיקה של השיטה יכולה להניח בבטחה שהמזהה שסופק תקין ומתייחס לחשבון קיים בספר הראשי.
  • אם השיטה תיכשל מסיבה כלשהי, העסקה תיכשל ולא יבוצעו עדכונים במצב החוזה.
  • אם השיטה מצליחה, המצב המעודכן של החוזה נרשם במצב העולמי.

מפרט השפה

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

תכונות אחרות מסומנות בתווית Roadmap. התכונות האלה מופיעות בתוכנית הפיתוח, אבל עדיין לא נתמכות על ידי קומפיילר gculpyc.

סוגים

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

סוגי ערכים עיקריים:

  • הכרטיסים int,‏ bool,‏ str ו-None כבר נתמכים.
  • Roadmap Decimal, bytes, Enum נמצאים בתוכנית.
  • אסור להשתמש בהגבלה float ובcomplex.

סוגי מאגרים:

  • יש כבר תמיכה ב-dict.
  • Roadmap list, tuple, set, dataclass נמצאים בתוכנית.
  • הגבלה צריך לציין סוגים קונקרטיים לערכים במאגר, למשל dict[str, int] מותר, אבל dict או dict[str, Any] אסורים.
  • יש תמיכה בהטמעה של מאגרי תגים בתוך מאגרי תגים אחרים, לדוגמה dict[str, list[int]].

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

מחלקות וירושה

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

תוכנית הפיתוח GCULpy יציע גמישות רבה יותר תוך שמירה על עקרונות הליבה שלו. תוכנית הפיתוח כוללת תמיכה בירושה יחידה בכיתות שהוגדרו על ידי המשתמש, עם החלפה של שיטות שמנוהלת באופן מפורש באמצעות קישוט @override. בנוסף, הפונקציה המובנית super() תיתמך רק בפורמט ללא ארגומנטים, כדי להבטיח פעולות ישירות וצפויות.

המודול gcul

‫GCULpy מספק מודול gcul מובנה עם סוגים ומשתנים חיוניים לפיתוח חוזים.

כיתה gcul.Contract

מחלקה בסיסית לכל החוזים. אי אפשר ליצור מופעים שלו ישירות. חוזים מופעלים רק באמצעות טרנזקציות של CreateContract. אי אפשר לשנות את השיטות והמאפיינים ממחלקת הבסיס gcul.Contract במחלקות משנה.

  • Contract.is_owner(account: Account) -> bool

    הפונקציה מחזירה True אם החשבון שצוין הוא הבעלים של החוזה.

כיתה gcul.Account

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

ההגדרה של המחלקה שווה בערך ל:

@dataclasses.dataclass(frozen=True)
class Account:
  """A valid account on the ledger."""

  id: str  # The ID of the account as a string.

gcul.sender: gcul.Account

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

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

אופרטורים

רוב האופרטורים שזמינים ב-Python נתמכים ב-GCULpy ופועלים כמצופה.

  • חיבור (+) וחיסור (-), כולל צורות אונאריות ובינאריות.
  • כפל (*), חילוק רצפה (//) ומודולו (%).
  • העלאה בחזקה (**) עבור מעריכים חיוביים.
  • השוואות (<, ‏ <=, ‏ >, ‏ >=, ‏ ==, ‏ !=).
  • פעולות AND ברמת הביטים (&), OR ברמת הביטים (|), ‏ XOR ברמת הביטים (^), הזזה שמאלה (<<), הזזה ימינה (>>), שלילה (~).
  • פעולות בוליאניות (and, or, not).
  • מפת דרכים זהות של אובייקט (is).
  • הגבלה לא ניתן להשתמש במעריכים שליליים, והשימוש בהם יוביל לשגיאת זמן ריצה.
  • הגבלה חלוקה אמיתית (/), כי סוג ההחזרה שלה הוא float, לא מותרת וגורמת לשגיאה בזמן ההידור.

בקרת זרימה

רוב משפטי בקרת הזרימה מ-Python פועלים ב-GCULpy עם אותה סמנטיקה:

  • pass דוחות.
  • קריאות פנימיות לפונקציות (אותו חוזה, לא רקורסיבי).
  • assert דוחות.
  • if ... then .. else ... דוחות.
  • for VAR in CONTAINER דוחות.
  • תכנון קריאות לפונקציות חיצוניות (לכל חוזה אחר, לא רקורסיבי).
  • מפת דרכים break והצהרות continue.
  • מפת דרכים raise והצהרות try ... except.
  • הצהרות match במפת הדרכים.
  • מפת דרכים generators והצהרות yield.
  • מנהלי הקשר של תוכנית הדרך וwithהצהרות.

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

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

פונקציות מובנות

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

A
abs()
all()
any()

B
bin()
bool()
bytes()

C
chr()

D
dict()
divmod()

E
enumerate()

F
format()
frozenset()

H
hash()
hex()

I
id()
int()

L
len()
list()

M
max()
min()

O
oct()
ord()

P
pow()
property()

R
range()
repr()
reversed()

S
set()
sorted()
staticmethod()
str()
sum()
super()

T
tuple()

Z
zip()

נתוני גרסה

  • 28 בינואר 2026 גרסה מוקדמת של מהדר gculpyc זמינה למשתתפים בגרסת הטרום-השקה הפרטית של Universal Ledger. מדריך לשימוש בקומפיילר זמין במאמר פריסת חוזה שניתן לתכנות.