استاندارد توکن ERC-223
آخرین ویرایش: @Bijan_F(opens in a new tab), ۲۲ خرداد ۱۴۰۳
مقدمه
ERC-223 چیست؟
استاندارد ERC-223 همانند ERC-20، برای توکنهای تعویض پذیر است. تفاوت کلیدی آنها این است که ERC-223 علاوه بر API (رابط کاربری برنامه نویسی)، منطق انتقال توکنها از فرستنده به گیرنده را نیز تعریف مینماید. این استاندارد یک مدل ارتباطی را معرفی میکند که اجازه میدهد انتقال توکنها در طرف گیرنده انجام شود.
تفاوتها با ERC-20
ERC-223 به یک سری از محدودیتهای استاندارد ERC-20 پاسخ میدهد و شیوهی جدیدی از ارتباط بین قرارداد هوشمند منبع توکن و قرارداد هوشمندی که ممکن است دریافت کننده توکنها باشد را معرفی میکند. اینها برخی از مواردی هستند که با ERC-223 امکان پذیرند ولی با ERC-20 نه:
- انجام و پردازش انتقال توکن در سمت گیرنده: گیرندهها میتوانند تشخیص دهند که یک توکن ERC-223 برای آنها واریز میشود.
- رد کردن توکنهایی که به درستی ارسال نشدهاند: اگر کاربری توکنهای ERC-223 را به قرارداد اشتباهی واریز نماید، آن قرارداد میتواند تراکنش را رد کند و باعث جلوگیری از دست رفتن توکنها شود.
- ابرداده در تراکنش ها: توکنهای ERC-223 میتوانند شامل ابرداده باشند و اجازه دهند تا اطلاعات دلخواه به تراکنش توکنها الصاق شود.
پیش نیازها
ساختار
ERC-223 یک استاندارد مخصوص توکن است که یک API برای توکنها در داخل قراردادهای هوشمند پیادهسازی میکند. همچنین یک API هم برای قراردادهایی که قرار است توکنهای ERC-223 دریافت کنند، تعریف میکند. قراردادهایی که از API گیرندهی ERC-223 پشتیبانی نمیکنند، قادر نخواهند بود توکنهای ERC-223 دریافت کنند و این باعث جلوگیری از خطای کاربر میشود.
اگر یک قرارداد هوشمند توابع و رویدادهایی که قرار است مطرح شوند را پیادهسازی نماید، میتواند بهعنوان یک قرارداد توکن سازگار با ERC-223 شناخته شود. پس از استقرار، مسئولیت پیگیری توکنهای ایجاد شده در اتریوم را بر عهده خواهد داشت.
قرارداد ملزم نیست فقط توابع و عملکرد زیر را داشته باشد و یک توسعه دهنده میتواند هر قابلیت دیگری را از سایر استانداردهای توکن متفاوت به این قرارداد اضافه کند. برای مثال توابع approve
(تائید) و transferFrom
(انتقال از) جزئی از استاندارد ERC-223 نیستند، بلکه این توابع میتوانند در صورت نیاز پیادهسازی شوند.
از EIP-223(opens in a new tab):
روشها
توکن ERC-223 باید روشهای زیر را پیادهسازی کند:
1// تابع اسم2function name() public view returns (string)3// تابع سمبل4function symbol() public view returns (string)5// تابع اعشار6function decimals() public view returns (uint8)7// تابع عرضه کل8function totalSupply() public view returns (uint256)9// تابع موجودی (آدرس دلخواه)10function balanceOf(address _owner) public view returns (uint256 balance)11// تابع انتقال توکن12function transfer(address _to, uint256 _value) public returns (bool success)13// تابع انتقال توکن (همراه با متغیر اضافه برای انتقال داده)14function transfer(address _to, uint256 _value, bytes calldata _data) public returns (bool success)نمایش همهکپی
قراردادی که قصد دارد توکنهای ERC-223 دریافت کند، باید روش زیر را پیادهسازی کند:
1// تابع قلاب دریافت توکن برای پیادهسازی توسط قرارداد هوشمند2function tokenReceived(address _from, uint _value, bytes calldata _data)کپی
اگر توکنهای ERC-223 به قراردادی ارسال شوند که تابع tokenReceived(..)
را پیادهسازی نکرده باشد، تراکنش باید شکست بخورد و توکنها نباید از موجودی فرستنده منتقل شوند.
رویدادها
1// رویداد انتقال2event Transfer(address indexed _from, address indexed _to, uint256 _value, bytes calldata _data)کپی
مثالها
رابط برنامهنویسی (API) توکن ERC-223 مشابه ERC-20 میباشد، بنابراین از نظر توسعه فرانت-اند هیچ تفاوتی ایجاد نمیشود. تنها تفاوت این است که احتمال دارد توکن ERC-223 توابع approve
+ transferFrom
را نداشته باشد، چرا که آنها در این استاندارد اختیاری هستند.
مثالهای Solidity
مثالهای زیر نشان میدهد که چگونه یک قرارداد ساده و اولیه توکن ERC-223 عمل میکند:
1pragma solidity ^0.8.19;2abstract contract IERC223Recipient {3 function tokenReceived(address _from, uint _value, bytes memory _data) public virtual;4}5contract VeryBasicERC223Token {6 event Transfer(address indexed from, address indexed to, uint value, bytes data);7 string private _name;8 string private _symbol;9 uint8 private _decimals;10 uint256 private _totalSupply;11 mapping(address => uint256) private balances;12 function name() public view returns (string memory) { return _name; }13 function symbol() public view returns (string memory) {return _symbol; }14 function decimals() public view returns (uint8) { return _decimals; }15 function totalSupply() public view returns (uint256) { return _totalSupply; }16 function balanceOf(address _owner) public view returns (uint256) { return balances[_owner]; }17 function isContract(address account) internal view returns (bool) {18 uint256 size;19 assembly { size := extcodesize(account) }20 return size > 0;21 }22 function transfer(address _to, uint _value, bytes calldata _data) public returns (bool success){23 balances[msg.sender] = balances[msg.sender] - _value;24 balances[_to] = balances[_to] + _value;25 if(isContract(_to)) {26 IERC223Recipient(_to).tokenReceived(msg.sender, _value, _data);27 }28 emit Transfer(msg.sender, _to, _value, _data);29 return true;30 }31 function transfer(address _to, uint _value) public returns (bool success){32 bytes memory _empty = hex"00000000";33 balances[msg.sender] = balances[msg.sender] - _value;34 balances[_to] = balances[_to] + _value;35 if(isContract(_to)) {36 IERC223Recipient(_to).tokenReceived(msg.sender, _value, _empty);37 }38 emit Transfer(msg.sender, _to, _value, _empty);39 return true;40 }41}نمایش همهکپی
حال ما به یک قرارداد دیگر نیاز داریم تا واریز tokenA
را بپذیرد، با فرض اینکه توکن A یک توکن ERC-223 است. این قرارداد باید فقط توکن A را بپذیرد و هر توکن دیگری را رد کند. زمانی که قرارداد توکن A را دریافت میکند، باید یک رویداد Deposit()
را اطلاع رسانی کند و مقدار متغیر داخلی deposits
را افزایش دهد.
کد مذکور به این شکل است:
1contract RecipientContract is IERC223Recipient {2 event Deposit(address whoSentTheTokens);3 uint256 deposits = 0;4 address tokenA; // The only token that we want to accept.5 function tokenReceived(address _from, uint _value, bytes memory _data) public override6 {7 // It is important to understand that within this function8 // msg.sender is the address of a token that is being received,9 // msg.value is always 0 as the token contract does not own or send Ether in most cases,10 // _from is the sender of the token transfer,11 // _value is the amount of tokens that was deposited.12 require(msg.sender == tokenA);13 deposits += _value;14 emit Deposit(_from);15 }16}نمایش همهکپی
سوالات متداول
اگر مقداری از توکن B به قرارداد بفرستیم، چه اتفاقی خواهد افتاد؟
تراکنش شکست خواهد خورد و انتقال توکنها انجام نخواهد شد. توکنها به آدرس فرستنده بازگشت داده خواهند شد.
چگونه میتوانیم به این قرارداد واریز انجام دهیم؟
تابع transfer(address,uint256)
یا transfer(address,uint256,bytes)
از توکن ERC-223 را با مشخص کردن آدرس RecipientContract
، فراخوانی کنید.
اگر یک توکن ERC-20 را به این قرارداد ارسال کنیم، چه اتفاقی خواهد افتاد؟
اگر یک توکن ERC-20 به RecipientContract
ارسال کنیم، توکنها انتقال خواهند یافت اما این انتقال شناسایی نخواهد شد (هیچ رویداد Deposit()
اجرا نخواهد شد و مقدار واریزیها تغییر نخواهد کرد). از واریزهای ERC-20 ناخواسته نمیتوان جلوگیری کرد.
چه میشود اگر بخواهیم پس از تکمیل انتقال توکن، تابع دیگری را اجرا کنیم؟
برای انجام دادن این کار راههای مختلف وجود دارد. در این مثال ما روشی را دنبال خواهیم کرد که باعث میشود انتقال ERC-223 مشابه انتقال اتر شود:
1contract RecipientContract is IERC223Recipient {2 event Foo();3 event Bar(uint256 someNumber);4 address tokenA; // The only token that we want to accept.5 function tokenReceived(address _from, uint _value, bytes memory _data) public override6 {7 require(msg.sender == tokenA);8 address(this).call(_data); // Handle incoming transaction and perform a subsequent function call.9 }10 function foo() public11 {12 emit Foo();13 }14 function bar(uint256 _someNumber) public15 {16 emit Bar(_someNumber);17 }18}نمایش همهکپی
هنگامی که RecipientContract
یک توکن ERC-223 را دریافت میکند، درست همانطور که تراکنشهای ارسال اتر فراخوانی توابع را بعنوان data
در تراکنش کدنگاری میکنند، قرارداد نیز تابعی را که به عنوان متغیر _data
در تراکنش توکن کدنگاری شده است اجرا خواهد کرد. برای اطلاعات بیشتر دیتا فیلد(opens in a new tab) را مطالعه فرمائید.
در مثال بالا، توکن ERC-223 باید با استفاده از تابع transfer(address,uin256,bytes calldata _data)
به آدرس RecipientContract
انتقال یابد. اگر مقدار پارامتر دیتا 0xc2985578
باشد (معادل امضاء تابع foo()
)، بعد از دریافت توکن واریزی و اجراء رویداد Foo()، تابع foo() اجرا خواهد شد.
پارامترها (متغیرهای ورودی) نیز میتوانند در data
انتقال توکن کدنگاری شوند، برای مثال ما میتوانیم تابع bar() را با مقدار 12345 برای _someNumber
اجرا کنیم. در این حالت مقدار data
باید
0x0423a13200000000000000000000000000000000000000000000000000000000000004d2
باشد به شکلی که
0x0423a132
امضاء تابع bar(uint256)
است و
00000000000000000000000000000000000000000000000000000000000004d2
همان 12345 است در قالب uint256.
محدودیتها
در حالی که ERC-223 به چندین مشکل پیدا شده در استاندارد ERC-20 میپردازد، خودش محدودیتهای خاص خود را دارد:
- پذیرش و سازگاری: ERC-223 هنوز به صورت فراگیر پذیرفته و پیادهسازی نشده است که باعث محدود شدن سازگاری آن با ابزارها و پلتفرمهای موجود میشود.
- پیش سازگاری: ERC-223 با ERC-20 پیش سازگار نیست، یعنی قراردادها و ابزارهای موجود برای ERC-20، باید برای کار کردن با ERC-223 اصلاح شوند.
- هزینه گاز: بررسیها و عملکردهای اضافه در تراکنشهای انتقال ERC-223 میتوانند منجر به هزینههای گاز بیشتر در مقایسه با تراکنشهای ERC-20 شوند.