אולי אחד הפוסטים היותר חשובים בנושא של טייפים, לפונקציות ישנן אפשרויות רבות להגדרה ואשתדל לעשות זאת בפוסט אחד וברור ומי שעדיין לא ברור לו שיעבור שוב על הפוסט ואם עדיין יש שאלות תרגישו חופשי לשים תגובות. כמו כן יכול להיות שאשאיר חלק מההגדרות להמשך, אך מה שבטוח אעבור על הדברים העיקריים בנושא הגדרות של פונקציות וכל מי שידע כיצד להגדיר פונקציות היטב הוא בכיוון הנכון של להיות מפתח בלוקציין (סולידיטי) .
הגדרות בסיסיות לפונקציות בסולידיטי
נתחיל מלהתבונן בדוגמה הראשונה והפשוטה הבאה של פונקציה:
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; contract FunctionsTypes { uint favNumber = 10; constructor() { } function getFaveNumber(uint x) external pure returns(uint) { return x; } }
זוהי דוגמה פשוטה של פונקציה ונעבור בה על המשתנים השונים המגדירים פונקציה, למשל:
כל פונקציה מוגדרת בתוך contract , ומתחילה במילה function. לאחר מכן שם הפונקציה (כרצונכם, רק להקפיד על camel case) . לאחר מכן המשתנים של הפונקציה (אם יש) , הכוונה בתוך הסוגריים העגולים , יגיעו המשתנים שהפונקציה מקבלת, וגם להם יינתנו ההגדרות המתאימות , למשל במקרה שלנו uint, וגם יכול להיות הגדרה של סוג המידע כפי שדיברנו בפוסט קורס סולידיטי – חלק ראשון – memory, calldata and storage .
לאחר מכן תגיע הגדרה חשובה (בדומה ל access modifiers אבל מעט יותר משוכלל הייתי אומר – מה שנקרא גם visibility modifiers). אפשר לראות בדוגמה את הביטוי external אך יש 4 אפשרויות שונות:
- external – הגדרה זו אחת החשובות והשימושיות ביותר, מאפשרת שימוש מחוץ לחוזה (ואין אפשרות לשימוש בתוך החוזה) כמו כן חוזים וכתובות אחרים כן יכולים לגשת לפונקציה זו.
- internal – פונקציה בעלת הגדרה זו תהיה זמינה רק לטובת החוזה שבה הוגדרה אך כן תהיה גם זמינה מחוזים אחרים שהם יורשים (inherit) של חוזה זה. הגדרה זו דומה לprivate רק שזמינה גם לחוזים יורשים של חוזה זה.
- public – כל אחד יכול לגשת לפונקציה זו ולעשות בה שימוש כולל החוזה עצמו.
- private – פונקציה בעלת הגדרה זו תהיה זמינה רק לשימוש החוזה בלבד. למשל איזו פונקציית util שנעשה בה שימוש בחוזה זה בלבד ושום שימוש חיצוני לא יוכל לגשת אליה. לפעמים תראו כי משתמשים בקונבנציה nameOfFunction_ בפונקציות אלו. כמו כן לאחר deploy לא תמצאו את הפונקציה זמינה כמובן.
חשוב לפרט מעט יותר על ההבדלים בין ההגדרות public ו external. מכוון שיותר קשה להבחין בהבדלים בניהם ושימושם מהותי ועלול גם לעלות לכם יותר גז עם שימוש לא נכון.
ההבדל בין external and public
החלטתי לתת מקום לנושא זה בפוסט וקצת לשפוך יותר מידע מכוון שהרבה שואלים על ההבדלים הללו, ושואלים למה להשתמש בexternal ולא ב public?
שימוש בpublic יעלה לכם יותר גז , מכוון שכל מידע מועתק מועתק ל memory בעת שימוש ב public ושימוש עם בexternal קורא את המידע שנשמר כ calldata ולכן שימוש הרבה יותר יעיל עם external.
להלן דוגמה דיי מוכר בדיונים השונים בין שני הגדרות אלו:
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; contract Test { function test(uint[20] a) public pure returns (uint){ // 496 gas return a[10]*2; } function test2(uint[20] a) external pure returns (uint){ // 261 gas return a[10]*2; } }
אפשר לראות כי בדוגמה הזו את ההבדלים בין עלויות הגז. מכוון שמערך a מועתק ישירות לmemory . כמו כן אם תגדירו את המערך להיות calldata מצבכם יהיה יותר טוב. אך עדיין העתקה תתקיים ועדיין מחיר הגז יהיה גבוה.
הגדרת הפונקציה
אחרי שהבנו את ארבעת ההגדרות שיכולות להיות עבור פונקציות ונמשיך לעיין בדוגמה נראה שישנן פונקציות שמוגדרות כ view או prue. השימוש בהם דומה ואסביר בהמשך. הגדרות אלו, מצביעות על כך שהפונקציות הן read only ואין להם יכולת לשנות מידע בבלוקציין. למשל:
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; contract Test { uint constant FAV_NUMBER = 10; uint s_value; constructor() {} function getValue() external view returns(uint) { return s_value; } function setValue() external { s_value = 2; } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; contract Test { uint constant FAV_NUMBER = 10; uint s_value; constructor() {} function getValue() external pure returns(uint) { return FAV_NUMBER; } function setValue() external { s_value = 2; } }
אוקי אז למעשה מה שאפשר לראות כאן זה שפונקציה שלא עושה קריאה מהבלוקציין היא יכולה להיות תחת הגדרה של pure ובעוד שview כן עושה שימוש של קריאה מהבלוקציין . פונקציות אלו לא יכולות לעשות שינוי בבלוקציין כלל. אם תנסו לשנות את הערך של s_value אתם תקבלו שגיאה כבר בקימפול של החוזה.
פונקציות שלא ניתנו להן הגדרות אלו יהיו פונקציות שכן יכולות לעשות שינויי בבלוקציין, לרוב פונקציות שעולות יותר גז אז כמובן שיש להקפיד על הגדרות אלו. למשל setValue בדוגמאות למעלה. נחמד יהיה לציין כי הגדרות אלו עושות שימוש בהAPI של סולידיטי כך שפונקציות קריאה שולחות eth_call בעוד שפונקציות ללא הגדרה שולחות בקשה שנקראת eth_sendTransction.
שימוש עם רמיקס:
כמו כן פונקציות שהן קריאה בלבד יהיו בצבע כחול ופונקציות כתיבה יהיו בצבע צהוב.

הגדרת תשובת הפונקציה
כפי שרובנו מכירים יש חשיבות גדולה להגדרת הערכים שחוזרים מהפונקציה, וכן בהרבה שפות מגדירים לפונקציה מה אנו מצפים שהיא תחזיר לנו. וגם בסולידיטי , אנחנו מתבקשים להגדיר זאת. אז במידה ונרצה שפונקציה תחזיר משהו נגדיר זאת כך (כמו כן אפשר לראות גם בדוגמאות למעלה)
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; contract Test { uint constant FAV_NUMBER = 10; uint s_value; constructor() {} function testReturns(uint[20] calldata a) external pure returns (uint) { return a.length; } }
כפי שאפשר לראות, נשתמש ראשית במונח returns ובסוגריים את ההגדרה של מה שאנו מחזירים. אם לא תגדירו זאת וכן יהיה לכם תשובה מהפונקציה , תקבלו שגיאה בתהליך הקימפול.
פונקציה מסוג payable
ייחוד נוסף בסולידיטי הוא ה payable modifier , פונקציות מסוג זה יהיו פונקציות שמעבירות תשלום אל החוזה ומחוצה לו. אולי אחד הדברים היותר שימושיים וחשובים. ה99 אחוז מהחוזים שנכתבים בסולידיטי ממשים פונקציות מסוג זה, הרי כל המשמעות מאחורי בלוקציין מבוסס בטבעות מבוזרים הוא נושא הטוקנים והטרנזקציות. ולכן חשוב להשתמש בסוג זה כי אם בטעות לא תשתמשו בפונקציה מסוג payable לצורך קבלה או שליחה של תשלום, סולידיטי ידחה את הבקשה אוטומטית. להלן דוגמה לפונקציה מסוג זה:
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; contract Test { uint constant FAV_NUMBER = 10; uint s_value; constructor() {} function deposit() payable external { require(msg.value == 1 ether); } }
להלן פונקציה שעושה הפקדה של איתריום אל החוזה. אפשר לראות את השימוש של payable. אפשר גם לרשום את הפונקציה ללא התנאי require וכל סכום יועבר על חוזה זה. כמו כן חשוב לציין שסוג זה של הגדרה יפגוש אותנו הרבה במהלך הקורס והוא לא רק משויך לפונקציות אלא גם לכתובת.
שימוש עם רמיקס: פונקציות payable יהיו בצבע אדום.

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