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
B
C
D |
E
F
H
I
L |
M
O
P
R |
S
T
Z |
נתוני גרסה
- 28 בינואר 2026 גרסה מוקדמת של מהדר
gculpycזמינה למשתתפים בגרסת הטרום-השקה הפרטית של Universal Ledger. מדריך לשימוש בקומפיילר זמין במאמר פריסת חוזה שניתן לתכנות.