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: gcul.AccountField[int]
def __init__(self, symbol: str):
self.symbol = symbol
self.total_supply = 0
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. הוא מכיל שדות (שמירת מצב) ושיטות (לוגיקת עיבוד שפועלת בשדות).
שדות
המצב בחוזה GCULpy מאוחסן בשדות. צריך להצהיר על כל השדות עם סוג סטטי ברמת הכיתה. יש שני סוגים של שדות:
שדות החוזה מכילים ערך יחיד שמאוחסן בחוזה עצמו. בדוגמה של
ERC20Token, השדותsymbol: strו-total_supply: intהם שדות חוזה.שדות החשבון מכילים ערך נפרד לכל חשבון שיוצר אינטראקציה עם החוזה. הם מוצהרים באמצעות
gcul.AccountField[T], כאשרTהוא סוג הערך לכל חשבון. בדוגמה שלERC20Token, השדהbalance: gcul.AccountField[int]הוא שדה של חשבון. כדי שחוזה יוכל לכתוב לנפח האחסון ששמור לחשבון, צריך להעניק לחוזה הרשאת אחסון באופן מפורש. אחרי שהנתונים נשמרים, רק המופע של החוזה יכול לשנות אותם, ולא החשבון.
הגבלה בשדות יש את מגבלות האחסון הבאות. עסקה שחורגת מאחת מהמגבלות האלה תיכשל.
- כל השדות של חוזה חולקים רשומת אחסון אחת, שיכולה להכיל כ-5MiB של נתונים שעברו סריאליזציה.
- כל השדות של החשבון שחוזה מאחסן לכל חשבון בנפרד יכולים להכיל לכל היותר 1MiB של נתונים שעברו סריאליזציה.
Methods
השיטות מגדירות את הלוגיקה של החוזה שניתן להפעיל. הן מתנהגות כמו שיטות של Python ויכולות לקרוא או לשנות את השדות של החוזה.
__init__: הפונקציה constructor מופעלת רק פעם אחת, כשהחוזה נפרס בפעם הראשונה. צריך להקצות ערך לכל שדה חוזה ב-constructor. ה-constructor לא מאתחל את שדות החשבון. הערך של כל חשבון נוצר כשהחשבון הזה מעניק את הרשאת האחסון.שיטות פרטיות: שיטות שמתחילות בקו תחתון (לדוגמה,
_internal_logic) הן פרטיות, ורק שיטות אחרות באותו חוזה יכולות לקרוא להן. המתורגמן של Universal Ledger אוכף את האילוץ הזה.שיטות ציבוריות: כל שיטה שלא מתחילה בקו תחתון (
_) היא ציבורית. כל משתמש עם ההרשאהROLE_CONTRACT_PARTICIPANTיכול לקרוא לשיטות ציבוריות על ידי שליחת טרנזקציה מסוג InvokeContractMethod.
הגבלה שיטות ציבוריות וכל השיטות של @gcul.readonly לא יכולות להשתמש בסוגים ניתנים לשינוי בחתימות שלהן, למשל list או dict. במקום זאת, השתמשו בהצהרות מסוג לקריאה בלבד כמו Sequence או Mapping.
הגבלה במהלך ההפעלה, השיטות מוגבלות לשימוש בחישובים עד לגבול העליון של הרשת. אם חורגים מהמגבלה הזו, העסקה נכשלת.
מחזור החיים של החוזה
בקטעים הבאים מפורטות הפעולות הנפוצות שקשורות למחזור החיים של חוזה GCULpy.
פריסת חוזה
קודם כול, אתם מקמפלים את קוד המקור של GCULpy באמצעות הקומפיילר gculpyc. לאחר מכן, משתמש עם ROLE_CONTRACT_CREATOR יכול לשלוח טרנזקציית CreateContract כדי לפרוס את קוד הבייט המקומפל ברשת Universal Ledger. הוראות מפורטות מופיעות במדריך פריסת חוזה שניתן לתכנות.
עסקה כזו תיראה כך:
client_transaction {
sender_id: "OWNER_ACCOUNT_ID"
create_contract_transaction {
contract_bytes: "COMPILED_BYTECODE"
init_arguments {
key: "symbol"
value: { string_value: "US02079K1079" }
}
}
}
כשהרשת מעבדת את העסקה הזו:
- ה-constructor, כלומר השיטה
__init__, מופעלת עם הארגומנטים שסופקו כדי ליצור מופע חוזה חדש. - השולח של העסקה הופך לבעלים של החוזה.
- מופע החוזה מאוחסן באופן קבוע בספר החשבונות ומוקצה לו מזהה חוזה ייחודי, שמוחזר כחלק מהפלט של העסקה.
מתן הרשאות
כדי שחוזה יוכל לאחסן נתונים בשדה חשבון בשם חשבון, בין אם מדובר בחשבון של משתמש או בחוזה אחר, צריך להעניק הרשאת אחסון לחוזה שמאחסן את הנתונים. זהו שלב אבטחה קריטי.
מתן הרשאות לחשבון משתמש
משתמש עם ROLE_CONTRACT_PARTICIPANT יכול לשלוח טרנזקציית GrantContractPermissions למזהה חוזה ספציפי:
client_transaction {
sender_id: "PARTICIPANT_ACCOUNT_ID"
grant_contract_permissions_transaction {
contract_id: "CONTRACT_ID"
permissions: CONTRACT_PERMISSION_STORAGE
}
}
כשהרשת מעבדת את העסקה הזו:
- אם החוזה לא מגדיר שדות חשבון, העסקה נדחית.
- אם החוזה מגדיר שדות חשבון, כולם מאוכלסים בערכי ברירת מחדל (לדוגמה,
contract.balance[gcul.sender] = 0). לאחר מכן הערכים האלה מאוחסנים במצב העולם כחלק מנתוני החשבון.
מתן הרשאות לחוזה של נציג
משתמש עם ROLE_CONTRACT_PARTICIPANT יכול לשלוח טרנזקציית GrantContractPermissions כדי לאפשר לחוזה אחד לאחסן נתונים בשם חוזה אחר שבבעלותו:
client_transaction {
sender_id: "OWNER_ACCOUNT_ID"
grant_contract_permissions_transaction {
contract_id: "TARGET_CONTRACT_ID"
delegate_contract_id: "DELEGATE_CONTRACT_ID"
permissions: CONTRACT_PERMISSION_STORAGE
}
}
כשהרשת מעבדת את העסקה הזו:
- שולח העסקה, כלומר
sender_id, חייב להיות הבעלים של החוזה שמזוהה על ידיdelegate_contract_id. - חוזה לא יכול להעניק הרשאות אחסון לעצמו, ולכן
delegate_contract_idחייב להיות שונה מ-contract_id. - הרשאות האחסון ניתנות רק לחוזה שצוין ב-
delegate_contract_id, ולא לשולח העסקה. - שדות החשבון שמוגדרים בחוזה היעד,
contract_id, מאותחלים בשביל החוזה המיופה,delegate_contract_id, וכך שיטות בחוזה היעד יכולות לגשת בבטחה לשדות החשבון שמוגדרים באמצעותgcul.callerאוgcul.senderבמהלך קריאות בין חוזים.
הפעלת שיטות חוזה
אחרי שפורסים חוזה ונותנים את ההרשאות הנדרשות, המשתמשים יכולים לבצע אינטראקציה עם החוזה על ידי קריאה ל-methods הציבוריות שלו. משתמש עם ROLE_CONTRACT_PARTICIPANT יכול לשלוח טרנזקציית InvokeContractMethod, ולציין את מזהה החוזה, שם השיטה וערכי הארגומנטים של השיטה.
client_transaction {
sender_id: "PARTICIPANT_ACCOUNT_ID"
invoke_contract_method_transaction {
contract_id: "CONTRACT_ID"
method_name: "mint"
method_arguments {
key: "beneficiary"
value: { account_id: "BENEFICIARY_ID" }
}
method_arguments {
key: "value"
value: { int64_value: 10 }
}
}
}
כשהרשת מעבדת את העסקה הזו:
- מופע החוזה שמשויך ל-
CONTRACT_IDשצוין מאוחזר. - השיטה
mint(beneficiary=Account("BENEFICIARY_ID"), value=10)מופעלת. האובייקטAccountשל המוטב נוצר ומאומת על ידי סביבת זמן הריצה. הלוגיקה של ה-method יכולה להניח שהמזהה שסופק תקין ומתייחס לחשבון קיים בספר החשבונות. - אם השיטה נכשלת מסיבה כלשהי, העסקה תיכשל ולא יבוצעו עדכונים במצב החוזה.
- אם השיטה מצליחה, המצב המעודכן של החוזה מתועד במצב הכללי.
מפרט השפה
GCULpy מיועדת לבטיחות ולחיזוי, ולכן היא לא מאפשרת כמה תכונות של Python. התכונות האלה יסומנו בתווית Restriction. המגבלות האלה נועדו להיות תכונות קבועות של השפה, והן נוספו כדי שיהיה קל יותר לקרוא, לבדוק ולנתח באופן סטטי את הלוגיקה של החוזה, וכדי להגביל התנהגויות מפתיעות או לא בטוחות.
תכונות אחרות מסומנות בתווית Roadmap. הן נמצאות במפת הדרכים להטמעה, אבל הקומפיילר של gculpyc עדיין לא תומך בהן.
סוגים
GCULpy תומכת במגוון סוגי משתנים נפוצים, עם דגש חזק על הקלדה סטטית.
סוגי ערכים עיקריים:
- כבר יש תמיכה ב-
int,bool,str,None. - מפת דרכים
Decimal,bytes,Enumנמצאים במפת הדרכים. - ההגבלה
floatו-complexלא מותרת. - ערכי Restriction
intהם מספרים שלמים חתומים בטווח 64 ביט, מ--263 עד 263 - 1. ליטרל של מספר שלם מחוץ לטווח הזה יגרום לשגיאה בזמן הקימפול, ופעולה שהתוצאה שלה מחוץ לטווח הזה תגרום לשגיאת זמן ריצה. - הגבלה הערכים של
strיכולים להיות באורך של עד 1,000,000 תווים. מחרוזת ארוכה יותר תגרום לשגיאה בזמן ההידור. אם תעבירו מחרוזת ארוכה יותר כארגומנט של method, או אם תיצרו מחרוזת כזו כתוצאה של פעולה, תתקבל שגיאת זמן ריצה.
סוגי המאגרים:
-
listוגםdict:-
list[T]: רצף שניתן לשינוי. תומך בהוספה לאינדקס, לדוגמהmy_list[0], ובשיטהappend(). -
dict[K, V]: מיפוי שניתן לשינוי. תומך בהוספה לאינדקס, לדוגמהmy_dict[key], ובשיטהpop().
-
-
collections.abc.Sequenceוגםcollections.abc.Mapping:-
collections.abc.Sequence[T]: הפניה לקריאה בלבד לרצף. תומך בקריאת רכיבים לפי אינדקס, לדוגמהmy_seq[0], אבל אי אפשר לשנות או למחוק רכיבים. list[T]הוא סוג משנה שלSequence[T], כך שאפשר להעביר, להחזיר או להקצות ערךlist[T]או ליטרל של רשימה ליעד שהוקלד כ-Sequence[T], אבל אי אפשר להקצותSequence[T]ל-list[T]. -
collections.abc.Mapping[K, V]: הפניה לקריאה בלבד למיפוי. אפשר לקרוא רשומות לפי מפתח, למשלmy_map[key], אבל אי אפשר לשנות או למחוק רשומות. dict[K, V]הוא סוג משנה שלMapping[K, V], כך שאפשר להעביר, להחזיר או להקצות ערךdict[K, V]או מילון מילולי ליעד שהוקלד כ-Mapping[K, V], אבל אי אפשר להקצותMapping[K, V]ל-dict[K, V].
-
- מפת דרכים
tuple,set,dataclassנמצאים במפת הדרכים. - הגבלה צריך לציין סוגים קונקרטיים לערכים במאגר, לדוגמה
dict[str, int]מותר, אבלdictאוdict[str, Any]פשוטים אסורים. - מפת דרכים חיתוך רשימה, לדוגמה
my_list[1:3]. - מפת דרכים שיטות אחרות, כמו
.get(),.keys(),.values(),.items(),.clear()או.extend().
מגבלה כל המשתנים – כולל שדות של חוזים וחשבונות, פרמטרים של פונקציות וסוגי החזרה – חייבים להיות מוגדרים ומוקלדים באופן סטטי. אי אפשר לשנות את הסוג שלהם בזמן הריצה, ורק סוגים קונקרטיים נתמכים. אי אפשר להשתמש בסוגים כערכים, למשל אי אפשר לאחסן אותם במשתנים או להעביר אותם לפונקציות כארגומנטים. ניסיון להקצות ערך לשדה שלא הוגדר יגרום לשגיאה בזמן הקימפול.
מגבלה צריך להקצות משתנה מקומי לפני שקוראים אותו. למשתנים מקומיים אין ערך ברירת מחדל, ולכן קריאה של משתנה לפני שהוא מוקצה גורמת לשגיאת זמן ריצה.
מחלקות וירושה
אפשר להגדיר רק מחלקות שהן מחלקות משנה ישירות של gcul.Contract או של gcul.ContractInterface.
הכלל המחמיר הזה מונע את המורכבות של ירושה מלאה ב-Python, שעלולה לגרום לבאגים שקשה למצוא ולהקשות על הבנת הקוד. מטעמי אבטחה, ניסיון לשנות מאפיין או שיטה ממחלקת הורה יגרום לשגיאה, כדי להגן מפני התנהגות לא צפויה.
מפת דרכים: GCULpy תציע גמישות רבה יותר תוך שמירה על עקרונות הליבה שלה. מפת הדרכים כוללת תמיכה בירושה יחידה במחלקות שמוגדרות על ידי המשתמש, עם שיטה לביטול מפורש שמנוהלת באמצעות מעצב @override. בנוסף, הפונקציה המובנית super() תהיה נתמכת רק בצורה ללא ארגומנטים, כדי להבטיח פעולות ישירות וצפויות.
המודול gcul
GCULpy מספק מודול gcul מובנה עם סוגים, מעצבים ומשתנים חיוניים לפיתוח חוזים.
כיתה gcul.Contract
המחלקה הבסיסית לכל החוזים. אי אפשר ליצור מופעים של חוזה באופן ישיר. חוזים נוצרים רק באמצעות טרנזקציות של CreateContract. אי אפשר לשנות שיטות ומאפיינים מהמחלקה הבסיסית gcul.Contract במחלקות משנה.
Contract.is_owner(account: Account) -> boolהפונקציה מחזירה את הערך
Trueאם החשבון שצוין הוא הבעלים של החוזה.Contract.call(target: ContractInterface, payment: Amount | None = None) -> ContractMethodsמתחילה אינטראקציה בין חוזים עם
target. במקום להפעיל שיטות ישירות ביעד (למשל,target.foo()), צריך להשתמש ב-self.call()כדי לנתב באופן מפורש את ההפעלה דרך זמן הריצה של ה-ledger. המנגנון הזה מחזיר אובייקט proxy מאובטח שמשמש להפעלת שיטות ציבוריות תואמות בחוזה היעד, כדי לוודא שגבולות הביצוע נאכפים בקפדנות וכלpaymentמועבר בצורה בטוחה. פרטים נוספים זמינים במאמר ContractInterface.צריך לספק
paymentכשמפעילים method שמעוטר ב-@gcul.payable, ולהשמיט אותו ב-methods שלא ניתנים לתשלום.החוזה
targetחייב להיות שונה. קריאה לחוזה הנוכחי דרךself.call()גורמת לשגיאת זמן ריצה. כדי להפעיל שיטה אחרת באותו חוזה, מפעילים אותה ישירות, לדוגמה,self.foo().Contract.native_transfer(beneficiary: Account, amount: Amount) -> Noneהעברה של סכום בתוך החוזה ישירות מהיתרה של החוזה אל
beneficiary.Contract.id: strמזהה מחרוזת ייחודי של החוזה.
כיתה gcul.Account
סוג מובנה שמייצג חשבון של משתמש או של חוזה בספר החשבונות. אי אפשר ליצור אובייקטים מסוג gcul.Account באופן ישיר. סביבת זמן הריצה יוצרת אותם בשבילכם ומספקת אותם כארגומנטים של פונקציות או שיטות. כדי להעביר חשבון כארגומנט של עסקה, צריך לספק AccountValue שמכיל את מזהה החשבון. סביבת זמן הריצה מאמתת את המזהה וממירה אותו לאובייקט חשבון מלא. אם המזהה לא שייך לחשבון רשום, העסקה נכשלת. כך תוכלו לוודא שאתם עובדים רק עם חשבונות תקינים.
ההגדרה של המחלקה מקבילה בערך ל:
@dataclasses.dataclass(frozen=True)
class Account:
"""A valid account on the ledger."""
id: str # The ID of the account as a string.
Account.id: strמזהה מחרוזת ייחודי של החשבון.
כיתה gcul.Amount
מייצג ערך כספי שנקוב במטבע ספציפי בספר החשבונות.
יוצרים מופע של סכום באמצעות gcul.Amount(value: int, currency: gcul.Currency). ההגדרה של המחלקה שווה בערך ל:
@dataclasses.dataclass(frozen=True)
class Amount:
"""Represents an amount denominated in a ledger currency."""
value: int
currency: Currency
כיתה gcul.Currency
מייצג מטבע שמוגדר במלואו בספר החשבונות. ההגדרה של המחלקה שווה בערך ל:
@dataclasses.dataclass(frozen=True)
class Currency:
"""Represents a fully qualified currency on the ledger."""
code: str
operator_id: str
כיתה gcul.ContractInterface
המחלקה הבסיסית להצהרה על הממשק הציבורי של חוזה.
אי אפשר לשנות שיטות ומאפיינים מהמחלקה הבסיסית gcul.ContractInterface במחלקות משנה. בנוסף, השיטות בהצהרת הממשק לא יכולות לכלול קוד, אלא רק את ....
כדי שחוזה יפעיל שיטות בחוזה אחר שנפרס ב-ledger, צריך להצהיר על מחלקת ממשק שעוברת בירושה מ-gcul.ContractInterface. ה-stubs של השיטות חייבים להתאים להצהרה שלהם בחוזה היעד. זה כולל ארגומנטים, סוג החזרה והערות של מעצבים כמו @gcul.readonly או @gcul.payable.
בתוך קוד החוזה, משתמשים ב-self.call(target, payment) כדי להפעיל שיטה מחוזה אחר.
הדוגמה הבאה ממחישה איך חוזה TokenVendor יוצר אינטראקציה עם חוזה אחר שמטמיע את ERC20Interface. הוא משתמש ב-self.call() כדי להעביר אסימונים לקונה בתמורה לתשלום במטבע שרשום בספר החשבונות:
class ERC20Interface(gcul.ContractInterface):
"""Interface for an external ERC20 token contract."""
def transfer(self, beneficiary: gcul.Account, value: int) -> int:
...
class TokenVendor(gcul.Contract):
"""A vendor contract that distributes ERC20 tokens."""
price_per_token: int
def __init__(self, price_per_token: int) -> None:
self.price_per_token = price_per_token
@gcul.payable
def purchase_tokens(self, token: ERC20Interface, quantity: int) -> int:
"""Accepts on-ledger currency for a specific quantity of ERC20 tokens."""
# Enforce positive quantity.
assert quantity > 0, "Quantity must be positive"
# Calculate the required payment.
required_payment = quantity * self.price_per_token
assert gcul.payment.value == required_payment, "Incorrect payment amount"
# Invokes `transfer` on the external ERC20 token contract
# to move tokens from the vendor's own balance to the caller.
return self.call(token).transfer(gcul.caller, quantity)
אפשר להשתמש בסוגי ממשקים גם כשדות חוזה, ולא רק כפרמטרים של שיטות. אחסון של ממשק בשדה מאפשר לחוזה להחזיק הפניה לחוזה אחר בין עסקאות, ולהחליף את ההפניה הזו מאוחר יותר. בחוזה לדוגמה הבא נעשה שימוש ב-ERC20Interface שהוגדר קודם:
class Router(gcul.Contract):
"""Holds a replaceable reference to an ERC20 token contract."""
target: ERC20Interface
def __init__(self, target: ERC20Interface) -> None:
self.target = target
def set_target(self, new_target: ERC20Interface) -> None:
assert self.is_owner(gcul.sender), "Only the owner can set the target"
self.target = new_target
כיתה gcul.AccountField
סוג גנרי מובנה להצהרה על שדות בחשבון. gcul.AccountField[T]
מכיל ערך נפרד מסוג T לכל חשבון שנתן לחוזה הרשאת אחסון. הוא מתנהג כמו מיפוי עם מפתחות gcul.Account, שבהם אפשר להשתמש כדי לקרוא או לכתוב ערך לכל חשבון ספציפי.
לדוגמה:
class Token(gcul.Contract):
balance: gcul.AccountField[int]
def deposit(self, account: gcul.Account, value: int) -> None:
self.balance[account] = value # Writes the value for one account.
def get_balance(self, account: gcul.Account) -> int:
return self.balance[account] # Reads the value for one account.
השדה T חייב להיות מסוג שיש לו ערך ברירת מחדל, ולכן אי אפשר להשתמש ב-gcul.Account, ב-gcul.Amount, ב-gcul.Currency ובסוגי ממשקים כערכים של שדות בחשבון. כשחשבון מעניק הרשאת אחסון לחוזה, כל שדה בחשבון מאוכלס בערך ברירת המחדל של הסוג שלו באותו חשבון.
הגבלה הפעולה היחידה שנתמכת בשדה חשבון היא הוספה לאינדקס באמצעות חשבון. אי אפשר להקצות את השדה עצמו למשתנה, להעביר אותו כארגומנט, להחזיר אותו מפונקציה או לבצע עליו איטרציה.
הגבלה אי אפשר למחוק את הרשומות בשדות של החשבון. אפשר להחליף ערך שמור, אבל הרשומה עצמה היא קבועה.
הגבלה שדות החשבון של gcul.sender, gcul.caller וכל gcul.Account שמועבר כארגומנט של שיטה (כולל בתוך רשימות או מאגרים אחרים) נטענים לפני הפעלת השיטה. בחשבונות אחרים, טעינה מדורגת של שדות חשבון בעסקה אחת יכולה להתבצע לכל היותר ל-15 זוגות (contract, account) נפרדים. גישה לשדה חשבון של אותו זוג כמה פעמים בעסקה נספרת רק פעם אחת. עסקה שחורגת מהמגבלה הזו תיכשל.
@gcul.readonly
מוסיפה הערה לשיטת חוזה כשיטה לקריאה בלבד. הצהרות על שיטות עם המעצב הזה לא יכולות לשנות את מצב החוזה או לבצע העברות מטבע ב-ledger. הם גם לא יכולים להשתמש בסוגי מאגרים ניתנים לשינוי בחתימות שלהם, למשל list או dict. במקום זאת, הם צריכים להשתמש בהצהרות מסוג לקריאה בלבד, כמו Sequence או Mapping.
@gcul.payable
הוספת הערה לשיטת חוזה שדורשת ביצוע תשלום. אם שיטה מצפה לקבל תשלום במטבע שרשום בספר החשבונות באמצעות gcul.payment, צריך לסמן אותה במעטר הזה, אחרת העסקה תיכשל.
gcul.sender: gcul.Account
מכיל הפניה לחשבון שחתם על העסקה הנוכחית של InvokeContractMethod ושלח אותה. הערך של gcul.sender לא משתנה במהלך ביצוע העסקה, גם אם היא כוללת קריאות בין חוזים, בניגוד ל-gcul.caller שמפנה למבצע הקריאה המיידי.
gcul.caller: gcul.Account
מכיל הפניה למבצע הקריאה המיידי של ה-method הנוכחי. בעסקאות ישירות של משתמשים, הערך של gcul.caller תואם לערך של gcul.sender ומצביע על מי ששלח את העסקה. בקריאות בין חוזים, gcul.caller תואם למזהה החשבון של החוזה שמבצע את הקריאה. קריאות פנימיות בתוך חוזה לא משנות את הערך של gcul.caller.
gcul.payment: gcul.Amount
התשלום מועבר לאמצעי תשלום שמופיע לידו הסמל @gcul.payable.
גישה אל gcul.payment בתוך שיטה שלא ניתן לשלם בה גורמת לשגיאה בזמן הידור.
אופרטורים
רוב האופרטורים שזמינים ב-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. - מנהלי הקשר של Roadmap והצהרות
with.
הגבלה GCULpy היא בכוונה לא שלמה לפי טיורינג כדי למנוע לולאות אינסופיות ולאפשר ניתוח סטטי. כך זה מתבצע:
- אין לולאות אינסופיות: מותר לבצע איטרציה רק באמצעות לולאות
forעל מאגרים סופיים או רצפים מוגבלים עםrange(...). אסור להשתמש בלולאותwhile. חלק מהעדכונים של מאגרים, למשל הוספה או הסרה של רכיבים לרשימה או מפתחות למילון, אסורים בזמן איטרציה עליהם. - אין רקורסיה: פונקציה לא יכולה לקרוא לעצמה, באופן ישיר או עקיף. סביבת זמן הריצה מבצעת בדיקות סטטיות ובדיקות בזמן ריצה כדי לזהות ולדחות את השימוש ברקורסיה.
- אין זרימת בקרה אסינכרונית: כדי לשמור על עקרונות התכנון המרכזיים של יכולת חיזוי, אבטחה והפעלה דטרמיניסטית, אסור להשתמש בפרימיטיבים של
async. פעולות אסינכרוניות מקשות על ההבנה של בקרת הזרימה של התוכנית, ולעיתים קרובות מובילות לנקודות חולשה ולמרוצי תהליכים.
פונקציות מובנות
GCULpy תומכת בפונקציות המובנות הבאות:
abs(x)הפונקציה מחזירה את הערך המוחלט של מספר שלם
x. הארגומנטים חייבים להיות מסוגint.len(s)הפונקציה מחזירה את האורך, כלומר מספר הפריטים, של מחרוזת או של מאגר סופי.
max(iterable)
max(arg1, arg2, *args)הפונקציה מחזירה את הפריט הגדול ביותר ב-iterable או את הארגומנט הגדול ביותר מבין שני ארגומנטים או יותר. קריאה ל-
max()על איטרבל ריק גורמת לשגיאת זמן ריצה.min(iterable)
min(arg1, arg2, *args)הפונקציה מחזירה את הפריט הקטן ביותר במערך שניתן לעבור עליו בלולאה, או את הערך הקטן ביותר מבין שני ארגומנטים או יותר. קריאה ל-
min()על איטרבל ריק גורמת לשגיאת זמן ריצה.range(stop)
range(start, stop[, step=1])יוצרת סדרה חשבונית של מספרים שלמים עם מספר שלם שאינו אפס
step.הגבלה אפשר להשתמש רק בשורה עם לולאות
for, לדוגמהfor i in range(10): .... אי אפשר להקצות אובייקטים של טווחים למשתנים, לאחסן אותם בשדות, להעביר אותם כארגומנטים לפונקציות או לשלוח אליהם שאילתות באמצעותlen().
מפת דרכים הפונקציות המובנות הבאות נמצאות במפת הדרכים להטמעה:
|
A
B
C
D |
E
F
H
I
L |
O
P
R |
S
T
Z |
נתוני גרסה
3 בנובמבר 2026. השקנו את גרסת ה-GA של המקמפל
gculpyc(v0.2.0), כולל תמיכה בקריאות בין חוזים, העברות מטבע ותשלומים ב-ledger, שיטות@gcul.readonly, list, הצהרות נוספות של זרימת בקרה ופונקציות מובנות.28 בינואר 2026. גרסה מוקדמת של הקומפיילר של
gculpycזמינה למשתתפים בגרסת טרום-השקה פרטית של Universal Ledger. במדריך פריסת חוזה שניתן לתכנות מוסבר איך משתמשים בקומפיילר.