اجازه دهید با یک مثال اساسی شروع کنیم که مقدار یک متغیر را تعیین میکند و آن را برای سایر قراردادها در معرض دید قرار میدهد. اگر در حال حاضر همه چیز را متوجه نشدید ایرادی ندارد، بعداً به جزئیات بیشتر خواهیم پرداخت.
Table of contents [Show]
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}
خط بعدی مشخص میکند که منبع کد برای نسخه Solidity 0.4.16 یا نسخه جدیدتر زبان نوشته شده است، اما شامل نسخه 0.9.0 نمیشود. این مسئله برای اطمینان از این است که قرارداد با یک نسخه کامپایلر جدید (breaking) قابل کامپایل نیست و میتواند رفتار متفاوتی داشته باشد. پراگماها دستورالعمل های رایج برای کامپایلرها در مورد نحوه برخورد با منبع کد هستند (e.g. pragma once).
قرارداد در مفهوم Solidity مجموعه ای از کد (کارکردهای آن) و داده ها (وضعیت آن) است که در یک آدرس خاص در بلاک چین اتریوم قرار دارد. خط ;uint storedData ، یک متغیر حالت به نام storedData از نوع uint (عدد صحیح بدون علامت 256 بیتی) را اعلام می کند. شما می توانید آن را به عنوان یک شکاف در یک پایگاه داده در نظر بگیرید که می توانید با فراخوانی توابع کدی که پایگاه داده را مدیریت می کند، پرس و جو کرده و تغییر دهید. در این مثال، قرارداد توابع set و get را تعریف می کند که می تواند برای تغییر یا بازیابی مقدار متغیر استفاده شود.
برای دسترسی به یک عضو (مانند یک متغیر حالت) قرارداد فعلی، معمولاً پشوند .this را اضافه نمیکنید، شما فقط مستقیماً از طریق نام آن به آن دسترسی دارید. برخلاف برخی از زبانهای دیگر، حذف آن موضوع سلیقهای نیست، بلکه به روشی کاملاً متفاوت برای دسترسی به عضو منجر میشود، اما بعداً در این مورد بیشتر توضیح خواهیم داد.
این قرارداد هنوز کار زیادی انجام نمیدهد به جز اینکه (به دلیل زیرساختهای ساخته شده توسط اتریوم) به هر کسی اجازه میدهد یک شماره واحد را ذخیره کند که برای هر کسی در جهان قابل دسترسی است، بدون اینکه راه (امکانی) برای جلوگیری از انتشار این شماره وجود داشته باشد. هر کسی میتواند دوباره با مقدار متفاوتی با set فراخوانی کند و شماره شما را بازنویسی کند، اما این شماره همچنان در تاریخچه زنجیره بلاک ذخیره میشود. بعداً خواهید دید که چگونه می توانید محدودیت های دسترسی را اعمال کنید تا فقط شما بتوانید شماره را تغییر دهید.
پیشنهاد ویژه: آموزش رایگان سالیدیتی
هشدار
در استفاده از متن یونیکد مراقب باشید، زیرا کاراکترهای مشابه (یا حتی یکسان) می توانند نقاط کد متفاوتی داشته باشند و به عنوان یک آرایه بایت متفاوت کدگذاری می شوند.
توجه داشته باشید
همه شناسه ها (نام قرارداد، نام تابع و نام متغیر) به مجموعه کاراکترهای ASCII محدود می شوند. امکان ذخیره داده های رمزگذاری شده UTF-8 در متغیرهای رشته ای وجود دارد.
پیشنهاد ویژه: برنامه نویسی بلاکچین
قرارداد زیر ساده ترین شکل یک ارز دیجیتال را پیاده سازی میکند. این قرارداد فقط به خالق آن اجازه میدهد تا سکههای جدید ایجاد کند (طرحهای انتشار مختلف امکانپذیر است). هر کسی می تواند بدون نیاز به ثبت نام با نام کاربری و رمز عبور برای یکدیگر سکه ارسال کند، تنها چیزی که نیاز دارید یک جفت کلید اتریوم است.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract Coin {
// The keyword "public" makes variables accessible from other contracts
address public minter;
mapping(address => uint) public balances;
// Events allow clients to react to specific contract changes you declare
event Sent(address from, address to, uint amount);
// Constructor code is only run when the contract is created
constructor() {
minter = msg.sender;
}
// Sends an amount of newly created coins to an address
// Can only be called by the contract creator
function mint(address receiver, uint amount) public {
require(msg.sender == minter);
balances[receiver] += amount;
}
// Errors allow you to provide information about why an operation failed.
// They are returned to the caller of the function.
error InsufficientBalance(uint requested, uint available);
// Sends an amount of existing coins from any caller to an address
function send(address receiver, uint amount) public {
if (amount > balances[msg.sender]) {
revert InsufficientBalance({
requested: amount,
available: balances[msg.sender]
});
}
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount);
}
}
این قرارداد مفاهیم جدیدی را معرفی می کند، اجازه دهید آنها را یکی یکی مرور کنیم.
خط ;address public minter یک متغیر حالت از نوع آدرس را اعلام میکند. نوع address یک مقدار 160 بیتی است که اجازه هیچ گونه عملیات حسابی را نمی دهد. این برای ذخیره آدرس قراردادها یا هش از نصف عمومی یک جفت کلید متعلق به حساب های خارجی مناسب است.
کلمه کلیدی public به طور خودکار تابعی را ایجاد میکند که به شما امکان میدهد خارج از قرارداد به مقدار فعلی متغیر حالت دسترسی داشته باشید. بدون این کلمه کلیدی، قراردادهای دیگر راهی برای دسترسی به متغیر ندارند. کد تابع تولید شده توسط کامپایلر معادل زیر است (external را نادیده بگیرید و فعلا view کنید):
function minter () external view returns ( address ) { return minter; }
شما می توانید تابعی مانند تابع بالا را خودتان اضافه کنید، اما یک متغیر تابع و حالت با همان نام خواهید داشت. شما نیازی به انجام این کار ندارید، کامپایلر آن را برای شما مشخص می کند.
خط بعدی، mapping (address => uint) public balances; (address => uint) یک متغیر حالت عمومی ایجاد میکند، اما نوع داده پیچیده تری است. نوع mapping به اعداد صحیح بدون علامت آدرس می دهد.
نگاشتها را میتوان بهعنوان جداول هش دید که تقریباً مقداردهی اولیه شدهاند، به طوری که هر کلید ممکن است از ابتدا وجود داشته باشد و به مقداری نگاشت شود که نمایش بایت آن تماماً صفر است. با این حال، نه میتوان فهرستی از تمام کلیدهای یک mapping را به دست آورد و نه فهرستی از همه مقادیر. آنچه را که به mapp اضافه کردهاید، ضبط کنید، یا از آن در زمینه ای استفاده کنید که به آن نیازی نیست. یا حتی بهتر است، فهرستی را نگه دارید یا از نوع داده مناسب تری استفاده کنید.
تابع getter ایجاد شده توسط کلمه کلیدی pubilc در مورد mapping پیچیده تر است. به شکل زیر به نظر می رسد:
function balances(address account) external view returns (uint) {
return balances[account];
}
می توانید از این تابع برای استعلام موجودی یک حساب استفاده کنید.
خط ;event Sent(address from, address to, uint amount) یک “رویداد” را اعلام می کند که در آخرین خط تابع send منتشر می شود. مشتریان اتریوم مانند برنامه های کاربردی وب می توانند بدون هزینه زیاد به این رویدادهای منتشر شده در بلاک چین گوش دهند. به محض انتشار، شنونده آرگومان های from ، to و amount دریافت می کند که امکان ردیابی تراکنش ها را فراهم می کند.
برای گوش دادن به این رویداد، میتوانید از کد جاوا اسکریپت زیر استفاده کنید، که از web3.js استفاده میکند تا Coin قرارداد شی را ایجاد کند، و هر رابط کاربری تابع balances تولید شده بهطور خودکار را از بالا فراخوانی میکند.
Coin.Sent().watch({}, '', function(error, result) {
if (!error) {
console.log("Coin transfer: " + result.args.amount + " coins were sent from " + result.args.from + " to " + result.args.to + ".");
console.log("Balances now:\n" +
"Sender: " + Coin.balances.call(result.args.from) +
" Receiver: " + Coin.balances.call(result.args.to));
}
});
constructor یک تابع خاص است که در حین ایجاد قرارداد اجرا میشود و نمیتوان آن را پس از آن فراخوانی کرد. در این صورت آدرس شخص سازنده قرارداد را به طور دائم ذخیره میکند. متغیر msg (همراه با tx و block ) یک متغیر جهانی ویژه است که حاوی ویژگیهایی است که امکان دسترسی به بلاک چین را فراهم میکند. msg.sender همیشه آدرسی است که فراخوانی تابع فعلی (خارجی) از آنجا آمده است. توابعی که قرارداد را تشکیل میدهند و کاربران و قراردادها میتوانند با آنها فراخوانی شوند، mint و send هستند.
تابع mint مقداری از سکه های تازه ایجاد شده را به آدرس دیگری ارسال میکند. تابع requir شرایطی تعریف شده را فراخوانی میکند که در صورت عدم تحقق همه تغییرات را برمیگرداند. در این مثال، ;require(msg.sender == minter) تضمین میکند که فقط سازنده قرارداد می تواند mint را صدا کند. به طور کلی، سازنده میتواند هر تعداد توکن را که دوست دارد ضرب (mint) کند، اما در یک نقطه، این امر منجر به پدیدهای به نام “سر ریز” میشود. توجه داشته باشید که به دلیل محاسبات پیشفرض Checked، اگر عبارت ;balances[receiver] += amount باشد، تراکنش برمیگردد(سرریز میشود)، به عنوان مثال، هنگامی که balances[receiver] + amount در محاسبات مورد نظر بزرگتر از حداکثر مقدار uint(2**256 – 1) شود. این نکته همچنین برای عبارت ; balances[receiver] += amount در تابع send نیز درست است.
خطاها به شما این امکان را می دهند که اطلاعات بیشتری در مورد علت شکست یک شرایط یا عملیات به صدازننده ارائه دهید. خطاها همراه با عبارت revert استفاده میشوند. دستور revert بدون قید و شرط تمام تغییرات مشابه تابع require را لغو و برمیگرداند، اما همچنین به شما امکان میدهد نام یک خطا و دادههای اضافی را که برای صدازننده (و در نهایت به front-end برنامه یا block explorer) ارائه میشود، ارائه دهید. به طوری که یک خرابی را می توان راحت تر اشکال زدایی کرد یا به آن واکنش نشان داد.
تابع send میتواند توسط هر کسی (که قبلاً تعدادی از این سکهها را دارد) برای ارسال سکهها به دیگران استفاده شود. اگر فرستنده سکه های کافی برای ارسال نداشته باشد، شرط if به درستی ارزیابی می شود. در نتیجه، revert باعث می شود عملیات با شکست مواجه شود و در عین حال جزئیات خطا را با استفاده از خطای InsufficientBalance به فرستنده ارائه می دهد.
توجه داشته باشید
اگر از این قرارداد برای ارسال سکه به یک آدرس استفاده کنید، وقتی به آن آدرس در کاوشگر بلاک چین نگاه میکنید چیزی نمیبینید، زیرا رکورد ارسال سکه و موجودیهای تغییر یافته فقط در ذخیرهسازی دادههای این قرارداد سکه خاص ذخیره میشود. با استفاده از رویدادها، می توانید یک “کاوشگر بلاک چین” ایجاد کنید که تراکنش ها و مانده های سکه جدید شما را ردیابی می کند، اما باید آدرس قرارداد سکه را بررسی کنید نه آدرس صاحبان سکه.
پیشنهاد یادگیری: آموزش رایگان سالیدیتی
درک بلاک چین به عنوان یک مفهوم برای برنامه نویسان چندان سخت نیست. دلیل آن این است که بیشتر پیچیدگیها (استخراج، هش، رمزنگاری منحنی بیضی، شبکههای همتا به همتا و غیره) فقط برای ارائه مجموعه خاصی از ویژگیها و وعدهها برای پلتفرم وجود دارد. هنگامی که این ویژگی ها را پذیرفتید، لازم نیست نگران فناوری زیربنایی باشید – یا برای استفاده از آن باید بدانید که AWS آمازون چگونه به صورت داخلی کار می کند؟
بلاک چین یک پایگاه داده تراکنشی مشترک در سطح جهانی است. این بدان معنی است که همه فقط با شرکت در شبکه می توانند ورودی های پایگاه داده را بخوانند. اگر می خواهید چیزی را در پایگاه داده تغییر دهید، باید یک تراکنش به اصطلاح ایجاد کنید که باید توسط بقیه پذیرفته شود. کلمه تراکنش به این معناست که تغییری که میخواهید انجام دهید (فرض کنید میخواهید همزمان دو مقدار را تغییر دهید) یا اصلاً انجام نشده یا کاملاً اعمال شده است. علاوه بر این، در حالی که تراکنش شما در پایگاه داده اعمال می شود، هیچ تراکنش دیگری نمی تواند آن را تغییر دهد.
به عنوان مثال، جدولی را تصور کنید که موجودی همه حساب ها را در یک ارز الکترونیکی فهرست می کند. اگر انتقال از یک حساب به حساب دیگر درخواست شود، ماهیت تراکنشی پایگاه داده تضمین می کند که اگر مبلغ از یک حساب کم شود، همیشه به حساب دیگر اضافه می شود. اگر به هر دلیلی امکان افزودن مبلغ به حساب هدف وجود نداشته باشد، اکانت منبع نیز تغییر نمی کند. علاوه بر این، یک تراکنش همیشه به صورت رمزنگاری توسط فرستنده (خالق) امضا می شود. این امر باعث میشود که دسترسی به تغییرات خاص پایگاه داده آسان باشد. در مثال ارز الکترونیکی، یک چک ساده تضمین می کند که تنها شخصی که کلید حساب را در دست دارد می تواند از آن پول منتقل کند.
یکی از موانع اصلی برای غلبه بر آن چیزی است که (در اصطلاح بیتکوین) « double-spend attack » نامیده میشود: اگر دو تراکنش در شبکه وجود داشته باشد که هر دو بخواهند یک حساب را خالی کنند، چه اتفاقی میافتد؟ فقط یکی از معاملات می تواند معتبر باشد، معمولاً تراکنشی که ابتدا پذیرفته می شود. مشکل این است که “first” یک اصطلاح عینی در یک شبکه همتا به همتا نیست.
پاسخ انتزاعی به این موضوع این است که شما مجبور نیستید اهمیت دهید. یک ترتیب پذیرفته شده جهانی از تراکنش ها برای شما انتخاب می شود و تضاد را حل می کند. تراکنشها در آنچه که «بلوک» نامیده میشود، دستهبندی میشوند و سپس اجرا و بین تمام گرههای شرکتکننده توزیع میشوند. اگر دو تراکنش با هم تناقض داشته باشند، تراکنش دوم رد می شود و بخشی از بلوک نمی شود.
این بلوکها یک توالی خطی را در زمان تشکیل میدهند و کلمه بلاک چین از آنجا گرفته شده است. بلوک ها در فواصل نسبتاً منظم به زنجیره اضافه می شوند – برای اتریوم این تقریباً هر 17 ثانیه است.
به عنوان بخشی از “مکانیسم انتخاب سفارش” (که “استخراج” نامیده می شود) ممکن است اتفاق بیفتد که هر از گاهی بلوک ها برگردانده شوند، اما فقط در “نوک” زنجیره. هر چه تعداد بلوک های بیشتری در بالای یک بلوک خاص اضافه شود، احتمال بازگشت این بلوک کمتر می شود. بنابراین ممکن است تراکنشهای شما برگردانده شوند و حتی از بلاک چین حذف شوند، اما هر چه بیشتر منتظر بمانید، احتمال آن کمتر خواهد بود.
توجه داشته باشید
تضمینی برای گنجاندن تراکنشها در بلوک بعدی یا هر بلوک خاصی وجود ندارد، زیرا این به عهده ارسالکننده تراکنش نیست، بلکه به ماینرها بستگی دارد که تعیین کنند تراکنش در کدام بلوک گنجانده شده است. اگر میخواهید فراخوانیهای آینده قرارداد خود را برنامهریزی کنید، میتوانید از یک ابزار اتوماسیون قرارداد هوشمند یا یک سرویس اوراکل استفاده کنید.
ماشین مجازی اتریوم یا EVM محیط زمان اجرا برای قراردادهای هوشمند در اتریوم است. نه تنها سندباکس (sandboxed)است، بلکه در واقع کاملاً ایزوله است، به این معنی که کدهای در حال اجرا در داخل EVM به شبکه، سیستم فایل یا سایر فرآیندها دسترسی ندارند. قراردادهای هوشمند حتی دسترسی محدودی به سایر قراردادهای هوشمند دارند.
دو نوع حساب در اتریوم وجود دارد که فضای آدرس یکسانی دارند: حسابهای خارجی که توسط جفتهای کلید عمومی-خصوصی (یعنی انسانها) کنترل میشوند و حسابهای قراردادی که توسط کد ذخیره شده همراه با حساب کنترل میشوند.
آدرس یک حساب خارجی از کلید عمومی تعیین می شود در حالی که آدرس یک قرارداد در زمان ایجاد قرارداد تعیین می شود (از آدرس سازنده و تعداد تراکنش های ارسال شده از آن آدرس مشتق شده است که اصطلاحاً “nonce” نامیده می شود).
صرف نظر از اینکه حساب کد را ذخیره میکند یا نه، EVM با این دو نوع حساب یکسان برخورد می کند.
هر حساب دارای یک ذخیره ارزش کلیدی دائمی است که از کلمات 256 بیتی به کلمات 256 بیتی به نام storage نگاشت میکند. علاوه بر این، هر حساب دارای موجودی در اتر (in “Wei” to be exact, 1 ether is 10**18 wei ) که می تواند با ارسال تراکنش هایی که شامل اتر است، تغییر یابد.
تراکنش پیامی است که از یک حساب به حساب دیگر ارسال می شود (که ممکن است یکسان یا خالی باشد، در ادامه می بینید). این میتواند شامل دادههای باینری (که به آن “ظرفیت ترابری” میگویند) و اتر باشد.
اگر حساب هدف حاوی کد باشد، آن کد اجرا می شود و محموله به عنوان داده ورودی ارائه می شود. اگر حساب هدف تنظیم نشده باشد (تراکنش گیرنده ندارد یا گیرنده null تنظیم شده است)، تراکنش یک قرارداد جدید ایجاد می کند. همانطور که قبلاً ذکر شد، آدرس آن قرارداد آدرس صفر نیست، بلکه آدرسی است که از فرستنده و تعداد تراکنش های ارسال شده آن (“nonce”) مشتق شده است. ظرفیت ترابری تراکنش ایجاد قرارداد به عنوان بایت کد EVM گرفته شده و اجرا می شود. داده های خروجی این اجرا به صورت دائمی به عنوان کد قرارداد ذخیره می شود. به این معنی که برای ایجاد یک قرارداد، کد واقعی قرارداد را ارسال نمی کنید، بلکه در واقع کدی را ارسال می کنید که در هنگام اجرا آن کد را برمی گرداند.
توجه داشته باشید
در حالی که یک قرارداد در حال ایجاد است، کد آن هنوز خالی است. به همین دلیل، نباید قرارداد در دست ساخت را دوباره فراخوانی کنید تا زمانی که سازنده آن اجرای آن را به پایان برساند.
به محض ایجاد، هر تراکنش با مقدار معینی گس شارژ میشود که باید توسط مبتکر معامله پرداخت شود (tx.origin ). در حالی که EVM تراکنش را انجام می دهد، گس به تدریج طبق قوانین خاص تخلیه میشود. اگر گس در هر نقطهای مصرف شود (یعنی منفی باشد)، یک استثنای خارج از گس ایجاد میشود که اجرا را به پایان میرساند و تمام تغییرات ایجاد شده را به حالت فریم فراخوانی فعلی برمیگرداند.
این مکانیسم استفاده اقتصادی از زمان اجرای EVM را تشویق میکند و همچنین به مجریان EVM (یعنی ماینرها / سهامداران) برای کارشان جبران میکند. از آنجایی که هر بلوک دارای حداکثر مقدار گس است، میزان کار مورد نیاز برای اعتبارسنجی یک بلوک را نیز محدود می کند.
قیمت گس مقداری است که توسط مبتکر معامله تعیین می شود، که باید gas_price * gas را از قبل به مجری EVM بپردازد. در صورتی که پس از اجرا مقداری گس باقی بماند، به مبدأ معامله مسترد می شود. در صورت استثنایی که تغییرات را برمیگرداند، گس مصرفشده مسترد نمیشود.
از آنجایی که مجریان EVM می توانند انتخاب کنند که یک تراکنش را لحاظ کنند یا نه، فرستندگان تراکنش نمی توانند با تعیین قیمت پایین گس از سیستم سوء استفاده کنند.
ماشین مجازی اتریوم دارای سه قسمت است که میتواند دادهها را ذخیره کند: Storage (ذخیره سازی)، memory (حافظه) و stack (انباشتن). هر حساب دارای یک منطقه داده به نام ذخیره است که بین فراخوانی عملکرد و تراکنشها پایدار است. Storage یک ذخیره کلیدی است که کلمات 256 بیتی را به کلمات 256 بیتی نگاشت میکند. شمارش Storage از داخل یک قرارداد ممکن نیست، خواندن آن نسبتاً پرهزینه است، و حتی بیشتر از آن مقداردهی اولیه و اصلاح فضای ذخیرهسازی. به دلیل این هزینه، باید آنچه را که در ذخیره سازی دائمی ذخیره میکنید، به مقداری که قرارداد برای اجرا نیاز دارد، به حداقل برسانید. دادههایی مانند محاسبات مشتقشده، ذخیرهسازی پنهان، و مجموعها را خارج از قرارداد ذخیره کنید. یک قرارداد نمیتواند در هیچ فضای ذخیرهسازی جدا از خود بخواند یا بنویسد.
دومین ناحیه داده، حافظه نامیده میشود که یک قرارداد یک نمونه تازه پاک شده برای هر تماس پیام دریافت میکند. حافظه خطی است و میتواند در سطح بایت آدرس دهی شود، اما خواندن به عرض 256 بیت محدود میشود، در حالی که عرض نوشتن می تواند 8 بیت یا 256 بیت باشد. حافظه با یک کلمه (256 بیتی) در هنگام دسترسی (خواندن یا نوشتن) به یک کلمه حافظه که قبلاً دست نخورده است (یعنی هرگونه تغییر در یک کلمه) گسترش مییابد.
در زمان توسعه، هزینه گس باید پرداخت شود. حافظه هر چه بزرگتر شود گرانتر است (در مقیاس درجه دوم). EVM یک ماشین ثبت نیست بلکه یک ماشین stack (انباشتن) است، بنابراین تمام محاسبات روی یک ناحیه داده به نام stack انجام می شود. حداکثر اندازه آن 1024 عنصر و حاوی کلمات 256 بیتی است. دسترسی به stack به روش زیر به انتهای بالایی محدود می شود: می توان یکی از 16 عنصر بالایی را در بالای stack کپی کرد یا بالاترین عنصر را با یکی از 16 عنصر زیر آن تعویض کرد. تمام عملیات های دیگر بالاترین دو (یا یک، یا بیشتر، بسته به عملیات) عنصر را از stack میگیرند و نتیجه را روی stack فشار میدهند. البته امکان انتقال عناصر stack به حافظه یا حافظه برای دسترسی عمیق تر به stack وجود دارد، اما دسترسی به عناصر دلخواه در عمق stack بدون برداشتن قسمت بالایی stack امکان پذیر نیست.
مجموعه دستورات EVM حداقل حفاظت به منظور جلوگیری از اجرای نادرست یا متناقض که میتواند باعث ایجاد مشکلات اجماع شود، میباشد. همه دستورالعملها بر روی نوع داده اصلی، کلمات 256 بیتی یا بر روی برشهایی از حافظه (یا آرایه های بایت دیگر) عمل میکنند. عملیات معمول حسابی، بیت، منطقی و مقایسه وجود دارد. پرشهای مشروط و بدون قید و شرط امکان پذیر است. علاوه بر این، قراردادها میتوانند به ویژگیهای مربوط به بلوک فعلی مانند شماره و زمان آن دسترسی داشته باشند. برای فهرست کامل، لطفاً فهرست کدهای عملیاتی را به عنوان بخشی از مستندات اسمبلی درون خطی ببینید.
قراردادها میتوانند قراردادهای دیگری را فراخوانی کنند یا اتر را از طریق فراخوانی پیام به حساب های غیرقراردادی ارسال کنند. فراخوانی پیام مشابه تراکنشها هست، زیرا دارای منبع، هدف، بار داده، اتر، گس و داده برگشتی هستند. در واقع، هر تراکنش از یک فراخوانی پیام سطح بالا تشکیل میشود که به نوبه خود میتواند فراخوانی پیامکی بیشتری ایجاد کند. یک قرارداد میتواند تصمیم بگیرد که چه مقدار از گس باقیمانده آن باید با فراخوانی پیام داخلی ارسال شود و چه مقدار از آن را می خواهد حفظ کند. اگر در فراخوانی داخلی (یا هر استثنای دیگری) یک استثنای خارج از گس اتفاق بیفتد، با یک مقدار خطا در stack علامت داده می شود.
در این حالت فقط گس ارسالی همراه با فراخوانی تمام می شود. در Solidity، فراخوانی قرارداد بهطور پیشفرض در چنین شرایطی یک استثنا دستی ایجاد میکند، به طوری که استثناها فراخوانی stack را حباب میکنند. همانطور که قبلاً گفته شد، قرارداد فراخوانی شده (که میتواند همان صدازننده باشد) یک نمونه حافظه تازه پاک شده را دریافت میکند و به ظرفیت ترابری فراخوانی – که در یک منطقه جداگانه به نام calldata ارائه می شود – دسترسی دارد. پس از اتمام اجرای آن، میتواند دادههایی را برگرداند که در مکانی در حافظه صدازننده که توسط صدازننده از قبل تخصیص داده شده ذخیره میشوند. همه این فراخوانیها کاملاً همزمان هستند.
فراخوانیها به عمق 1024 محدود میشوند، به این معنی که برای عملیات پیچیدهتر، حلقه ها باید بر فراخوانیهای بازگشتی ترجیح داده شوند. علاوه بر این، تنها 63/64 گس را میتوان در یک فراخوانی پیام ارسال کرد که در عمل باعث محدودیت عمق کمی کمتر از 1000 می شود.
نوع خاصی از فراخوانی پیام وجود دارد، به نام delegatecall که با message call یکسان است جدا از این که کد در آدرس مقصد در متن (یعنی در آدرس) اجرا میشود فراخوانی قرارداد و msg.sender و msg.sender مقادیر خود را تغییر نمیدهند.
این بدان معنی است که یک قرارداد میتواند به صورت پویا کد را از یک آدرس مختلف در زمان اجرا بارگذاری کند. ذخیرهسازی، آدرس فعلی و موجودی همچنان به قرارداد در حال فراخوانی اشاره دارد، فقط کد از آدرس فراخوانده گرفته میشود.
این امکان پیادهسازی ویژگی «کتابخانه» را در Solidity فراهم میکند: کد کتابخانه قابل استفاده مجدد که میتواند در فضای ذخیرهسازی قرارداد اعمال شود، مثلا به منظور پیاده سازی یک ساختار داده پیچیده.
این امکان وجود دارد که داده ها را در یک ساختار داده نمایه شده ویژه ذخیره کنید که تا سطح بلوک نگاشت میکند. این ویژگی به نام log توسط Solidity به منظور پیادهسازی رویدادها استفاده میشود. قراردادها نمیتوانند به اطلاعات log پس از ایجاد دسترسی داشته باشند، اما میتوان به طور موثر از خارج از بلاکچین به آنها دسترسی داشت. از آنجایی که بخشی از اطلاعات log در فیلترهای بلوم ذخیره میشوند، جستجوی این دادهها به روشی کارآمد و از نظر رمزنگاری امن امکانپذیر است، بنابراین همتایان شبکهای که کل بلاکچین را دانلود نمیکنند (به اصطلاح (light clients) کلاینتهای سبک) میتوانند همچنان این logs را پیدا کنید.
قراردادها حتی می توانند قراردادهای دیگری را با استفاده از یک اپکد خاص ایجاد کنند (یعنی آنها به سادگی آدرس صفر را به عنوان یک تراکنش فراخوانی نمی کنند). تنها تفاوت بین این تماسهای ایجاد شده و تماسهای پیام معمولی این است که ظرفیت ترابری دادهها اجرا میشوند و نتیجه بهعنوان کد ذخیره میشود و صدازننده/ایجادکننده آدرس قرارداد جدید را در stack دریافت میکند.
تنها راه حذف کد از بلاکچین زمانی است که قراردادی در آن آدرس عملیات خود تخریبی (selfdestruct ) را انجام دهد. اتر باقی مانده ذخیره شده در آن آدرس به یک هدف تعیین شده ارسال میشود و سپس ذخیرهسازی و کد از state (حالت) حذف میشود. حذف قرارداد در تئوری ایده خوبی به نظر میرسد، اما به طور بالقوه خطرناک است، زیرا اگر شخصی اتر را به قراردادهای حذف شده بفرستد، اتر برای همیشه از بین می رود.
هشدار
حتی اگر قراردادی با selfdestruct حذف شود، همچنان بخشی از تاریخچه بلاک چین است و احتمالاً توسط اکثر گرههای اتریوم حفظ میشود. بنابراین استفاده از selfdestruct مانند حذف داده ها از هارد دیسک نیست.
توجه داشته باشید
حتی اگر کد یک قرارداد شامل یک فراخوانی برای selfdestruct نباشد، همچنان میتواند آن عملیات را با استفاده از delegatecall یا فراخوانی کد ( callcode ) انجام دهد.
اگر میخواهید قراردادهای خود را غیرفعال کنید، باید آنها را با تغییر وضعیت داخلی غیرفعال کنید که باعث برگرداندن همه عملکردها میشود. این امر استفاده از قرارداد را غیرممکن می کند، زیرا اتر را فوراً برمی گرداند.
مجموعه کوچکی از آدرسهای قرارداد وجود دارد که خاص هستند: محدوده آدرس بین 1 و (شامل) 8 حاوی «قراردادهای از پیش کامپایلشده» است که میتوان آنها را به عنوان هر قرارداد دیگری نامید، اما رفتار آنها (و مصرف گاز آنها) با کد دخیره شده EVM در آدرس تعریف نشده است (آنها حاوی کد نیستند) اما در عوض در خود محیط اجرای EVM پیاده سازی می شوند.
زنجیره های مختلف سازگار با EVM ممکن است از مجموعه متفاوتی از قراردادهای از پیش کامپایل شده استفاده کنند. همچنین ممکن است در آینده قراردادهای از پیش کامپایل شده جدیدی به زنجیره اصلی اتریوم اضافه شود، اما به طور منطقی می توانید انتظار داشته باشید که آنها همیشه در محدوده 1 تا 0xffff (شامل) باشند.