تحويلات رموز ERC-20 والموافقة عليها من عقد سوليديتي ذكي
في الدرس التعليمي السابق درسنا تشريح رمز ERC-20 في سوليديتي على بلوكتشين إيثريوم. في هذه المقالة، سنرى كيف يمكننا استخدام عقد ذكي للتفاعل مع رمز باستخدام لغة سوليديتي.
لهذا العقد الذكي، سننشئ منصة تداول لامركزية وهمية حقيقية حيث يمكن للمستخدم تداول الإيثر مقابل رمز ERC-20 الذي نشرناه حديثًا.
في هذا الدرس التعليمي، سنستخدم النص البرمجي الذي كتبناه في الدرس التعليمي السابق كأساس. ستقوم منصة التداول اللامركزية الخاصة بنا بإنشاء مثيل للعقد في الدالة الإنشائية الخاصة به وتنفيذ العمليات التالية:
- استبدال الرموز بالإيثر
- استبدال الإيثر بالرموز
سنبدأ النص البرمجي لمنصة التداول اللامركزية الخاصة بنا بإضافة قاعدة التعليمات البرمجية البسيطة الخاصة بـ ERC20:
1pragma solidity ^0.8.0;23interface IERC20 {45 function totalSupply() external view returns (uint256);6 function balanceOf(address account) external view returns (uint256);7 function allowance(address owner, address spender) external view returns (uint256);89 function transfer(address recipient, uint256 amount) external returns (bool);10 function approve(address spender, uint256 amount) external returns (bool);11 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);1213 event Transfer(address indexed from, address indexed to, uint256 value);14 event Approval(address indexed owner, address indexed spender, uint256 value);15}1617contract ERC20Basic is IERC20 {1819 string public constant name = "ERC20Basic";20 string public constant symbol = "ERC";21 uint8 public constant decimals = 18;2223 mapping(address => uint256) balances;2425 mapping(address => mapping (address => uint256)) allowed;2627 uint256 totalSupply_ = 10 ether;2829 constructor() {30 balances[msg.sender] = totalSupply_;31 }3233 function totalSupply() public override view returns (uint256) {34 return totalSupply_;35 }3637 function balanceOf(address tokenOwner) public override view returns (uint256) {38 return balances[tokenOwner];39 }4041 function transfer(address receiver, uint256 numTokens) public override returns (bool) {42 require(numTokens <= balances[msg.sender]);43 balances[msg.sender] = balances[msg.sender]-numTokens;44 balances[receiver] = balances[receiver]+numTokens;45 emit Transfer(msg.sender, receiver, numTokens);46 return true;47 }4849 function approve(address delegate, uint256 numTokens) public override returns (bool) {50 allowed[msg.sender][delegate] = numTokens;51 emit Approval(msg.sender, delegate, numTokens);52 return true;53 }5455 function allowance(address owner, address delegate) public override view returns (uint) {56 return allowed[owner][delegate];57 }5859 function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {60 require(numTokens <= balances[owner]);61 require(numTokens <= allowed[owner][msg.sender]);6263 balances[owner] = balances[owner]-numTokens;64 allowed[owner][msg.sender] = allowed[owner][msg.sender]-numTokens;65 balances[buyer] = balances[buyer]+numTokens;66 emit Transfer(owner, buyer, numTokens);67 return true;68 }69}70إظهار الكلسيقوم عقدنا الذكي الجديد الخاص بمنصة التداول اللامركزية بنشر ERC-20 والحصول على كل ما تم توفيره:
1contract DEX {23 IERC20 public token;45 event Bought(uint256 amount);6 event Sold(uint256 amount);78 constructor() {9 token = new ERC20Basic();10 }1112 function buy() payable public {13 // سيتم التنفيذ لاحقاً14 }1516 function sell(uint256 amount) public {17 // سيتم التنفيذ لاحقاً18 }1920}إظهار الكللذا، لدينا الآن منصة التداول اللامركزية الخاصة بنا وكل احتياطي الرموز متاح لديها. يحتوي العقد على وظيفتين:
الشراء: يمكن للمستخدم إرسال الإيثر والحصول على رموز في المقابلالبيع: يمكن للمستخدم أن يقرر إرسال الرموز لاستعادة الإيثر
وظيفة الشراء
لنقم بكتابة النص البرمجي لوظيفة الشراء. سنحتاج أولاً إلى التحقق من كمية الإيثر التي تحتويها الرسالة والتحقق من أن العقد يمتلك رموزًا كافية وأن الرسالة تحتوي على بعض الإيثر. إذا كان العقد يمتلك رموزًا كافية، فسيرسل عدد الرموز إلى المستخدم ويصدر حدث Bought.
لاحظ أنه إذا استدعينا وظيفة require في حالة حدوث خطأ، فسيتم إرجاع الإيثر المرسل مباشرة وإعادته إلى المستخدم.
لتبسيط الأمور، سنقوم بتبديل رمز واحد مقابل 1 واي.
1function buy() payable public {2 uint256 amountTobuy = msg.value;3 uint256 dexBalance = token.balanceOf(address(this));4 require(amountTobuy > 0, "تحتاج إلى إرسال بعض الإيثر");5 require(amountTobuy <= dexBalance, "لا توجد رموز كافية في الاحتياطي");6 token.transfer(msg.sender, amountTobuy);7 emit Bought(amountTobuy);8}في حالة نجاح عملية الشراء، يجب أن نرى حدثين في المعاملة: حدث Transfer للرمز وحدث Bought.
وظيفة البيع
الوظيفة المسؤولة عن البيع ستتطلب أولاً من المستخدم أن يكون قد وافق على المبلغ عن طريق استدعاء وظيفة approve مسبقاً. تتطلب الموافقة على التحويل أن يتم استدعاء رمز ERC20Basic الذي تم إنشاؤه بواسطة منصة التداول اللامركزية من قِبل المستخدم. يمكن تحقيق ذلك عن طريق استدعاء وظيفة token() الخاصة بعقد منصة التداول اللامركزية أولاً لاسترداد العنوان الذي نشرت فيه منصة التداول اللامركزية عقد ERC20Basic المسمى token. ثم نقوم بإنشاء مثيل لهذا العقد في جلستنا ونستدعي وظيفة approve الخاصة به. بعد ذلك، يمكننا استدعاء وظيفة sell الخاصة بمنصة التداول اللامركزية واستبدال رموزنا مرة أخرى بالإيثر. على سبيل المثال، هكذا يبدو هذا في جلسة Brownie تفاعلية:
1#### بايثون في وحدة تحكم Brownie التفاعلية...23# نشر منصة التداول اللامركزية (DEX)4dex = DEX.deploy({'from':account1})56# استدعاء وظيفة الشراء لمبادلة الإيثر بالرمز7# 1e18 هو 1 إيثر مُقوّم بالـ wei8dex.buy({'from': account2, 1e18})910# الحصول على عنوان النشر لرمز ERC2011# الذي تم نشره أثناء إنشاء عقد DEX12# dex.token() تُرجع عنوان النشر للرمز13token = ERC20Basic.at(dex.token())1415# استدعاء وظيفة الموافقة الخاصة بالرمز16# الموافقة على عنوان DEX كمنفق17# وعدد الرموز التي يُسمح له بإنفاقها18token.approve(dex.address, 3e18, {'from':account2})19إظهار الكلبعد ذلك، عند استدعاء وظيفة البيع، سنتحقق مما إذا كان التحويل من عنوان المتصل إلى عنوان العقد قد تم بنجاح، ثم نعيد إرسال الإيثر إلى عنوان المتصل.
1function sell(uint256 amount) public {2 require(amount > 0, "تحتاج إلى بيع بعض الرموز على الأقل");3 uint256 allowance = token.allowance(msg.sender, address(this));4 require(allowance >= amount, "تحقق من بدل الرمز");5 token.transferFrom(msg.sender, address(this), amount);6 payable(msg.sender).transfer(amount);7 emit Sold(amount);8}إذا سار كل شيء على ما يرام، يجب أن ترى حدثين (Transfer و Sold) في المعاملة وتحديث رصيد الرمز ورصيد الإيثر لديك.
من هذا الدرس التعليمي، رأينا كيفية التحقق من الرصيد والبدل لرمز ERC-20، وكذلك كيفية استدعاء Transfer وTransferFrom لعقد ERC20 ذكي باستخدام الواجهة.
بمجرد إجراء معاملة، لدينا درس تعليمي لجافا سكريبت للانتظار والحصول على تفاصيل حول المعاملات (opens in a new tab) التي أُجريت على عقدك ودرس تعليمي لفك تشفير الأحداث الناتجة عن تحويلات الرموز أو أي أحداث أخرى (opens in a new tab) طالما أنك تملك واجهة التطبيق الثنائية (ABI).
إليك النص البرمجي الكامل للدرس التعليمي:
1pragma solidity ^0.8.0;23interface IERC20 {45 function totalSupply() external view returns (uint256);6 function balanceOf(address account) external view returns (uint256);7 function allowance(address owner, address spender) external view returns (uint256);89 function transfer(address recipient, uint256 amount) external returns (bool);10 function approve(address spender, uint256 amount) external returns (bool);11 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);1213 event Transfer(address indexed from, address indexed to, uint256 value);14 event Approval(address indexed owner, address indexed spender, uint256 value);15}1617contract ERC20Basic is IERC20 {1819 string public constant name = "ERC20Basic";20 string public constant symbol = "ERC";21 uint8 public constant decimals = 18;2223 mapping(address => uint256) balances;2425 mapping(address => mapping (address => uint256)) allowed;2627 uint256 totalSupply_ = 10 ether;2829 constructor() {30 balances[msg.sender] = totalSupply_;31 }3233 function totalSupply() public override view returns (uint256) {34 return totalSupply_;35 }3637 function balanceOf(address tokenOwner) public override view returns (uint256) {38 return balances[tokenOwner];39 }4041 function transfer(address receiver, uint256 numTokens) public override returns (bool) {42 require(numTokens <= balances[msg.sender]);43 balances[msg.sender] = balances[msg.sender]-numTokens;44 balances[receiver] = balances[receiver]+numTokens;45 emit Transfer(msg.sender, receiver, numTokens);46 return true;47 }4849 function approve(address delegate, uint256 numTokens) public override returns (bool) {50 allowed[msg.sender][delegate] = numTokens;51 emit Approval(msg.sender, delegate, numTokens);52 return true;53 }5455 function allowance(address owner, address delegate) public override view returns (uint) {56 return allowed[owner][delegate];57 }5859 function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {60 require(numTokens <= balances[owner]);61 require(numTokens <= allowed[owner][msg.sender]);6263 balances[owner] = balances[owner]-numTokens;64 allowed[owner][msg.sender] = allowed[owner][msg.sender]-numTokens;65 balances[buyer] = balances[buyer]+numTokens;66 emit Transfer(owner, buyer, numTokens);67 return true;68 }69}7071contract DEX {7273 event Bought(uint256 amount);74 event Sold(uint256 amount);7576 IERC20 public token;7778 constructor() {79 token = new ERC20Basic();80 }8182 function buy() payable public {83 uint256 amountTobuy = msg.value;84 uint256 dexBalance = token.balanceOf(address(this));85 require(amountTobuy > 0, "تحتاج إلى إرسال بعض الإيثر");86 require(amountTobuy <= dexBalance, "لا توجد رموز كافية في الاحتياطي");87 token.transfer(msg.sender, amountTobuy);88 emit Bought(amountTobuy);89 }9091 function sell(uint256 amount) public {92 require(amount > 0, "تحتاج إلى بيع بعض الرموز على الأقل");93 uint256 allowance = token.allowance(msg.sender, address(this));94 require(allowance >= amount, "تحقق من بدل الرمز");95 token.transferFrom(msg.sender, address(this), amount);96 payable(msg.sender).transfer(amount);97 emit Sold(amount);98 }99100}إظهار الكلآخر تحديث للصفحة: 21 أغسطس 2025

