امروزه دنیای غیرمتمرکز رشد چشمگیری را از نظر جذب کاربران تجربه میکند. شرکتها هرساله برای رفع نیازهای متنوع بازار، در حال طراحی و توسعه برنامههای غیرمتمرکز (dApps) بر بستر اتریوم هستند. در هسته اصلی این برنامهها ماشین مجازی اتریوم (EVM) قرار دارد. Ethereum Virtual Machine یا همان EVM که با قراردادهای هوشمند نیز سازگار است در سالیدیتی نوشته شده است. درحالیکه یادگیری syntax و Solidity خیلی دور از ذهن و دشوار نیست، اما نوشتن یک قرارداد هوشمند مقیاسپذیر، مناسب و مطمئن که درک بالایی از فرایند الگوهای طراحی داشته باشد به هیچ عنوان ساده نیست.
چرا درک الگوهای طراحی تا این اندازه مهم است؟
برای فهمیدن، الگوها را درک کنید. (فیلسوف بریتانیایی)
یکی از اصلیترین تفاوتها میان یک برنامهنویس تازهکار و پیشرفته، داشتن یک درک درست از الگوهای مناسب در طراحی است. این الگوها یک راهحل امتحانشده هستند که میتوانند به قویترین ابزار شما برای حل مشکلات متداول تبدیل شوند.
در مقایسه با دیگر مباحث برنامهنویسی نرمافزاری، توسعه یک قرارداد هوشمند کارآمد، یک امر پیچیده بهحساب میآید. ویژگی ماشین توزیعشده (distributed state machine)، برنامه شما را تبدیل به یک برنامه تمام وقت (۲۴/۷) میکند و دسترسی تمامی کاربران در سراسر دنیا را به برنامه شما مهیا میکند که این یک تیغ دولبه است. برای اینکه مطمئن شوید برنامه شما از امنیت، قابلیت اعتماد و قطعیت کافی برخوردار است، بایستی از بهترین روشهای ممکن در طراحی الگوها استفاده کنید.
آیا قدرت ریسک کردن بر روی پلتفرمی را دارید که کوچکترین اشکال یا اختلال در آن میتواند هزینهای چند میلیون دلاری را به شما تحمیل کند؟
آموزش الگوهای مجوز در سالیدیتی
با وجود دسترسی تمام افراد حاضر در شبکه به جزئیات در قراردادهای هوشمند، این مسئله ممکن است برای یک برنامه مطلوب نباشد. گاهی فرایندها و عملکردهایی در برنامه وجود دارد که لزومی ندارد کنترل آن در اختیار همه اعضا باشد و بایستی آن عملکرد در کنترل گروه خاصی از افراد حاضر در شبکه باشد.
عدم موفقیت در طراحی چنین سازوکاری ممکن است قابلیتهای نامطلوب و پیشبینی نشدهای را بهوجود بیاورد:
[tie_list type=”starlist”]
- حالتهایی که توسط الگوهای مجوز میتوان اجرا کرد.
- اجازه صدور مجوزهای اجرایی و فرامجوزی به گروه خاصی از کاربران
- اجازه انجام تراکنش پس از اعتبارسنجی فراخواننده (caller)
- اعمال برخی محدودیتها بر روی ویژگیهای یک قرارداد توسط خالق آن
[/tie_list]
در این مقاله الگوهای اعتبارسنجی را به ۴ دسته کلی تقسیم کردهایم.
اعمال محدودیت در دسترسی
پیش از اجرایی شدن یک تابع (function) برخی شرایط وجود دارد که فراخواننده (caller) بایستی انجام دهد. این شرایط میتواند مواردی مانند شناسایی فراخواننده، وارد کردن برخی مولفهها، وضعیت قرارداد و مواردی از این دست باشد. این تابع زمانی اجرایی میشود که تمامی موارد خواستهشده در قرارداد برآورده شده باشد.
در صورتی که تمامی موارد برآورده نشود، EVM با برگرداندن وضعیت به حالت قبلی خود، یک خطا شناسایی میکند و برای این درخواست تغییری را در وضعیت ایجاد نمیکند. اگر محدودیتی یکسان در چندین تابع قابلبهکارگیری باشد، با اعمال برخی اصلاحات میشود از لایه محافظتی یکسان استفاده کرد. همچنین اگر تمایل ندارید که توابع و متغیرهای شما بهصورت عمومی در دسترس باشد، میتوانید از یک وضعیت خصوصی برای اطلاعات خود استفاده کنید.
نمونه کد: برای استفاده مستقیم از کدهای موجود، در انتهای مقاله به بخش منابع مراجعه کنید.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title An incremental time-bound donation receiver
*/
contract AccessRestriction {
/**
* @dev public variables in global, visible to everyone
*/
address public treasury = msg.sender;
uint256 public creationTime = block.timestamp;
uint256 public minimumDonation;
/**
* @dev private visibility of winner address
*/
address private winner;
/**********Modifier Blocks********/
/**
* @dev check if donation period has started
*/
modifier onlyBefore(uint256 _time) {
require(block.timestamp < _time);
_;
}
/**
* @dev check if donation period has ended
*/
modifier onlyAfter(uint256 _time) {
require(block.timestamp > _time);
_;
}
modifier isHigherDonation() {
require(msg.value > minimumDonation, "Please send higher amount");
winner = msg.sender;
minimumDonation = msg.value;
_;
}
function sendDonation()
external
payable
onlyBefore(creationTime + 1 weeks)
isHigherDonation
{
payable(treasury).transfer(msg.value);
}
function revealHighestDonor()
external
view
onlyAfter(creationTime + 1 weeks)
returns (address)
{
return winner;
}
}
حالت چند مجوزی (Multi Authorization)
شرایطی وجود دارد که ممکن است یک تراکنش یا تابع نیازمند تایید چندین کاربر باشد. در چنین حالتی تعداد کل افراد دارای صلاحیت برای اعتبارسنجی را X درنظر میگیریم و تعداد زیرمجموعه مورد نظر را Y فرض میکنیم. در نتیجه همیشه برای اجرایی شدن یک تراکنش X≥Y است. هنگامیکه با پرداختهای چندامضایی (Multisig) روبهرو هستیم این یک رویکرد مناسب است. چالشی که با آن در این رویکرد روبهرو هستیم این است که افراد حاضر در گروه اعتبارسنج authorization group بایستی از پیش شناسایی شده باشند و در زمان اجرایی شدن، بایستی حداقل عضو لازم برای امضای این قرارداد در دسترس باشند. همچنین در حالتی که یک کاربر در فرایند اعتبارسنجی، کلید یا اطلاعات حساب کاربری خود را از دست بدهد، حسابکاربری آن فرد بیاستفاده خواهد ماند.
کد نمونه:
یک کد نوشتهشده ساده ولی جذاب برای حالتهای چندمجوزی توسط کریستین لوندکوییست Christian Lundkvist نوشته شده است. این اجرای جذاب برای حسابهای چندامضایی را از اینجا مشاهده کنید. همچنین مطمئنترین و اصلاحشدهترین منبع نسبی موجود در این زمینه را میتوانید در Gnosis safe repo ببینید. توصیه میشود که از هیچ کدبرنامهنویسی multi-auth بدون انجام اصلاحات الگوهای مورد نظر، استفاده نکنید.
مالکیت و کنترل دسترسی نقش محور
هر قرارداد هوشمند اجرا شده میتواند مانند نسخههای سنتی خود از عملیاتها و دسترسیهای منحصربهفرد پشتیبانی کند. اختصاص این نقشها و نظارت بر آنها میتواند به کمک مالکیت و کنترل دسترسی نقش محور اجرایی شود. دسترسی نقش محور این امکان را میدهد تا ویژگیهای خاصی را مخصوص گروه مشخصی از اصلاحکنندهها (modifiers) طراحی کرد.
نمونه کد:
پیادهسازی استاندارد این الگو در سایت OpenZeppelin قابلمشاهده است.
اعتبارسنجی داینامیک را در یک Off-Chain Secret فعال کرد
برخلاف اعضای نقش محور (role-based) و گروههای از پیش اعتبارسنجیشده، ممکن است شرایطی پیش بیاید که شناسایی گروه اعتبارسنجی امکانپذیر نباشد. Dynamic binding بهصورت پیشفرض انجام نمیشود و همانگونه که بهطور معمول لیستهای نظارتی از پیشتعریفشدهاند، آنها بایستی در اولین تراکنش یا قرارداد اجراییشده، اعتبارسنجی شوند. ما برای چنین شرایطی میتوانیم یک Off-Chain Secret از سمت مشتری برای هدف تابع شناساییشده بسازیم. این رمز توسط الگوریتم SHA-256 تبدیل به هش (hash) میشود و به قرارداد اضافه میشود. سپس این رمز تبدیل به یک الزام اجرایی در کنار دیگر پارامترها اعتبارسنجی میشود. این رمزها میتوانند مطابق با استاندارد key-exchange protocol با آدرسهای از پیشتعیینشده به اشتراک گذاشته شوند.
Hashlocks برای کانالهای پرداختی و مبادلات اتمی (Atomic Swaps) بسیار پرکاربرد است. همچنین این رمزها بهترین روش برای تراکنشهای تاییدی در on-chain است.
نمونه کد:
این یک نمونه الحاق رمز به یک قراداد است که میتواند در کنار هر hashlocks دیگری بهکار گرفته شود. این رمز به بخش افشا و منابع قرارداد منتقل میشود تا در مواقع Time lock، بلوکهای revelation و expiration با یکدیگر مقایسه شوند.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;
contract SecretRegistry {
mapping(bytes32 => uint256) private secretToBlock;
event SecretRevealed(bytes32 indexed secrethash, bytes32 secret);
/**
* @dev Register the secret that's been used for validation
*/
function registerSecret(bytes32 secret) public returns (bool) {
bytes32 secrethash = sha256(abi.encodePacked(secret));
if (secretToBlock[secrethash] > 0) {
return false;
}
secretToBlock[secrethash] = block.number;
emit SecretRevealed(secrethash, secret);
return true;
}
function getRevealedSecretBlockHeight(bytes32 secrethash) public view returns (uint256) {
return secretToBlock[secrethash];
}
}
سخن پایانی
اعتبارسنجی On-Chain از طریق کنترل مدیریت و تعیین نقشها برای مدیریت دارایی تبدیل به یک ضرورت شدهاست. همچنین بایستی به شدت درباره رمزها و نحوه به اشتراکگذاری آنها حساسیت به خرج دهید و تلاش کنید تا در توسعه برنامه غیرمتمرکز dApps بعدی خود از این الگوها استفاده کنید. نمونه کدهای دیگری را میتوانید در این repo