سالیدیتی زبانی است که برای توصیف قراردادهای هوشمند در بلاکچین اتریوم استفاده میشود. قراردادهای هوشمند قوانین و شرایطی را برای اجرای خودکار تعاملات بین طرفین در بلاکچین تعریف میکنند. با استفاده از سالیدیتی، میتوانید قراردادهای هوشمندی را برنامهریزی و پیادهسازی کنید که امکان انجام تراکنشها، مدیریت داراییها و اجرای قوانین مربوط به طرفین را فراهم میکند.
زبان سالیدیتی یک زبان برنامهنویسی است که برای توسعه نرمافزارهای قابل اجرا بر روی پلتفرمهای مختلف بلاک چینی مورد استفاده قرار میگیرد.
برنامه نویسی سالیدیتی یک زبان قوی و انعطافپذیر است که از ویژگیهایی مانند متغیرها، توابع، مدیریت حافظه، مدیریت استثناء و ارثبری پشتیبانی میکند.
برخی از کاربردهای رایج سالیدیتی عبارتند از:
سطوح دسترسی در زبان سالیدیتی به چهار دسته تقسیم می شوند:
این سطوح دسترسی کنترلی را بر روی توابع و متغیرهای قرارداد فراهم میکنند. استفاده از سطوح دسترسی به ما امکان میدهد که کنترل بیشتری بر روی دسترسی به توابع و متغییرهای قرارداد داشته باشیم. این کنترل میتواند به امنیت بیشتر قرارداد کمک کند و از استفاده نادرست از توابع و متغییرها جلوگیری کند.
در این مقاله درباره سطح دسترسی در سالیدیتی صحبت میکنیم
Table of contents [Show]
این سطح دسترسی بالاترین سطح دسترسی و هر کسی میتواند به توابع و متغییرهای قرارداد دسترسی داشته باشد، هم از داخل قرارداد و هم از خارج قرارداد. این سطح دسترسی برای توابع و متغییرهایی استفاده میشود که باید برای همه قابل دسترسی باشند.
مثال 1: بیایید یک مثال کاربردی استفاده از متغییرهای public در solidity بزنیم. فرض کنید ما یک قرارداد برای یک حراجی ساده در بلاکچین ایجاد میکنیم. در این حراجی، میخواهیم بتوانیم پیشنهادات را بررسی کنیم و بالاترین پیشنهاد را ببینیم.
آموزش رایگان سالیدیتی را از دست ندهید.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleAuction {
// متغییر public برای نگهداری بالاترین پیشنهاد
uint public highestBid;
// متغییر public برای نگهداری آدرس برنده فعلی
address public highestBidder;
constructor() {
highestBid = 0;
}
// تابع برای ارائه یک پیشنهاد جدید
function bid() public payable {
require(msg.value > highestBid, "There already is a higher bid.");
highestBid = msg.value;
highestBidder = msg.sender;
}
}
contract ExtendedAuction is SimpleAuction {
function getHighestBid() public view returns (uint) {
return highestBid;
}
function getHighestBidder() public view returns (address) {
return highestBidder;
}
}
وقتی یک متغییر به عنوان publicتعریف میشود، Solidity به صورت خودکار یک تابع getter برای آن ایجاد میکند.
این تابع به ما اجازه میدهد که مقدار متغییر را از خارج قرارداد بخوانیم. برای مثال، اگر ما یک قرارداد دیگر به نام AuctionViewer داشته باشیم که یک نمونه از SimpleAuction را نگه میدارد، میتوانیم به highestBid و highestBidder دسترسی داشته باشیم:
contract AuctionViewer {
SimpleAuction auction;
function createAuction(address _auctionAddress) public {
auction = SimpleAuction(_auctionAddress);
}
function getHighestBid() public view returns (uint) {
return auction.highestBid();
}
function getHighestBidder() public view returns (address) {
return auction.highestBidder();
}
}
در این مثال، تابع createAuction یک آدرس قرارداد به عنوان ورودی میگیرد و یک نمونه جدید از SimpleAuction را با استفاده از آدرس مشخص میسازد. توابع getHighestBid و getHighestBidder همچنان میتوانند از توابع getter خودکار استفاده کنند کهSolidity برایhighestBid و highestBidder ایجاد کرده است تا مقادیر آنها را بخوانند.
مثال 2: بیایید یک مثال کاربردی استفاده از تابع public در solidity بزنیم. فرض کنید ما یک قرارداد برای یک بانک ساده در بلاکچین ایجاد میکنیم. در این بانک، میخواهیم بتوانیم مقدار پول را در حسابهای مختلف بررسی کنیم و امکان واریز و برداشت وجود داشته باشد.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleBank {
mapping(address => uint) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance.");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
function getBalance() public view returns (uint) {
return balances[msg.sender];
}
function depositAndCheckBalance() public payable {
deposit();
uint balance = getBalance();
assert(balance >= msg.value);
}
}
در این مثال، SimpleBank یک قرارداد بانک ساده است. ما یک تابع public به نام deposit داریم که اجازه میدهد که کاربران پول واریز کنند، و یک تابع public به نام withdrawکه اجازه میدهد که کاربران پول برداشت کنند. همچنین، تابع getBalance اجازه میدهد که کاربران مقدار پول خود را در بانک ببینند.
تابع depositAndCheckBalance در قرارداد SimpleBank ابتدا تابع deposit را فراخوانی میکند که مقدار اتر ارسالی را به موجودی کاربر اضافه میکند. سپس با فراخوانی تابع getBalance، موجودی جدید حساب کاربر را بررسی میکند و اطمینان حاصل میکند که حداقل برابر با مقدار اتر ارسالی است.
اگر ما یک قرارداد دیگر به نام ExtendedBank داشته باشیم که از SimpleBank ارثبری میکند، میتوانیم به توابع withdraw ، deposit و getBalance دسترسی داشته باسیم:
contract ExtendedBank is SimpleBank {
function checkBalance() public view returns (uint) {
return getBalance();
}
}
در این کد، تابع checkBalance تابع public getBalance را از SimpleBank فراخوانی میکند. این تابع میتواند موجودی کاربر را بررسی کند.
وقتی کاربر تابع depositToBank را فراخوانی میکند و اتری را به آن ارسال میکند، مقدار ارسالی به تابع deposit در قرارداد SimpleBank ارسال میشود. این تابع deposit مقدار ارسالی را به موجودی کاربر (که با آدرس او مشخص میشود) اضافه میکند.
به این ترتیب، تابع depositToBank به کاربر اجازه میدهد تا اتر به قرارداد SimpleBank واریز کند و موجودی خود را افزایش دهد.
contract BankUser {
SimpleBank bank;
function createBank(address _simpleBank) public {
bank = SimpleBank(_simpleBank);
}
function depositToBank() public payable {
bank.deposit{value: msg.value}();
}
}
سطح دسترسی private به این معنی است که فقط داخل قرارداد میتواند به توابع و متغیرها دسترسی داشته باشد. این سطح دسترسی برای توابع و متغیرهایی استفاده میشود که نباید برای همه قابل دسترسی باشند و فقط باید در داخل قرارداد استفاده شوند.
مثال 3: در زیر یک مثال کاربردی استفاده از متغیرهای private در solidity را می بینید. در این مثال، ما یک قرارداد بانکی ساده ایجاد می کنیم که امکان واریز و برداشت اتر به حساب ها را فراهم می کند. ما یک متغییر privateبه نام balances تعریف می کنیم که میزان اتر در حساب هر کاربر را نگه می دارد.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;
contract SimpleBank {
mapping(address => uint) private balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
function getBalance() public view returns (uint) {
return balances[msg.sender];
}
}
در این قرارداد، تنها راه برای دسترسی به میزان اتر در حساب یک کاربر از طریق تابع getBalanceاست که تنها به کاربر اجازه می دهد میزان اتر در حساب خود را ببیند. این امر از دسترسی غیرمجاز به میزان اتر در حساب های دیگر جلوگیری می کند. همچنین، این متغییر از قراردادهای مشتق شده قابل دسترسی نیست، که این امر از تغییر غیرمجاز میزان اتر در حساب ها توسط قراردادهای دیگر جلوگیری می کند.
contract DerivedBank is SimpleBank {
function checkBalance(address user) public view returns (uint) {
// This will not compile because `balances` is private in SimpleBank
// return balances[user];
}
}
مثال 4: بیایید یک مثال کاربردی از استفاده از تابع private در Solidity بزنیم. فرض کنید ما یک قرارداد برای یک بانک ساده داریم که کاربران میتوانند در آن پول واریز کنند. اما ما میخواهیم که فقط در صورتی که مقدار واریزی بیش از یک حداقل مشخص باشد، واریز انجام شود. برای این منظور، میتوانیم یک تابع private تعریف کنیم که این بررسی را انجام دهد:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleBank {
mapping(address => uint256) public balances;
uint256 private minimumDeposit = 1 ether;
function _isDepositEnough(uint256 amount) private view returns (bool) {
return (amount >= minimumDeposit);
}
function deposit() public payable {
require(_isDepositEnough(msg.value), "Deposit is not enough.");
balances[msg.sender] += msg.value;
}
}
تابع _isDepositEnough به عنوان یک تابع private تعریف شده است زیرا این تابع فقط برای استفاده داخلی در قرارداد SimpleBank طراحی شده است. این تابع یک جزء از منطق داخلی قرارداد است که بررسی میکند آیا مقدار واریزی کاربر بیش از حداقل مشخص شده است یا خیر.
استفاده از توابع private در این چنین مواردی میتواند به افزایش امنیت و تمیز بودن کد کمک کند. با محدود کردن دسترسی به توابعی که فقط باید داخل قرارداد فراخوانی شوند، میتوانیم از این اطمینان حاصل شویم که کد قرارداد به شکل صحیح و امن کار میکند. همچنین، این کمک میکند تا کد را تمیزتر و قابل فهمتر بسازیم، زیرا مشخص میکند که کدام توابع فقط برای استفاده داخلی هستند.
سطح دسترسی internal شبیه به private است، با این تفاوت که قراردادهای مشتق شده نیز میتوانند به توابع و متغیرها دسترسی داشته باشند. این سطح دسترسی برای توابع و متغیرهایی استفاده میشود که باید در داخل قرارداد و قراردادهای مشتق شده قابل دسترسی باشند.
مثال 5: در اینجا یک مثال از یک قرارداد “کتابخانه” را در نظر بگیرید که کتاب ها و تعداد آنها را نگه می دارد. در این مثال، ما یک متغییر internal به نام bookCounts تعریف می کنیم که تعداد کتاب های موجود در کتابخانه را نگه می دارد.
تابع addBook یک کتاب با نام مشخص شده را به کتابخانه اضافه می کند. و با هر بار فراخوانی، تعداد آن کتاب در کتابخانه یک واحد افزایش می یابد. تابع getBookCount تعداد کتاب های با نام مشخص شده موجود در کتابخانه را برمی گرداند.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;
contract Library {
mapping(string => uint256) internal bookCounts;
function addBook(string memory bookName) public {
bookCounts[bookName]++;
}
function getBookCount(string memory bookName)
public
view
returns (uint256)
{
return bookCounts[bookName];
}
}
در قرارداد Library متغییر bookCounts به عنوان یک متغییر internal تعریف شده است.در تابع addBook ما به متغییر bookCounts دسترسی داریم و می توانیم مقدار آن را افزایش دهیم.
همچنین، در تابع getBookCount ما به متغییر bookCounts دسترسی داریم و می توانیم مقدار آن را برگردانیم.
در این قرارداد، متغییر bookCounts به عنوان internal تعریف شده است، بنابراین از خارج قرارداد قابل دسترسی نیست. اما، این متغییر در قراردادهای مشتق شده قابل دسترسی است. برای نشان دادن این موضوع، می توانیم یک قرارداد مشتق شده ایجاد کنیم:
contract SpecialLibrary is Library {
function donateBook(string memory bookName, uint256 count) public {
bookCounts[bookName] += count;
}
}
مثال 6: مثال زیر یک یک سیستم رایگیری ساده را ایجاد میکند. در این سیستم، هر نفر میتواند به یک گزینه رای دهد.
votes یک مپینگ است که آدرسهای اتریوم را به اعداد صحیح نگاشت میکند. در این مورد، هر آدرس اتریوم به یک عدد صحیح نگاشت میشود که نشاندهنده گزینهای است که آن آدرس به آن رای داده است.
به عبارت دیگر، هر بار که یک کاربر به یک گزینه رای میدهد، عدد مربوط به آن گزینه در مپینگ votesذخیره میشود. (اگر گزینههای رایگیری شمارههای 1 تا 5 باشند، کاربر با فراخوانی تابع vote و ارسال یک عدد بین 1 تا 5، رای خود را ثبت میکند.)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;
contract Voting {
mapping(address => uint256) private votes;
function recordVote(address voter, uint256 option) internal {
votes[voter] = option;
}
function vote(uint256 option) public {
recordVote(msg.sender, option);
}
}
در این مورد، تابع voteبه تابع recordVote دسترسی دارد زیرا recordVote به عنوان یک تابع internal تعریف شده است. این به این معنی است که تابع vote میتواند recordVote را در داخل قرارداد “Voting” فراخوانی کند.
تابع voteTwice در کانترکت “AdvancedVoting” به کاربر اجازه میدهد که به دو موضوع متفاوت رای دهد. این تابع دو ورودی به نامهای option1 و option2 دریافت میکند که هر کدام نمایانگر یک موضوع برای رایگیری هستند.
contract AdvancedVoting is Voting {
function voteTwice(uint256 option1, uint256 option2) public {
vote(option1);
recordVote(msg.sender, option2);
}
}
وقتی کاربر تابع voteTwice را فراخوانی میکند، ابتدا تابع vote از کانترکت پدر یعنی “Voting” فراخوانی میشود و رای اول کاربر یعنی option1 ثبت میشود. سپس، تابع recordVote که یک تابع internal در کانترکت “Voting” است، فراخوانی میشود و رای دوم کاربر یعنی option2 ثبت میشود. به این ترتیب، با استفاده از یک بار فراخوانی تابع voteTwice، کاربر میتواند به دو موضوع متفاوت رای دهد.
سطح دسترسی external به این معنی است که فقط از خارج قرارداد میتوان به توابع دسترسی داشت. این سطح دسترسی برای توابعی استفاده میشود که باید فقط از خارج قرارداد قابل فراخوانی باشند. این سطح دسترسی فقط برای توابع قابل دسترسی است و نمیتوان از آن برای متغیرها استفاده کرد.
یکی از کاربردهای متداول توابع external در قراردادهای هوشمند، استفاده از آنها برای ایجاد API های قابل دسترسی برای دیگر قراردادها یا کاربران است.
مثال 7: مثال زیر مربوط یک قرارداد ساده است که یک تابع double دارد که یک عدد صحیح را به عنوان ورودی میگیرد و دو برابر آن را برمیگرداند.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Simple {
function double(uint256 number) external pure returns (uint256) {
return number * 2;
}
}
contract Extended {
Simple public simple;
function initialize(address _simpleAddress) public {
simple = Simple(_simpleAddress);
}
function square(uint256 number) public view returns (uint256) {
return simple.double(number);
}
}
قرارداد Extended در Solidity یک نمونه از قرارداد Simple را نگه میدارد و تابع double آن را فراخوانی میکند. این قرارداد با استفاده از تابع initialize آدرس یک قرارداد Simple را دریافت و ذخیره میکند. سپس، تابع square یک عدد را به عنوان ورودی میگیرد، آن را به تابع double در قرارداد Simple میفرستد و خروجی را برمیگرداند.
در ادامه، یک جدول مقایسهای از سطوح دسترسی در زبان سالیدیتی ارائه شده است. این مقایسه میتواند به برنامهنویسان کمک کند تا سطوح دسترسی متناسب با نیازهای پروژه خود انتخاب کنند.