همانطورکه در بخش اول گفته شد ما می خواهیم از بدو تولد توکن ها؛ علاوه بر اطلاعات تراکنش ها، سوابق همه آدرس هایی که حداقل یکبار «آکا توکن» دریافت کرده اند را ذخیره کنیم. به این ترتیب در طول زمان می توانیم با استفاده از مشخصات تراکنش ها، بالانس همه حساب ها را محاسبه نماییم. ضمنا خواهید دید که با استفاده از همین اطلاعات ذخیره شده، راستی آزمایی تراکنش ها و حل مشکل دوبار خرج کردن هم به سادگی انجام می شود. پس در این مرحله ما به استرات ها، مپینگ ها و محاسبات جدیدی نیاز داریم. از طرفی فراموش نکنیم که همواره باید استاندارد ERC20 رعایت شود.
برای انجام همه این کارها، یک روش این است که به صورت جداگانه یک قرارداد هوشمند سازنده بلاک BPSC را روی شبکه اتریوم دیپلوی نماییم و ازاین قرارداد در قرارداد موجود، استفاده کنیم. البته در این حالت دیپلوی قراردادها باید در دو مرحله انجام شود. (ابتدا مثلا باید قرارداد X دیپلوی شود و پس ازمشخص شدن آدرس آن، می توان این آدرس را در قرارداد Y ثبت کرد و نهایتا قرارداد Y را دیپلوی نمود) همچنین در این روش حتما باید موارد دیگری را نیز رعایت کنیم. اما هدف اصلی ما در این مقاله این است که درک جزئیات برای خوانندگان محترم ساده باشد. لذا برای گام دوم، ما یک قرارداد به نام SmartCreator102.sol آماده کرده ایم که برای ساحتن این فایل، همه موارد ضروری به متن قرارداد SmartCreator101.sol اضافه شده است و در حقیقت، فانکشن های فرارداد اول، در قرارداد دوم توسعه پیدا کرده اند. شما می توانید همه کدهای این مقاله را از اینجا دانلود نمایید.
مهمترین دیتاهای قرارداد جدید، در مپینگ accounts و استرات trxes ذخیره می شوند. مپینگ accounts همه اطلاعات مشتریان (بالانس و ....) را ذخیره می کند. حتی اگر آدرسی برای اولین بار «آکا توکن» دریافت کند و هنوز در این مپینگ عضو نباشد، بلافاصله همه مشخصات این اکانت به مپینگ accounts اضافه می گردد و این اکانت برای همیشه عضو می شود. استرات trxes مشخصات تراکنش ها را ذخیره می کند. عملکرد مپینگ تودرتو allowances در قرارداد جدید، دقیقا مانند قرارداد قبلی است. ضمنا تعدادی پارامتر دیگر هم به قرارداد جدید اضافه شده اند تا برنامه نویسی راحت تر انجام شود.
در قرارداد جدید؛ اولین مشخصاتی که در مپینگ accounts ذخیره می شود (مشتری شماره صفر)، مشخصات owner است. و اولین مشخصاتی که در استرات trxes ذخیره می شود (تراکنش شماره صفر)، مساوی شدن بالانس owner با تعداد کل توکن ها می باشد. ضمنا بقیه فانکشن های قرارداد قدیم و جدید، بسیار شباهت دارند. ولی فانکشن transfer_ استثناء است و تغییرات زیادی داشته است. اکثر وظایفی که از قرارداد جدید انتظار داریم، در همین فانکشن اتفاق می افتد. ما در ادامه عملکرد این فانکشن را دقیقا بررسی می کنیم و نگاه نزدیکتری به کدهای این فانکشن خواهیم داشت.
همانطور که در تصویر بالا می بینید؛ تقریبا کدهای ابتدایی فانکشن transfer_ تغییری نکرده است. یعنی مشخصات فانکشن، و require های ابتدایی، مثل قرارداد اول هستند. فقط ما یک modifier جدید به نام isLocked به مشخصات این فانکشن اضافه کردیم. این modifier اجازه نمی دهد که همزمان دو آدرس متفاوت، این فانکشن را کال کنند.
در تصویر بالا، ادامه کدهای فانکشن transfer_ را می بینید. این کدها مهمترین بخش قرارداد جدید هستند. البته شاید کمی گیج کننده به نظر برسند ولی نگران نباشید، زیرا این کدها فقط چند کار ساده را به ترتیب انجام می دهند. ما تصویر بالا را به چند بخش تقسیم کردیم و در ادامه برای هر کدام از این بخش ها، توضیح مختصری می دهیم.
در بخش A ؛ بالانس همه اکانت های ذخیره شده در قرارداد، با هم جمع می شوند و حاصل جمع در متغییر totalChecking ذخیره می گردد. فراموش نکنید که همیشه مبنای محاسبات، استیت همان لحظه قرارداد است که با آخرین بلاک شبکه اتریوم، آپدیت شده است.
بخش های C ، B و D ؛ فقط زمانی فعال می شوند که مقدار بدست آمده برای متغییر totalChecking با تعداد واقعی توکن های قرارداد، یعنی مقدار totalValue ، برابر نباشد. در این حالت ما متوجه می شویم که اشخاصی توانسته اند از غیرمتمرکز بودن شبکه استفاده کنند و یک یا چند تراکنش نامعتبر از توکن ما را، در بلاکچین اتریوم ثبت نمایند. به عبارت دیگر، اشخاصی با تقلب موفق شده اند که توکن های خود را دوبار خرج کنند. بنابراین برای اینکه بالانس همه حساب های این قرارداد تصحیح شوند، ما باید تراکنش های نامعتبر را شناسایی و حذف کنیم.
در بخش B ؛ بالانس تک تک حساب های قرارداد را برابر صفر می کنیم. البته شاید این کار ترسناک به نظر برسد، ولی خوشبختانه هیچ مشکلی وجود ندارد و ما هنوز اطلاعات همه تراکنش ها را داریم.
دربخش C ؛ از تراکنش شماره صفر( مربوط به owner) شروع می کنیم و تک تک تراکنش ها را به ترتیب بررسی می نماییم. بدیهی است که فقط و فقط وقتی یک تراکنش معتبر باشد، بالانس حساب های فرستنده و گیرنده، آپدیت می شوند. اصلی ترین شرط برای معتبر بودن یک تراکنش این است که بالانس فرستنده درآن لحظه، مساوی یا بیشتر از مبلغ تراکنش باشد. به این ترتیب کدهای این قسمت باعث می شوند که همه تراکنش های نامعتبری که با فریب و یا حتی اشتباه ماینرها، از طریق شبکه اتریوم در قرارداد ما ثبت شده اند، نادیده گرفته شوند. یعنی هیچوقت تراکنش های دو بار خرج کردن، نمی توانند مشکلی در محاسبات ما ایجاد کنند و همیشه بالانس حساب ها بصورت خودکار تصحیح می شوند. پس عملا نیازی به راستی آزمایی ماینرها هم وجود ندارد. البته شما می توانید تراکنش های نامعتبررا بلافاصله حذف کنید و حتی قبل از حذف کردن، موضوع را با ایونت به بیرون قرارداد اطلاع دهید تا روی بلاکچین اتریوم هم ثبت شود. ولی اگر حذف هم نکنید، هربار این تراکنش ها شناسایی شده و نادیده گرفته می شوند. به این نکته هم توجه کنید که این تراکنش های نامعتبر، به هرحال قبلا در بلاکچین اتریوم ذخیره شده اند.
کدهای این قسمت، کمی پیچیده به نظر می رسند، ولی در حقیقت عملکرد ساده ای دارند. اگر چند دقیقه جزئیات کدها را بررسی کنید، متوجه می شوید که این پیچیدگی ها صرفا به منظور دستیابی به دیتای ذخیره شده در استرات ها و مپینگ ها بوجود آمده اند. ضمنا با دقت بیشتر روی کدنویسی، مطمئن خواهید شد که منطق ریاضی همین چند خط کد، بسیار محکمتر از منطق انواع مکانیسم های اجماع است و کدهای این قرارداد می تواند جایگزین بسیار بهتری برای انواع مکانیسم های اجماع (برای تراکنش رمز ارزها و توکن ها) باشد. مشکل اساسی شبکه های غیرمتمرکز، همیشه مشکل دوبار خرج کردن بوده است. اما دراین قرارداد، ازنظر ریاضی امکان این اتفاق محال است. حتی برای دبل چک کردن کدهای قرارداد، یک require دیگر در بخش E خواهیم داشت.
در بخش D ؛ بالانس همه اکانت ها (که در بخش C اصلاح شده اند) با هم جمع می شوند و حاصل جمع درهمان متغییر totalChecking ذخیره می گردد. به عبارت دیگر، مقداری که در بخش A برای متغییر totalChecking محاسبه شده بود، اصلاح می شود.
در بخش E ؛ فقط یک require وجود دارد. این require یکبار دیگر، برابری مقدار بدست آمده برای متغییر totalChecking را با تعداد واقعی توکن های قرارداد (totalValue) کنترل می نماید. لطفا توجه کنید که این شرط حتما محقق خواهد شد. زیرا متغییر totalChecking یکبار در بخش A محاسبه شد و اگر برابرtotalValue نبود، در بخش های C ، B و D اصلاح گردید و حالا دیگر حتما این شرط برقرار است.
در ادامه برای سه بخش آخر از فانکشن transfer_ (تصویر بالا) توضیح مختصری می دهیم. این سه بخش، فقط وقتی اجرا می شوند که همه کنترل های لازم انجام شده باشند. البته همانطورکه در بخش های قبلی ملاحظه کردید، حتی درصورت وجود تراکنش های نامعتبر، بالانس همه حساب ها با دقت و از ابتدا اصلاح شده اند.
در بخش F ؛ اگر گیرنده توکن ها برای اولین بار «آکا توکن» دریافت کند، تمام مشخصات گیرنده در مپینگ accounts ذخیره می شود. ضمنا بلافاصله ایونت NewAccountWasOpened اجرا می گردد.
در بخش G ؛ ابتدا تعداد توکن های این ترانسفر یعنی مقدار amount ، از بالانس فرستنده کسر می گردد و سپس دقیقا به مقدار amount به بالانس گیرنده افزوده می گردد. همانطور که می دانید همیشه توصیه می شود که ابتدا بالانس فرستنده را آپدیت کنید و بعد آپدیت بالانس گیرنده را انجام دهید.
بخش H ؛ بخش پایانی فانکشن transfer_ است. در این بخش تمام مشخصات تراکنش جدید، در استرات trxes ذخیره می گردد. ضمنا بلافاصله ایونت Transfer اجرا می گردد.
شما به سادگی می توانید شبیه قرارداد اولیه، قرارداد جدید را نیز تست کنید. ولی لطفا توجه کنید که در حالت عادی تراکنش ها فقط به شرط معتبر بودن اجرا می شوند. به عبارت دیگر شما به سادگی نمی توانید عملکرد این قرار داد را در برخورد با یک تراکنش نامعتبر ببینید و تست کنید. پس چه باید کرد؟
پیشنهاد ما این است که قبل از دیپلوی کردن قرارداد، تغییرات کوچکی در کدها ایجاد نماییم، تا بتوانیم بصورت مصنوعی پدیده دوبار خرج کردن را شبیه سازی کنیم. برای این کار، ما فایل SmartCreator102x.sol را ایجاد کرده ایم و در ادامه تغییرات ایجاد شده در این فایل را توضیح می دهیم. فراموش نکنید که این فایل صرفا برای تست است و فایل اصلی در این مرحله همان فایل SmartCreator102.sol می باشد. ضمنا برای اینکه توکن های این مرحله با توکن های مرحله اول متفاوت باشند، نام سمبل توکن های مرحله دوم را AKAx گذاشته ایم.
مثلا فرض کنید که بازهم تعداد کل توکن های مرحله دوم، یک میلیون توکن «آکا ایکس» باشد و owner قرارداد نیز آکانت شماره یک است. به این ترتیب پس از دیپلوی کردن قرارداد، بالانس آکانت شماره یک، برابر یک میلیون خواهد بود. در تراکنش اول، پانصد هزار توکن از آکانت اول به آکانت دوم ارسال می کنیم. در تراکنش دوم نیز، پانصد هزار توکن دیگر از آکانت اول به آکانت سوم ارسال می گردد. حالا فرض می کنیم که در تراکنش سوم تقلب یا اشتباهی صورت می گیرد و آکانت سوم موفق می شود که تمام پانصد هزار توکن خود را دوبار خرج کند. یعنی مثلا اکانت سوم بتواند یک میلیون توکن را به آکانت چهارم ارسال کند. بنابراین پس از تراکنش سوم، بالانس آکانت ها به شکل زیر خواهد بود:
Account balance number one = 0
Account balance number two = 500000
Account balance number three = 0
Account balance number four = 1000000
اما خواهید دید که این اشتباه ادامه پیدا نمی کند و قرارداد ما هنگام تراکنش چهارم بصورت خودکار، تراکنش نامعتبر را شناسایی کرده و آن را نادیده می گیرد. ضمنا همزمان بالانس همه حساب ها را تصحیح خواهد کرد. یعنی اگر مثلا در تراکنش چهارم، یکصد هزار توکن از آکانت شماره دو به آکانت شماره یک ارسال گردد، در این حالت سورپرایز خواهید شد و بالانس آکانت ها به شکل زیر تصحیح می شود:
Account balance number one = 100000
Account balance number two = 400000
Account balance number three = 0
Account balance number four = 500000
البته با روش های مختلفی می توان امکان این نوع تقلب (یا اشتباه) را برای قرارداد ایجاد کرد. مثلا در فایل SmartCreator102x.sol برای اینکه تراکنش شماره سه، قابلیت دوبار خرج کردن را پیدا کند، ما فقط کدهای زیر را به فایل SmartCreator102.sol اضافه کرده ایم:
ما برای تست قرارداد در مرحله دوم؛ فایل SmartCreator102x.sol را مطابق مشخصاتی که قبلا گفته شد، روی شبکه تست Rinkeby دیپلوی کردیم. سپس مطابق توضیحات بالا، چهار تراکنش انجام دادیم. اگر به تصاویر زیر توجه کنید، خواهید دید که نتایج تست های انجام شده کاملا منطبق با پیش بینی ما هستند. البته شما هم می توانید تست های بیشتری انجام دهید تا از عملکرد این قرارداد مطمئن شوید.
به این ترتیب در طول زمان، همیشه یک دفتر کل مستقل و مورد اطمینان برای توکن های خودمان خواهیم داشت و دیگر نیازی به راستی آزمایی ماینرها نیست. البته همانطور که ملاحظه کردید، در این قرارداد راستی آزمایی تراکنش ها قبل از هر تراکنش انجام می شود. اما این زمانبندی در قراردادهای دیگر می تواند بر مبنای دیگری ( مثلا هر چند ثانیه) تنظیم گردد. ضمنا ما در گام سوم برای دسترسی به اطلاعات حساب ها و تراکنش ها، فانکشن های متعددی را ارائه می کنیم. ولی توجه داشته باشید که این قرارداد روی بلاکچین اتریوم دیپلوی شده است و اطلاعات تراکنش ها بصورت ایونت، در بلاکچین اتریوم ذخیره شده و قابل دسترس است. برای دیدن این اطلاعات می توانید از راهنمایی ها و تصاویر زیر استفاده کنید.