قبل از اینکه شروع کنیم، نسخه سالیدیتی را در کد قرارداد هوشمند به روز رسانی کنید تا بتوانیم از امکانات آن استفاده کنیم. البته الان که این مطلب در حال نگارش است، نسخه 0.6.0 برای سالیدیت موجود است ولی ما با 0.5.1 کار می کنیم. لذا اولین خط را به شکل زیر تغییر دهید.
pragma solidity ^0.5.1;
این کار باعث توسعه همزمان قدرت برنامه نویسی با توسعه سالیدیتی خواهد شد.
اکنون، برای جلوگیری از برخی هشدارها در نسخه جدید سالدیتی، function مربوط به get()
را به شکل زیر تغییر می دهیم:
function get() public view returns(string memory) { return value; }
همین تغییرات را برای function مربوط به set()
هم انجام می دهیم:
function set(string memory _value) public { value = _value; }
فوق العاده، هم کانون کد ما منطبق با تغییرات و به روز است است. در این مرحله در خصوص اصول پایه و انواع داده ها و ساختارهای آنها بررسی کوتاهی داریم. این موارد به شما کمک می کند که استفاده بهتری در هنگام پیاده سازی کد قرارداد هوشمند اتریوم خود داشته باشید.
در ابتدا، با state variable شروع می کنیم، سالیدتی امکانات خیلی خوبی برای این نوع از متغیر ارائه داده است. در ابتدا ما value
را به state variable اختصاص می دهیم و از عملگر get()
برای داده مقدار به آن استفاده می کنیم. سالیدیتی یک روش خیلی ساده برای این کار تعریف کرده که در زیر می بینید:
string public value;
این روش وضعیت متغیر را به صورت دسترسی public
تعریف می کند و این اجازه را می دهد که از خارج از قرارداد هوشمند هم بتوان آن را به صورت دسترسی read بازخوانی کرد. در عمل سالیدیتی عملگر value()
را خیلی سریع صدا کرده و دیگر نیازی به استفاده از get()
نیست. برای همین خیلی راحت می توان state variable به شکل زیر تعریف کرد:
string public value = "myValue"
عملا ما دیگر نیازی به استفاده از عملگر constructor
هم نداریم، که باعث کاهش زمان و حجم کد نوشته شده خواهد شد. باز هم می توانیم با تعریف کردن متغیر به شکل ثابت از تغییرهای بعدی آن جلوگیری کنیم، مثل این حالت:
string public constant value = "myValue"
اکنون، باید عملگر set()
را حذف کنیم چرا که سالیدیتی به شما اجازه نمی دهد که در حالت constant روی آن تغییری ایجاد شود.
آنچه در بالا دیدید مروری بر خلاصه سازی بود که سالیدیتی ایجاد کرده و کار را ساده کرده است. اکنون می توان state variable ها را برای انواع دیگری از داده ها بررسی کنیم. همانطور که در حالت string
دیدیم، ما می توانیم میزان عمومی یا visibility را به نام متغییر تخصیص دهیم. برای همین ما با ساختن یک متغیر boolean
به شکل زیر شروع می کنیم:
bool public myBool = true;
این متغییر می تواند true
یا false
باشد.
اکنون یک متغیر عددی integer به شکل زیر می سازیم:
int public myInt = 1;
متغیر های عددی می توانند مثبت یا منفی باشند. اگر می خواهید اعداد حتما مثبت باشند، از روش زیر استفاده کنید:
uint public myUint = 1;
حتی ما می توانیم تعداد خاصی از بیت ها را برای اعداد در نظر بگیریم. در مثال بالا به صورت پیش فرض 256 بیت در نظر گرفته شده که به شکل زیر باز شده است:
uint256 public myUint256 = 9999;
شما می توانید به صورت 8 بیت هم آن را محدود کنید، مثل این:
uint8 public myUint8 = 8;
اکنون، بررسی کوتاهی روی ساختار داده سالیدیتی داشته باشیم. در ابتدا به ساختار داده Enum
نگاه کنید، این ساختار داده روشی برای نگهداری لیست های تجمیع شده یا enumerated lists است. یک enum در قرارداد هوشمند به شکل زیر است:
enum State { Waiting, Ready, Active }
به طور مثال، این موضوع می تواند وضعیت active
را در قرارداد هوشمند به شکل گزینه های Waiting
, Ready
, and Active
نشان دهد. ما می توانیم وضعیت فعلی قرارداد هوشمند را از طریق زیر چک کنیم:
State public state;
اکنون، می توانیم وضعیت پیش فرض در constructor را به شکل زیر تعریف کنیم:
constructor() public { state = State.Waiting; }
یا اینکه به حالت active
تغییر دهیم:
function activate() public { state = State.Active; }
و در انتها، می توانیم لیست enum را چک کرده و ببینیم که آیا وضعیت قرارداد به صورت فعال است یا خیر؟
function isActive() public view returns(bool) { return state == State.Active; }
در این مرحله، قرارداد هوشمند ما باید به شکل زیر باشد:
pragma solidity 0.5.1; contract MyContract { enum State { Waiting, Ready, Active } State public state; constructor() public { state = State.Waiting; } function activate() public { state = State.Active; } function isActive() public view returns(bool) { return state == State.Active; } }
این یک مثال از روشی استفاده از enums بود که بررسی وضعیت را درون قرارداد هوشمند انجام می دهد.
در آینده در خصوص استفاده از این عملگر برای قراردادهای هوشمند برگزاری عرضه توکن اولیه یا ICO ها و وضعیت open
یا closed
آن بیشتر توضیح خواهم داد.
سالیدیتی به شما اجازه می دهد که ساختار داده خاص خودتان را به روش Structs
بسازید و داشته باشید. به طور کلی شما هر نوع داده ای که بخواهید می توانید مدل کرده و با خاصیت های دلخواه و ترکیبی از انواع داده داشته باشید. یک نگاهی به این ساختار داشته باشیم و از مثال struct people
استفاده می کنیم:
struct Person { string _firstName; string _lastName; }
خوب چه کاری کردیم؟ ما "فردی" را مدل کردیم که _firstName
و _lastName
دارد. دقت کنید که ما قادریم هر نوع داده ای که دوست داریم را وارد کنیم. ما از strings برای هر دو attribute استفاده کردیم. در عین حال می توانیم تا 17 ویژگی متفاوت را در اینجا برای هر نوع داده ای داشته باشیم. فعلا سعی کنیم همین طور ساده حفظ کرده و با همین مدل دو attribute برای فرد استفاده کنیم.
اکنون لازم است که محلی برای ذخیره سازی در این ساختار فرد داشته باشیم، برای همین ما از آرایه استفاده می کنیم. مثال people
را به صورت آرایه ببینید:
Person[] public people;
دقت کردید که ما یک آرایه به نام people
را برای people تعریف کردیم. علاوه بر این دسترسی خارجی را به شکل public قرار می دهیم و به وضعیت متغیر people
آن را وصل می کنیم. این روش باعث ایجاد عملگری شده که به افراد درون آرایه دسترسی لازم را می دهد. این را باید اشاره می کردم، که بازخوانی عملگر people()
خارج از قرارداد هوشمند کل اطلاعات آرایه را برنمی گرداند. در عوض عملگر people()
می تواند argument مثل index
را قبول کرده و از این طریق به افراد داخل آرایه بر اساس اجازه دسترسی می دهد. دقت شود که این یک آرایه بر پایه صفر است. برای مثال ما برای دسترسی به اولین نفر در آرایه به این شکل بازخوانی می کنیم:
people(0)
اکنون، میتوانیم روشی بسازیم که "فرد" جدیدی به این آرایه اضافه کنیم. عملگر addPerson()
را به شکل زیر تعریف می کنیم:
function addPerson(string memory _firstName, string memory _lastName) public { people.push(Person(_firstName, _lastName)); peopleCount += 1; }
این عملگر بر اساس ساختار ویژگی های person
درخواست های جدیدی را قبول می کند و بعد از آن یک instantiate جدید برای ایجاد کرده و با استفاده از روش عملگر push
به آرایه people
اضافه می کند. علاوه بر این وضعیت peopleCount
را هم به عدد 1 تغییر می دهد. می توانیم وضعیت متغیر را در قرارداد هوشمند به شکل زیر تعریف کنیم:
uint256 public peopleCount;
ما از این داده به عنوان cache شمارنده استفاده می کنیم. یادتون که گفتم شما نمی تونید کل آرایه people
را با استفاده از عملگر people()
بازخوانی کنید؟ با این روش که بدانید چند نفر درون آرایه هستند، شاید بهتر بتوانیم از عملگر people()
برای بازخوانی person
استفاده کنیم.
خوب الان کد قرارداد هوشمند ما باید به شکل زیر باشد:
pragma solidity 0.5.1; contract MyContract { Person[] public people; uint256 public peopleCount; struct Person { string _firstName; string _lastName; } function addPerson(string memory _firstName, string memory _lastName) public { people.push(Person(_firstName, _lastName)); peopleCount += 1; } }
سالیدیتی یک ساختار داده دیگر هم دارد که عنوان mapping
دارد، این ساختار داده به شما اجازه ذخیره سازی زوج کلیدها را می دهد. این ساختار داده شبیه یک آرایه اشتراکی و یا یک جدول هش Hash table در دیگر عملگرها رفتار می کند. ما از mapping به شکل زیر میتوانیم استفاده کنیم:
mapping(uint => Person) public people;
این روش mapping را در ساختار person
ذخیره می کندو جایگرین آرایه people
که در مثال قبل استفاده کردیم. این کلید یک integer امضا نشده است و داده آن به عنوان struct در person
استفاده می شود. نوع استفاده از این کلید دقیقا مثل id در پایگاه داده استفاده می کنیم. ما می توانیم struct را در person
به شکل زیر به روزسانی و آپدیت کنیم:
struct Person { uint _id; string _firstName; string _lastName; }
اکنون میتوانیم عملگر addPerson()
را برای به روز رسانی و mapping در people
به شکل زیر استفاده کنیم:
function addPerson(string memory _firstName, string memory _lastName) public { peopleCount += 1; people[peopleCount] = Person(peopleCount, _firstName, _lastName); }
در این مرحله از cache شمارنده peopleCount
برای ساخت id برای person
استفاده میکنیم. بعد این فرآیند یک struct جدید با id پیدا شده برای person
ایجاد می کند و ویژگی های آن را هم دریافت و انتقال می دهد. سپس این را به mapping مربوط به people
اضافه می کند. قرارداد هوشمند کامل شده به شکل زیر خواهد بود:
pragma solidity 0.5.1; contract MyContract { uint256 peopleCount = 0; mapping(uint => Person) public people; struct Person { uint _id; string _firstName; string _lastName; } function addPerson(string memory _firstName, string memory _lastName) public { peopleCount += 1; people[peopleCount] = Person(peopleCount, _firstName, _lastName); } }
عالی. یک مرور کلی روی مفاهیم اولیه انواع داده ها و ساختار داده ها در سالیدیتی داشتیم.
در بخش بعدی در مورد عملگرها، modifiers و زمان صحبت میکنیم. برای اطلاعات بیشتر در خصوص انواع ساختار داده ها خود کتابخانه سالیدیتی را بخوانید:
https://solidity.readthedocs.io/en/v0.4.24/types.html?highlight=data%20structure