در سالیدیتی، رویدادها نقش ابزارهای ثبت و گزارش را ایفا میکنند و عملکردی شبیه به print
یا console.log
در محیط اتریوم دارند. در این بخش، نحوه استفاده از رویدادها، اصول مهم در طراحی آنها و همچنین نکات فنی را بررسی میکنیم که بسیاری از منابع از آنها غافل میمانند.
آموزش رویدادها در سالیدیتی
مثال زیر نشان میدهد که چطور میتوان یک رویداد ساده را در سالیدیتی منتشر کرد:
1 2 3 4 5 6 7 8 9 |
contract ExampleContract { // بعداً درباره اهمیت پارامتر indexed توضیح خواهیم داد. event ExampleEvent(address indexed sender, uint256 someValue); function exampleFunction(uint256 someValue) public { emit ExampleEvent(sender, someValue); } } |
توکن های ERC20 یکی از شناختهشدهترین کاربردهای رویدادها را ارائه میدهند. این توکن ها هنگام انتقال، اطلاعاتی مانند آدرس فرستنده، آدرس گیرنده و مقدار توکن منتقلشده را در قالب یک رویداد ثبت میکنند.
در نگاه اول ممکن است این سؤال مطرح شود:
آیا این اطلاعات تکراری نیستند؟
در واقع، با بررسی تراکنش ها و تحلیل calldata
هم میتوان به همین داده ها رسید.
این نکته درست است؛ حذف رویدادها به منطق کلی قرارداد آسیبی نمیزند. اما اگر توسعهدهنده ای این کار را انجام دهد، بررسی سابقه تراکنش ها برای کاربران و برنامه های خارجی به فرآیندی دشوار و ناکارآمد تبدیل میشود. رویدادها راهکاری سریع، ساختارمند و بهینه برای تحلیل اطلاعات تاریخی در بلاک چین فراهم میکنند.
بازیابی سریعتر تراکنش ها در اتریوم
کلاینت اتریوم هیچ رابط برنامه نویسی (API) مشخصی برای فهرست کردن تراکنش ها بر اساس نوع آنها ارائه نمیدهد. اگر قصد دارید تراکنش های گذشته را بررسی کنید، چند گزینه در اختیار دارید:
-
getTransaction
-
getTransactionFromBlock
تابع getTransactionFromBlock
تنها میتواند تراکنش های موجود در یک بلاک مشخص را به شما نشان دهد و نمیتواند اطلاعاتی درباره تراکنش های مربوط به یک قرارداد هوشمند در بلاک های مختلف ارائه کند.
تابع getTransaction
هم فقط زمانی کارآمد است که هش دقیق تراکنش را در اختیار داشته باشید. اما رویدادها، در مقایسه با این روشها، بسیار سادهتر قابل بازیابی هستند. در این زمینه، کلاینت اتریوم گزینههای زیر را ارائه میدهد:
-
events
-
events.allEvents
-
getPastEvents
برای استفاده از این توابع، ابتدا آدرس قرارداد هوشمند مورد نظر را مشخص میکنید. سپس با توجه به پارامترهای جستجو، این توابع مجموعهای از رویدادهایی را که قرارداد ثبت کرده است، برمیگردانند.
اتریوم هیچ روشی برای بازیابی تمام تراکنش های مربوط به یک قرارداد هوشمند ارائه نمیدهد، اما به شما این امکان را میدهد که تمام رویدادهای منتشرشده توسط یک قرارداد را بازیابی کنید.
چرا چنین تفاوتی وجود دارد؟ برای اینکه رویدادها سریع و بهینه قابل جستجو باشند، باید اطلاعات آنها بهصورت مجزا ذخیره شود؛ این موضوع باعث افزایش حجم دادهها میشود. اگر اتریوم میخواست این سطح از قابلیت جستجو را برای تمام تراکنش ها فراهم کند، اندازه بلاک چین بهصورت چشمگیری افزایش پیدا میکرد. در عوض، زبان سالیدیتی این امکان را در اختیار برنامه نویس میگذارد تا خودش تعیین کند که چه اطلاعاتی ارزش این افزایش حجم را دارند. به این ترتیب، فقط داده هایی که بازیابی سریع آنها اهمیت دارد، بهعنوان رویداد ثبت میشوند تا از بیرون زنجیره (off-chain) بتوان آنها را به راحتی واکشی کرد.
گوش دادن به رویدادها (Listening to Events)
توسعه دهندگان سالیدیتی رویدادها را با هدف استفاده خارج از زنجیره (off-chain) طراحی کردهاند.
در ادامه، مثالی از کاربرد عملی API هایی که پیشتر معرفی شدند ارائه میشود. در این مثال، کلاینت به رویدادهایی که یک قرارداد هوشمند منتشر میکند، گوش میدهد و در صورت انتشار، آنها را دریافت و پردازش میکند.
مثال اول: گوش دادن به رویدادهای انتقال در توکن های ERC20
در این مثال، هر زمان که یک توکن ERC20 رویداد Transfer
را منتشر میکند، کد نوشتهشده بهطور خودکار یک تابع برگشتی (callback) را اجرا میکند.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const { ethers } = require("ethers"); // const provider = your provider const abi = [ "event Transfer(address indexed from, address indexed to, uint256 value)" ]; const tokenAddress = "0x..."; const contract = new ethers.Contract(tokenAddress, abi, provider); contract.on("Transfer", (from, to, value, event) => { console.log(`Transfer event detected: from=${from}, to=${to}, value=${value}`); }); |
مثال دوم: فیلتر کردن رویداد Approval برای یک آدرس خاص در توکن ERC20
برای بررسی رویدادهای گذشته، میتوانیم از کد زیر استفاده کنیم. در این مثال، به دنبال تراکنش های Approval
در یک قرارداد توکن ERC20 میگردیم که مربوط به یک آدرس مشخص باشند.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const ethers = require('ethers'); const tokenAddress = '0x...'; const filterAddress = '0x...'; const tokenAbi = [ // ... ]; const tokenContract = new ethers.Contract(tokenAddress, tokenAbi, provider); // this line filters for Approvals for a particular address. const filter = tokenContract.filters.Approval(filterAddress, null, null); tokenContract.queryFilter(filter).then((events) => { console.log(events); }); |
1 |
tokenContract.filters.Transfer(address1, address2, null); |
در کد بالا، مقدار null
به این معناست که هر مقدار برای آن فیلد قابل پذیرش است. در رویداد Transfer
نیز همین مقدار به ما اجازه میدهد تمام مقادیر انتقال را بدون هیچ محدودیتی بررسی کنیم.
برای درک بهتر، نگاهی به نمونهای مشابه در کتابخانه web3.js
میاندازیم. در این نسخه، بازه جستجو با پارامترهای fromBlock
و toBlock
مشخص شده است. علاوه بر این، نشان میدهیم چگونه میتوان بهطور همزمان رویدادهایی را دریافت کرد که فرستنده آنها یکی از چند آدرس خاص باشد. این آدرسها با استفاده از شرط منطقی «OR» با یکدیگر ترکیب میشوند.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
const Web3 = require('web3'); const web3 = new Web3('https://rpc-endpoint'); const contractAddress = '0x...'; // The address of the ERC20 contract const contractAbi = [ { "anonymous": false, "inputs": [ { "indexed": true, "name": "from", "type": "address" }, { "indexed": true, "name": "to", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" } ]; const contract = new web3.eth.Contract(contractAbi, contractAddress); const senderAddressesToWatch = ['0x...', '0x...', '0x...']; // The addresses to watch for transfers from const filter = { fromBlock: 0, toBlock: 'latest', topics: [ web3.utils.sha3('Transfer(address,address,uint256)'), null, senderAddressesToWatch, ] }; contract.getPastEvents('Transfer', { filter: filter, fromBlock: 0, toBlock: 'latest', }, (error, events) => { if (!error) { console.log(events); } }); |
مثال سوم: ساخت جدول رتبهبندی (Leaderboard) بدون ذخیره دادهها در قرارداد
فرض کنید یک قرارداد هوشمند دارید که افراد میتوانند به آن اتریوم اهدا کنند و شما میخواهید در بخش رابط کاربری (Frontend)، اهداکنندگان را بر اساس میزان کمک مالی رتبهبندی کنید. در ادامه، ابتدا یک روش ناکارآمد را بررسی میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
contract Donations { struct Donation { address donator; uint256 amount; } Donation[] public donations; // frontend queries this fallback() external payable { donations.push(Donation({ donator: msg.sender, amount: msg.value })); } // more functions for the owner to withdraw } |
اگر قرار نیست این اطلاعات روی زنجیره (on-chain) خوانده شوند، این روش ساده انگارانه است. چون هر بار که فردی اتریوم ارسال میکند، باید هزینه گس قابل توجهی برای ذخیره داده ها روی بلاک چین پرداخت کند.
در مقابل، میتوان راهکار بسیار بهینه تری با استفاده از رویدادها (events) پیاده سازی کرد:
1 2 3 4 5 6 7 |
contract Donations { event Donation(address indexed donator; uint256 amount); fallback() external payable { emit Donation(msg.sender, msg.value); } // more functions for the owner to withdraw } |
در این روش، رابط کاربری (Frontend) میتواند به راحتی تمام رویدادهای Donation
را از قرارداد هوشمند بازیابی کرده و آنها را بر اساس مقدار amount
مرتب کند تا جدول رتبه بندی اهداکنندگان ساخته شود.
نکته مهم اینجاست که رویدادها در وضعیت (state) بلاک چین ذخیره میشوند و موقتی یا فرار (ephemeral) نیستند. بنابراین، نیازی نیست نگران از دست رفتن رویدادی باشید؛ کلاینت در هر زمان میتواند دوباره همه رویدادها را استعلام و بازیابی کند.
رویدادهای indexed و non-indexed در سالیدیتی
در مثال قبلی، کد به درستی عمل میکرد چون رویدادهای Approve
و Transfer
در استاندارد ERC20 پارامتر sender
را بهصورت indexed تعریف کردهاند. در ادامه، نحوه تعریف این رویداد را در سالیدیتی مشاهده میکنید:
1 |
event Approval(address indexed owner, address indexed spender, uint256 value); |
اگر پارامتر owner
در این تعریف بهصورت indexed مشخص نشده بود، کد جاوا اسکریپتی که در بخش قبلی دیدید بدون هیچ پیغام خطایی، نتیجهای برنمیگرداند و عملاً عملکردی نداشت.
این نکته اهمیت زیادی دارد، چون به ما میگوید نمیتوان رویدادهای ERC20 را بر اساس مقدار value
فیلتر کرد؛ چون این فیلد indexed نیست. بنابراین، اگر بخواهید فقط تراکنش هایی با مقدار خاص را بررسی کنید، باید تمام رویدادها را دریافت کرده و فیلتر را در سمت جاوا اسکریپت اعمال کنید؛ این کار از طریق کلاینت اتریوم امکانپذیر نیست.
در سالیدیتی، به هر پارامتری که در تعریف رویداد با کلیدواژه indexed
مشخص شده باشد، یک تاپیک (topic) گفته میشود. کلاینت ها میتوانند بر اساس این تاپیک ها، رویدادها را بهصورت بهینه فیلتر و جستجو کنند.
بهترین شیوه ها در استفاده از رویدادها در سالیدیتی
بر اساس عرف رایج میان توسعه دهندگان، بهترین زمان برای ثبت رویداد (emit) در سالیدیتی زمانی است که تغییری مهم در وضعیت قرارداد اتفاق میافتد. برخی از مثالهای رایج برای این موقعیت ها عبارتاند از:
-
تغییر مالکیت قرارداد
-
انتقال اتر
-
انجام معامله یا تبادل
البته، هر تغییر وضعیت (state change) نیازی به ثبت رویداد ندارد. توسعه دهنده باید از خود بپرسد:
“آیا کسی ممکن است بخواهد این تراکنش را سریعتر کشف یا بازیابی کند؟”
اگر پاسخ مثبت است، ثبت رویداد توجیهپذیر خواهد بود.
پارامترهای مناسب را index کنید
در انتخاب پارامترهایی که باید با کلیدواژه indexed
مشخص شوند، باید با دقت و تشخیص درست عمل کرد. به یاد داشته باشید که پارامترهای غیر indexed بهصورت مستقیم قابل جستجو نیستند.
برای درک بهتر این موضوع، توصیه میشود طراحی رویدادها را در پروژه های معتبر بررسی کنید، از جمله:
بهعنوان یک قاعده کلی:
مقادیر رمزارز (مانند مبلغ انتقال) معمولاً نباید indexed شوند، اما آدرسها باید indexed باشند.
البته این قاعده نباید بهصورت کورکورانه اجرا شود؛ همیشه شرایط خاص پروژه را در نظر بگیرید.
از ثبت رویدادهای تکراری خودداری کنید
برای مثال، اگر یک کتابخانه داخلی هنگام ضرب (mint) توکن بهطور خودکار رویداد مربوطه را منتشر میکند، نیازی نیست که شما دوباره همان رویداد را در قرارداد خود ثبت کنید. این کار تنها باعث افزوده شدن دادههای غیرضروری میشود.
رویدادها را نمیتوان در توابع view یا pure استفاده کرد
رویدادها وضعیت بلاک چین را با ذخیره لاگ تغییر میدهند، به همین دلیل جزو عملیات تغییر وضعیت محسوب میشوند. به همین علت، در توابع view
یا pure
که نباید تغییری در بلاک چین ایجاد کنند، امکان استفاده از رویدادها وجود ندارد.
برخلاف زبان هایی که از print
یا console.log
برای دیباگ کردن استفاده میکنند، رویدادها برای این کار مناسب نیستند. چون رویدادها فقط زمانی ثبت میشوند که تراکنش با موفقیت انجام شده باشد. اگر تراکنش بازگردانده شود (revert)، رویداد مربوطه هم منتشر نمیشود. بنابراین، برای دیباگ باید از ابزارهای شبیه سازی یا ثبت در حافظه محلی استفاده کنید.
یک رویداد در سالیدیتی چند آرگومان میتواند داشته باشد؟
از نظر فنی، برای پارامترهای غیر indexed محدودیتی بر تعداد آرگومان ها وجود ندارد، اما اگر تعداد آنها بیش از حد زیاد شود، با محدودیت پشته (stack limit) در ماشین مجازی اتریوم مواجه خواهید شد. بهعنوان نمونه، کد زیر با اینکه معنای خاصی ندارد، از نظر نگارشی در سالیدیتی معتبر است:
1 2 3 |
contract ExampleContract { event Numbers(uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256); } |
همچنین، هیچ محدودیتی ذاتی برای طول رشته ها (string
) یا آرایه ها (array
) در داده های ثبتشده در لاگ وجود ندارد. این نوع داده ها را میتوان بدون محدودیت مشخص در رویدادها ذخیره کرد (البته باید به هزینه گس توجه داشت).
اما یک محدودیت مهم در مورد پارامترهای indexed (تاپیکها) وجود دارد:
در یک رویداد معمولی، حداکثر میتوان سه پارامتر را بهصورت indexed مشخص کرد. اگر بخواهید چهار پارامتر را index کنید، باید از رویدادهای anonymous استفاده کنید (که در بخشهای بعدی به تفاوت آنها میپردازیم).
در نهایت، تعریف رویدادی که هیچ آرگومانی ندارد نیز کاملاً معتبر است و از نظر سالیدیتی مشکلی ندارد.
نامگذاری متغیرها در رویدادها: اختیاری اما توصیهشده
در سالیدیتی، مشخص کردن نام برای پارامترهای رویداد الزامی نیست، اما به شدت توصیه میشود. بهعنوان مثال، دو رویداد زیر از نظر عملکرد کاملاً یکسان هستند:
1 2 |
event NewOwner(address newOwner); event NewOwner(address); |
1 |
event Trade(address,address,address,uint256,uint256); |
در این مثال، ممکن است بتوان حدس زد که آدرسها به ترتیب مربوط به فرستنده و دو توکن هستند، و مقادیر uint256
بیانگر حجم مبادله باشند. اما چون نامی برای پارامترها مشخص نشده، تفسیر دقیق آن دشوار خواهد بود و درک معنای رویداد برای توسعهدهندگان دیگر پیچیده میشود.
بهصورت قراردادی، معمول است که نام رویدادها با حرف بزرگ (حرف اول Capital) نوشته شود، اما کامپایلر سالیدیتی چنین الزامی را ایجاد نمیکند و در صورت رعایت نکردن آن هم کد اجرا میشود.
ارثبری رویدادها از قراردادهای والد و اینترفیسها
در سالیدیتی، اگر یک رویداد در یک قرارداد والد (parent contract) تعریف شده باشد، قرارداد فرزند (child contract) نیز میتواند آن رویداد را ارسال (emit) کند.
نکته مهم این است که رویدادها از نظر سطح دسترسی همواره داخلی (internal) هستند و نمیتوان آنها را private
یا public
تعریف کرد.
در مثال زیر، رویداد NewNumber
در قرارداد والد تعریف شده و هم در همان قرارداد و هم در قرارداد فرزند قابل استفاده است:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
contract ParentContract { event NewNumber(uint256 number); function doSomething(uint256 number) public { emit NewNumber(number); } } contract ChildContract is ParentContract { function doSomethingElse(uint256 number) public { emit NewNumber(number); } } |
1 2 3 4 5 6 7 8 9 |
interface IExampleInterface { event Deposit(address indexed sender, uint256 amount); } contract ExampleContract is IExampleInterface { function deposit() external payable { emit Deposit(msg.sender, msg.value); } } |
ExampleContract
رویدادی را منتشر میکند که قبلاً در اینترفیس IExampleInterface
تعریف شده است، بدون نیاز به تعریف مجدد آن در بدنه قرارداد.
سلکتور رویداد (Event Selector)
ماشین مجازی اتریوم (EVM) برای شناسایی رویدادها، از هش keccak256
امضای رویداد (event signature) استفاده میکند.
از نسخه ۰.۸.۱۵ به بعد سالیدیتی، میتوان سلکتور یک رویداد را بهصورت مستقیم با استفاده از عضو .selector
نیز به دست آورد.
در مثال زیر، این قابلیت نشان داده شده است:
1 2 3 4 5 6 7 8 9 10 11 |
pragma solidity ^0.8.15; contract ExampleContract { event SomeEvent(uint256 blocknum, uint256 indexed timestamp); function selector() external pure returns (bool) { // true return SomeEvent.selector == keccak256("SomeEvent(uint256,uint256)"); } } |
سلکتور رویداد در واقع خودش یکی از تاپیکها (topics) به شمار میرود (در بخشهای بعدی بیشتر درباره این موضوع توضیح خواهیم داد).
نکته مهم این است که قرار دادن یا حذف کلیدواژه indexed
روی پارامترهای رویداد، هیچ تغییری در سلکتور ایجاد نمیکند؛ چرا که سلکتور فقط به امضای تابع (نام و نوع پارامترها) بستگی دارد و نه به نحوه فیلترپذیری آنها.
رویدادهای ناشناس (Anonymous Events)
در سالیدیتی، میتوان رویدادها را با استفاده از کلیدواژه anonymous
بهصورت ناشناس تعریف کرد. در این حالت، رویداد فاقد سلکتور خواهد بود.
این موضوع بدان معناست که کد سمت کلاینت نمیتواند مانند مثالهای قبلی، این رویدادها را بهصورت خاص و مستقیم فیلتر کند.
مثال زیر، نحوه تعریف یک رویداد ناشناس را نشان میدهد:
1 2 3 4 5 6 7 8 9 10 11 |
pragma solidity ^0.8.15; contract ExampleContract { event SomeEvent(uint256 blocknum, uint256 timestamp) anonymous; function selector() public pure returns (bool) { // ERROR: does not compile, anonymous events don't have selectors return SomeEvent.selector == keccak256("SomeEvent(uint256,uint256)"); } } |
در حالت عادی، امضای رویداد (event signature
) بهعنوان یکی از تاپیکها (topics) در نظر گرفته میشود. اما وقتی رویدادی را بهصورت ناشناس تعریف میکنیم، چون دیگر نیازی به تاپیک مربوط به امضا نداریم، میتوانیم چهار پارامتر را بهصورت indexed مشخص کنیم (درحالیکه در حالت معمولی فقط سه پارامتر را میتوان index کرد).
مثال زیر، تعریف معتبر یک رویداد ناشناس با چهار پارامتر indexed را نشان میدهد:
1 2 3 4 |
contract ExampleContract { // valid event SomeEvent(uint256 indexed, uint256 indexed, address indexed, address indexed) anonymous; } |
مباحث پیشرفته درباره رویدادها در سطح اسمبلی EVM
در این بخش، جزئیات رویدادها در سطح اسمبلی ماشین مجازی اتریوم (EVM) بررسی میشود. اگر در ابتدای مسیر یادگیری توسعه قراردادهای هوشمند هستید، میتوانید از این بخش عبور کنید.
جزئیات پیادهسازی: فیلتر بلوم (Bloom Filters)
اگر یک کلاینت اتریوم بخواهد تمام تراکنشهایی که در یک قرارداد هوشمند انجام شدهاند را بازیابی کند، باید همه بلاکها را یکییکی اسکن کند. این کار بار ورودی/خروجی بسیار سنگینی به همراه دارد. برای حل این مشکل، اتریوم از یک بهینهسازی مهم استفاده میکند: فیلتر بلوم.
در هر بلاک، رویدادها در یک ساختار دادهای به نام Bloom Filter ذخیره میشوند.
فیلتر بلوم مجموعهای احتمالاتی (probabilistic set) است که میتواند با سرعت بالا پاسخ دهد که آیا یک عضو خاص در آن مجموعه وجود دارد یا نه. بهجای اسکن کامل بلاک، کلاینت میتواند از فیلتر بلوم بپرسد که آیا رویدادی خاص در آن بلاک منتشر شده است یا خیر.
این روش بسیار سریعتر از بررسی خطبهخط دادههای بلاک است.
استفاده از فیلتر بلوم، امکان جستجوی سریع رویدادها در بلاک چین را برای کلاینت فراهم میکند.
فیلتر بلوم یک ساختار احتمالاتی است؛ به این معنا که گاهی بهاشتباه اعلام میکند یک عضو در مجموعه وجود دارد، در حالی که چنین نیست.
هرچه تعداد اعضای ذخیرهشده در فیلتر بیشتر شود، احتمال خطا نیز افزایش مییابد. برای جبران این خطاها، اندازه فیلتر باید بزرگتر شود که به معنی مصرف بیشتر حافظه خواهد بود.
به همین دلیل، اتریوم تراکنشها را در فیلتر بلوم ذخیره نمیکند و فقط رویدادها را در این ساختار نگهداری میکند. چون تعداد رویدادها بهمراتب کمتر از تراکنشهاست، این تصمیم باعث میشود اندازه بلاک چین در حد معقول باقی بماند.
زمانی که فیلتر بلوم اعلام میکند که یک رویداد ممکن است در بلاک وجود داشته باشد، کلاینت باید بلاک را بررسی کند تا از وقوع آن رویداد مطمئن شود. اما از آنجا که چنین بررسیهایی فقط در تعداد محدودی از بلاکها انجام میشوند، در مجموع این فرآیند باعث کاهش قابل توجه بار پردازشی برای کلاینت خواهد شد.
رویدادها در Yul (سطح اسمبلی سالیدیتی)
در زبان میانی Yul، تفاوت میان پارامترهای indexed (موضوعات یا topics) و پارامترهای غیر indexed (بدنه داده) بهروشنی نمایان میشود.
برای ارسال (emit) رویدادها در Yul، مجموعهای از توابع با نامهای log0
تا log4
در اختیار برنامهنویس قرار دارد. این توابع مستقیماً به opcodeهای معادل در ماشین مجازی اتریوم (EVM) تبدیل میشوند. جدول زیر، که با سادهسازی از مستندات Yul استخراج شده، این توابع را شرح میدهد:
تابع Yul | کاربرد |
---|---|
log0(p, s) |
ارسال لاگ بدون topic؛ داده از حافظه در بازه [p … p + s) گرفته میشود |
log1(p, s, t1) |
ارسال لاگ با یک topic (t1) و داده از بازه حافظه [p … p + s) |
log2(p, s, t1, t2) |
ارسال لاگ با دو topic و داده |
log3(p, s, t1, t2, t3) |
ارسال لاگ با سه topic و داده |
log4(p, s, t1, t2, t3, t4) |
ارسال لاگ با چهار topic و داده |
در Yul (و EVM)، هر لاگ میتواند حداکثر ۴ topic داشته باشد. اما در سالیدیتی، یک رویداد غیرناشناس (non-anonymous) تنها میتواند ۳ پارامتر indexed داشته باشد. دلیل این محدودیت آن است که اولین topic به هش امضای رویداد اختصاص مییابد.
در نتیجه، باقیمانده فضا برای پارامترهای قابل فیلتر فقط سه عدد است.
در رویدادهای ناشناس (anonymous)، چون امضای رویداد ذخیره نمیشود، میتوان تا ۴ پارامتر indexed را استفاده کرد.
پارامترهای غیرindexed بهصورت کدگذاری ABI (ABI-encoded) در یک ناحیه پیوسته از حافظه قرار میگیرند؛ این ناحیه در بازه [p … p + s)
تعریف شده و دادهها بهصورت یک رشته بایت متوالی در لاگ ذخیره میشوند.
همانطور که پیشتر اشاره شد، سالیدیتی بهطور نظری هیچ محدودیتی برای تعداد پارامترهای غیرindexed در رویدادها اعمال نمیکند. دلیل این موضوع در معماری سطح پایین EVM نهفته است:
در opcodeهای log
، هیچ محدودیتی برای اندازه ناحیه حافظهای که داده در آن ذخیره میشود وجود ندارد. تنها محدودیتهای موجود، مربوط به محدودیت کلی اندازه قرارداد و هزینه گس ناشی از گسترش حافظه هستند.
هزینه گس برای ارسال رویداد در سالیدیتی
ارسال (emit) رویدادها در سالیدیتی بهمراتب ارزانتر از نوشتن داده در متغیرهای ذخیرهسازی (storage) است. دلیل این تفاوت در این است که رویدادها برای استفاده خارج از زنجیره (off-chain) طراحی شدهاند و نیازی نیست که قراردادهای هوشمند به آنها دسترسی مستقیم داشته باشند؛ همین موضوع باعث کاهش سربار محاسباتی و در نتیجه هزینه گس کمتر میشود.
فرمول محاسبه گس برای ارسال یک رویداد (منبع):
1 |
375 + 375 * num_topics + 8 * data_size + mem_expansion_cost |
-
حداقل هزینه برای هر رویداد ۳۷۵ گس است.
-
برای هر پارامتر indexed (تاپیک)، ۳۷۵ گس اضافی محاسبه میشود.
-
در رویدادهای غیرناشناس، امضای رویداد نیز بهعنوان یک تاپیک ذخیره میشود، بنابراین یک هزینه اضافی برای سلکتور رویداد در نظر گرفته میشود.
-
سپس، برای دادههایی که در حافظه ذخیره و روی زنجیره منتشر میشوند، به ازای هر کلمه ۳۲ بایتی، ۸ گس پرداخت میشود.
-
چون داده قبل از ارسال در حافظه نگهداری میشود، باید هزینه گسترش حافظه را نیز در محاسبه گس در نظر گرفت.
تعداد پارامترهای indexed (تاپیکها) بیشترین تأثیر را در هزینه گس رویدادها دارد. بنابراین اگر نیاز واقعی به جستجو و فیلتر روی یک پارامتر وجود ندارد، بهتر است آن را indexed
نکنید تا هزینه گس بهینه بماند.
جمعبندی
رویدادها ابزار بسیار مؤثری برای دسترسی سریع کلاینتها به تراکنشهای مهم هستند. اگرچه رویدادها تأثیری بر منطق اجرایی قرارداد ندارند، اما به برنامهنویس این امکان را میدهند که تعیین کند کدام تراکنشها ارزش ثبت و پیگیری از خارج زنجیره را دارند. این ویژگی برای شفافیت بیشتر در قراردادهای هوشمند بسیار حیاتی است.
از نظر مصرف گس، رویدادها در مقایسه با عملیاتهایی مانند نوشتن در storage
بسیار مقرونبهصرفه هستند، البته در صورتی که از حافظه زیاد استفاده نشود و تعداد پارامترهای indexed نیز کنترلشده باشد.
راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.
- انتشار: ۵ خرداد ۱۴۰۴
دسته بندی موضوعات
- آموزش ارز دیجیتال
- آموزش برنامه نویسی
- آموزش متنی برنامه نویسی
- اطلاعیه و سایر مطالب
- پروژه برنامه نویسی
- دوره های تخصصی برنامه نویسی
- رپورتاژ
- فیلم های آموزشی
- ++C
- ADO.NET
- Adobe Flash
- Ajax
- AngularJS
- apache
- ARM
- Asp.Net
- ASP.NET MVC
- AVR
- Bootstrap
- CCNA
- CCNP
- CMD
- CSS
- Dreameaver
- EntityFramework
- HTML
- IOS
- jquery
- Linq
- Mysql
- Oracle
- PHP
- PHPMyAdmin
- Rational Rose
- silver light
- SQL Server
- Stimulsoft Reports
- Telerik
- UML
- VB.NET&VB6
- WPF
- Xml
- آموزش های پروژه محور
- اتوکد
- الگوریتم تقریبی
- امنیت
- اندروید
- اندروید استودیو
- بک ترک
- بیسیک فور اندروید
- پایتون
- جاوا
- جاوا اسکریپت
- جوملا
- دلفی
- دوره آموزش Go
- دوره های رایگان پیشنهادی
- زامارین
- سئو
- ساخت CMS
- سی شارپ
- شبکه و مجازی سازی
- طراحی الگوریتم
- طراحی بازی
- طراحی وب
- فتوشاپ
- فریم ورک codeigniter
- فلاتر
- کانستراکت
- کریستال ریپورت
- لاراول
- معماری کامپیوتر
- مهندسی اینترنت
- هوش مصنوعی
- یونیتی
- کتاب های آموزشی
- Android
- ASP.NET
- AVR
- LINQ
- php
- Workflow
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس