ما «بلاکچین هوشمند» را اینطور تعریف می کنیم؛ هر بلاکچینی که از قراردادهای هوشمند برای راستی آزمایی تراکنش ها استفاده کند، یک «بلاکچین هوشمند» است. ما قبلا در چند مقاله، مزیت های «بلاکچین هوشمند» را بررسی کردیم و جزئیات این ایده را برای استفاده در بلاکچین های آینده شرح دادیم. اما سوال بعدی این است که آیا می توانیم یک قرارداد هوشمند را روی یک بلاکچین فعال (مانند اتریوم) دیپلوی کنیم؛ تا راستی آزمایی تراکنش ها را بدون نیاز به ماینرها انجام دهد؟ جواب مثبت است. ما در همین مقاله تصمیم داریم که یک قرارداد هوشمند اتریوم ایجاد کنیم؛ که می تواند از «دوبار خرج کردن» توکن های خودش جلوگیری کند.
در این مقاله ابتدا روش تولید توکن با استاندارد ERC20 را توضیح می دهیم و یک قرارداد هوشمند برای تولید این نوع توکن ها می نویسیم. سپس همین قرارداد هوشمند را توسعه خواهیم داد تا بتواند از بدو تولد توکن ها، مشخصات همه حساب ها را ذخیره کند. یعنی علاوه بر تراکنش ها، مشخصات همه حساب هایی که حداقل یکبار از این نوع توکن ها دریافت کرده اند، ذخیره می شود. به این ترتیب، این قرارداد هوشمند در طول زمان می تواند با استفاده از مشخصات تراکنش ها، بالانس همه حساب ها (یعنی دفترکل مخصوص این نوع توکن) را محاسبه کند. ولی نکته مهم این است که این قرارداد روی شبکه غیر متمرکز اتریوم دیپلوی شده است و استیت قرارداد های هوشمند در این شبکه، فقط پس از اجماع همه ماینرها و ساخت یک بلاک جدید اتریوم، آپدیت می شود.
در حالت کلی، تاخیر در آپدیت استیت قرارداد های هوشمند می تواند امکان دوبار خرج کردن را به کاربران بدهد. اما همه می دانیم که ماینرها و سازندگان بلاک برای ایجاد هر بلاک جدید روی شبکه اتریوم، راستی آزمایی تراکنش ها را (با مصرف برق بسیار زیاد) انجام می دهند و اجازه دوبار خرج کردن توکن ها را به هیچکس نخواهند داد. ولی ما فرض می کنیم که مثلا یک شخص متقلب توانسته است از غیرمتمرکز بودن شبکه استفاده کند و یک تراکنش نامعتبر از توکن ما را در بلاکچین اتریوم ثبت نماید و به اصطلاح، توکن خود را دوبار خرج کند. پس به این ترتیب استیت جدید قرارداد ما نیز، نامعتبر خواهد بود. اما ما از قبل کدهایی به قرارداد خودمان اضافه می کنیم که حتی در این حالت، قرارداد هوشمند ما بصورت اتومات تراکنش های نامعتبر در استیت جدید خود را شناسایی و حذف کند. یعنی عملا خواهیم دید که بالانس همه حساب های دفترکل، همیشه صحیح خواهد بود و به راستی آزمایی ماینرهای شبکه اتریوم، احتیاجی نیست.
تعجب نکنید؛ راستی آزمایی همه تراکنش ها، فقط با چند خط کد نویسی با زبان سولیدیتی امکان پذیراست و ما همه جزئیات لازم را برای اولین بار در همین مقاله شرح خواهیم داد. ضمنا یک تقلب را نیز در تراکنش ها شبیه سازی می کنیم، تا با هم بتوانیم عملکرد راستی آزمایی قرارداد هوشمند خودمان را تست کنیم. در حقیقت یکبار دیگر نشان خواهیم داد که با کمال اطمینان می توان وظیفه راستی آزمایی تراکنش های توکن ها و رمزارزها را به قراردادهای هوشمند سپرد و نیازی به انواع مکانیسم های اجماع و اتلاف میلیون ها دلار هزینه برق نیست. مقاله حاضر در اکتبر سال ۲۰۲۰ توسط سمیه غلامی و مهران کاظمی نیا نوشته شده است.
درابتدا می خواهیم تعداد یک ملیون توکن ERC20 با نام «AKA» تولید کنیم. برای انجام این کار، یک قرارداد هوشمند مطابق استاندارد ERC20 نوشته ایم. شما می توانید همه کدهای این مقاله را از اینجا دانلود نمایید. ولی توجه نمایید که نام فایل ما دراین مرحله، SmartCreator101.sol می باشد. در ادامه جزئیات این قرارداد را با هم بررسی می کنیم.
پرکاربردترین قراردادهای هوشمند شبکه اتریوم، قراردادهای تولید کننده توکن های ERC20 هستند. خوشبختانه مقالات و کتابخانه های خوبی در این زمینه منتشر شده اند. حتی استانداردهای دیگری مانند ERC223 ، ERC621 ، ERC777 ، ERC827 برای تکمیل و توسعه استاندارد ERC20 وجود دارند. اما برای اینکه درک جزئیات توسعه قرارداد، برای خوانندگان راحت تر باشد، ما از import کردن استانداردها و کتابخانه ها پرهیز کردیم. ولی درعین حال، کتابخانه ضروری SafeMath ، فانکشن های اجباری، و ایونت های استاندارد ERC20 ، همه در متن قرارداد SmartCreator101.sol گنجانده شده است.
آدرس شخصی که این قرارداد هوشمند را روی شبکه دیپلوی می کند، با نام owner ذخیره می کنیم. owner باید هنگام دیپلوی کردن قرارداد، نام ، سمبل و تعداد توکن ها را تعیین کند. این پارامترها همیشه ثابت خواهند ماند. ضمنا از همان ابتدا، کل توکن های تولید شده، برابر بالانس حساب owner قرار می گیرد. با این کار فقط owner برای فروش و یا واگذاری همه توکن ها، تصمیم خواهد گرفت. پارامتر decimals را برابر صفر در نظر گرفته ایم. یعنی فرض کردیم نمی توان کسری از یک توکن را ترانسفر کرد و همیشه یک عدد صحیح از توکن ها قابل خرج کردن هستند.
فانکشن های totalSupply و balanceOf از نوع فانکشن های view هستند. این نوع فانکشن ها، فقط مقادیر را از روی بلاکچین می خوانند و آن مقادیر را برمی گردانند. به همین دلیل حتی هزینه gas هم ندارند. یعنی اگر کسی totalSupply را کال کند، تعداد کل توکن ها را دریافت می کند و اگر فانکشن balanceOf راهمراه یک آدرس کال کند، بالانس همان آدرس را دریافت خواهد کرد. این دو فانکشن هیچ محاسباتی انجام نمی دهند، ولی به هرحال از فانکشن های اجباری استاندارد ERC20 هستند و حتما باید به همین شکل و بدون تغییر در قرارداد باشند. به عنوان مثال اگر بخواهید تغییری در فانکشن balanceOf ایجاد کنید و قبل از مقدار بالانس، نام توکن را هم در خروجی فانکشن اضافه کنید؛ احتمالا کیف های پول ERC20 درست کار نمی کنند.
فانکشن بعدی transfer است. همیشه شخص فرستنده، این فانکشن را کال می کند و همزمان آدرس گیرنده و تعداد توکن های ارسالی را مشخص می نماید. سپس این فانکشن همه کنترل های لازم را انجام می دهد و اگر مشکلی پیدا نکرد، بالانس فرستنده و گیرنده را آپدیت می کند و به اصطلاح، ترانسفر کامل می شود. چون فرستنده این فانکشن را کال می کند، پس خودش باید هزینه gas را پرداخت کند. ضمنا چون در فانکشن های دیگر هم باید عملیات ترانسفر را به همین شکل مدل کنیم، پس بهتر دیدیم که یک فانکشن دیگر به نام transfer_ بصورت اینترنال اضافه نماییم. به این ترتیب، فقط یکبار همه کنترل ها و محاسبات مربوط به ترانسفر را تایپ می کنیم. همچنین برای کنترل Overflow (هنگام عملیات جمع برای متغییرهای uint256) نیز فانکشین دیگری به نام add اضافه کردیم. این فانکشن اینترنال و pure است. یعنی اطلاعات بلاکچین را نمی خواند و هیچ چیز هم در بلاکچین ذخیره نمی کند.
سه فانکشن هنوز بررسی نشده اند. فانکشن approve وقتی کاربرد دارد که کسی بخواهد برای خرج کردن تعدادی از توکن های خودش، به شخص دیگری وکالت بدهد. برای اینکار او باید این فانکشن را کال کند و همزمان آدرس اسپاندر و تعداد توکن ها (allowance) را مشخص نماید. سپس این فانکشن، آدرس هولدر، آدرس اسپاندر و همچنین تعداد توکن ها را دریک مپینگ تو در تو، ذخیره می کند. نام فانکشن بعدی transferFrom است. این فانکشن توسط یک اسپاندرکال می شود و او همزمان باید، آدرس هولدر، آدرس recipient و تعداد توکن های ارسالی (کمتر یا مساوی allowance) را مشخص کند. البته این فانکشن نیز همه کنترل های لازم را انجام خواهد داد و اگر مشکلی وجود نداشته باشد، این فاکشن اجرا می شود و بالانس فرستنده (هولدر)، بالانس recipient ، و مقدار allowance را آپدیت می کند. لطفا توجه کنید که برای اجرای فانکشن approve ، هزینه gas توسط خود هولدر پرداخت می شود. اما برای اجرای فانکشن transferFrom ، هزینه gas باید توسط اجرا کننده قرارداد، یعنی اسپاندر پرداخت شود. به عبارت دیگر، با این که بالانس اسپاندر هنگام اجرای فانکشن approve ، ثابت است و تغییر نمی کند، ولی هنگام اجرای فانکشن transferFrom ، به مقدار هزینه gas کاهش می یابد. آخرین فانکشن، allowance نام دارد که یک فانکشن از نوع view می باشد. هر کس بدون پرداخت هزینه gas می تواند این فانکشن را کال کند و مقدار باقیمانده allowance را از روی بلاکچین بخواند. بدیهی است که هنگام کال کردن این فانکشن، می بایست آدرس هولدر و آدرس اسپاندر مورد نظر خود را مشخص کند.
نام ایونت اول، Transfer می باشد. پس از هر تراکنش موفق، این ایونت به محیط خارج قرارداد پیام می فرستد و مشخصات تراکنش در بلاکچین ذخیره می شود. پارامترهای این ایونت، آدرس فرستنده، آدرس recipient ، و تعداد توکن های ارسالی می باشند. نام ایونت دوم، Approval است. هر بار که فانکشن approve با موفقیت اجرا می شود، این ایونت نیز در بلاکچین ذخیره می گردد. ایونت دوم هم سه پارامتر دارد. پارامترهای این ایونت به ترتیب، آدرس هولدر، آدرس اسپاندر و مقدار allowance هستند.
حالا وقت این است که قرارداد خودمان را تست کنیم. برای این کار، ما از شبکه تست Rinkeby و MetaMask استفاده می کنیم. پس از کامپایل کردن قرارداد در ریمکیس، به بخش دیپلوی می رویم. سپس در باکس ENVIRONMENT ، باید گزینه Injected web3 را انتخاب کنیم. البته ما در شبکه تست Rinkeby چندین اکانت داریم. مثلا با اکانت شماره یک، قرارداد را دیپلوی میکنیم و مقادیر زیر را به قرارداد پاس می دهیم:
name = "AKA Token" ==> The name of the coins
symbol = "AKA" ==> Symbol of the coins
totalValue = 1000000 ==> Total number of coins
به این ترتیب آدرس اکانت شماره یک، owner این قرارداد خواهد بود و مطابق کدهای قرارداد، بالانس owner برابر یک میلیون آکا توکن ثبت می شود. قبل از اینکه تک تک فانکشن ها را تست کنیم، بهتر است آکا توکن را به کیف پول متامکس معرفی کنیم تا بتوانیم بالانس اکانت ها را همزمان در متامکس هم ببینیم. به اصطلاح باید آکا توکن را به کیف پول متامکس، add کنیم. برای این کار ابتدا از ریمکس، آدرس قرارداد دیپلوی شده را کپی کرده و در متامکس، این آدرس را روی باکس مخصوص پیست می کنیم. ضمنا سمبل توکن یعنی «AKA» را در باکس پائین تر تایپ می نمائیم. وقتی آکا توکن روی اکانت شماره یک add شود، متامکس بالانس این آکانت را یک میلیون آکا توکن نمایش می دهد.
حالا به راحتی می توانیم عملکرد همه فانکشن های قرارداد را تست کنیم. فانکشن های آبی رنگ از نوع view هستند و هزینه gas ندارند. ولی اگر فانکشن های نارنجی رنگ را کال کنیم، هزینه gas توسط بلاکچین از ما دریافت می شود. البته فعلا قرارداد ما روی شبکه تست Rinkeby دیپلوی شده است و هزینه gas با اتر فیک پرداخت خواهد شد. در ادامه، تصاویر بعضی از تست های انجام شده را می بینید. شما هم می توانید تست های بیشتری انجام دهید تا از عملکرد این قرارداد، مطمئن شوید.