בהמשך לפוסט הקודם ובהמשך לחלק הראשון בקורס סולידיטי למתחילים והפעם נדבר על טייפים חשובים מאוד וייחודיים לסולידיטי שמאד חשוב להבין היטב, כמתכנתים שרוצים להיות מומחים ולהיות מתכנתי בלוקציין אמיתיים חשוב מאד להבין את השוני בין הטייפים הללו וכן להשתמש בהם בתבונה וזה מהרבה סיבות, גם לצורך קוד תקין וגם לחיסכון בגז (ges optimisation).
רקע על המידע שנשמר בבלוקציין
לפני שניגש לדוגמאות , אציין כמה עובדות חשובות באיך שסולידיטי שומר מידע בבלוקציין. יש מידע שנשמר בדטהבייס של הבלוקציין כמעין מחסנית, שנקרא storage כל מידע שבחרתם לשמור כ storage יהיה קבוע, כלומר יחתם בבלוקציין (כמו כן יהיה זמין לקריאה עבור כולם). לרוב שמירה של מידע זה יהיה יקר יותר ויעלה יותר גז ולכן נשתדל להשתמש בstorage בתבונה. למשל כל מה שמוגדר כ state variable ישמר כ storage data. למשל בדוגמה הזו כל המשתמשים שהוגדרו בחוזה (state variable) ישמרו במחסנית של storage עבור חוזה זה: (משתנים מסוג constant or immutable לא ישמרו ב storage ושימרו כחלק מה bytecode של החוזה – נדבר על זה בהמשך כמובן)
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; contract ExampleStorageMemoryAndCalldata { uint256 favNum; bool isOk; string name; address myAddress; ... }
יש גם מידע שלא ישמר בבלוקציין, ומשתנים אלו יהיו זמניים וימחקו לאחר השימוש בהם, למשל משתנים שהם local variables שהגדרתם במטודה כלשהי (arguments) , ישמרו כ memory variables. בדומה גם calldata ונרחיב על ההבדלים בהמשך, כמו כן memory ישמשו בעיקר לקריאה של מידע ולא לכתיבה כי הם אינם נשמרים גם אם תשנו מידע מסוג זה הוא ישתנה אבל לא ישמור את השינוי.
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; contract ExampleStorageMemoryAndCalldata { string name = "jon"; // state variable function memoryTest(string memory _newName) external { string memory newName = name; newName = _newName; } }
Storage
אז כפי שכבר ציינתי , לסטורג שימוש חשוב ועיקרי והוא, שנוכל להגדיר מה המידע שאנחנו רוצים לשמור בסטורג, כלומר מידע שמאוחסן כרשימה גדולה המקושרת לחוזה, מידע זה הינו קבוע וזמין לחוזה שלכם (וגם זמין לקריאה עבור אחרים). אציג דוגמה להמחשה והסברים למטה:
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; contract ExampleStorageMemoryAndCalldata { struct Person { string dateOfBirth; string name; uint256 weight; } mapping (address => Person) public persons; constructor() { } function modifyMyData(address _address) external returns(Person memory) { Person storage myData = persons[_address]; return myData; } }
כל משתנה שנגדיר בחוזה, מעל ל constructor , במקרה שלנו בדוגמה נראה את persons שהוא מסוג mapping הינו משתנה שישמר בסטורג. דוגמה נוספת, אפשר לראות שעשיתי שימוש גם בתוך הפונקציה myData שהוגדר גם כ storage , אציין כי כל מה שתגדירו כ state variable הינו נשמר ב storage ואין אפשרות אחרת. משתנים בתוך פונקציות לרוב יהיו memory או calldata.
חשוב לציין ולזכור להמשך
יש לציין משהו חשוב שנדבר עליו בהמשך , משתנים קבועים immutale and constant אינם נשמרים ב storage אלא נשמרים ב"bytecode" של החוזה עצמו.
חשוב לציין שכל קריאה או כתיבה למשתנה שהוא מסוג storage יהיה משמעותית יקר יותר בעמלות הגז.
Memory
מידע שישמר כזמני, יהיה זמין לשימוש רק בתוך פוקציות שנגדיר, לאחר שהפונקציה תסיים את עבודתה מידע זה ימחק. כמובן אינו יהיה נגיש מבחוץ (כי אינו נשמר בבלוקציין). אציין כי מדיע שמגיע לפנוקציה שהוא מסוג string תדרשו להגדיר אותו כ memory מכוון שסולידיטי מייצג סטרינג כמערכים ולכן יהיה חשוב להגדיר זאת , כמו כן תוצג שגיאה בהתאם. להלן קוד הדוגמה:
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; contract ExampleStorageMemoryAndCalldata { constructor() { } function simpleFun(string memory _data, uint256 _favNum) public pure returns(string memory) { uint256 favNum = _favNum; string memory data = string.concat( "hello ", _data); return data; } }
אפשר לראות שכל מה שהוא סטרינג מחייב להוסיף את ההגדרה של memory (או אולי סטורג אם זה מה שתרצו להגדיר), קח סולידיטי מבין את ההתייחסות של סטרינג בתוך פונקציה. להבדיל ממספרים שאין צורך לציין וכן מידע זה ישמר בmemory.
נוסיף ונגיד כי ניתן לשנות את המידע שנשמר במשתנה שהוא מסוג memory אבל המידע לא באמת נשמר. (אך ניתן לשנותו במהלך חייו).
calldata
בדומה מאוד לmemory, רק שאין יכולת לשנות אותו. כי אינו יוצר עותק בשונה מmemory וכן יהיה אפשר להגדיר רק משתנים של פונקציה כinput למשל :
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; contract ExampleStorageMemoryAndCalldata { constructor() { } function anotherSimpleFun(string calldata _data) public pure returns(string memory) { string memory data = string.concat( "hello ", _data); return data; } }
calldata הוא הזול ביותר אם נדבר במונחים של גז, וכן השימוש בו דומה ל memory, אך אינו ניתן לשינוי והינו מתאים רק למשתנים שנכנסים לפונקציה (input).
סיכום קורס סולידיטי memory, calldata and storage
אז דיברנו על כמה מההגדרות החשובות בסולידיטי ועל ההבדלים בינהם , בהמשך נראה דוגמאות איך באמת להשתמש בהם בתבונה על מנת לחסוך בגז וגם בשביל לא לעשות טעויות קריטיות, שגם נצריך בדוגמאות בחלקים היותר פרקטיים של הקורס. בהצלחה בהמשך הדרך 🐊🤗