Mwongozo wa Mkataba wa ERC-20
Utangulizi
Moja ya matumizi ya kawaida ya Ethereum ni kwa kikundi kuunda tokeni inayoweza kuuzwa, kwa maana fulani sarafu yao wenyewe. Tokeni hizi kwa kawaida hufuata kiwango, ERC-20. Kiwango hiki hufanya iwezekane kuandika zana, kama vile mabwawa ya ukwasi na mikoba, zinazofanya kazi na tokeni zote za ERC-20. Katika makala haya tutachambua utekelezaji wa ERC20 wa Solidity wa OpenZeppelin (opens in a new tab), pamoja na ufafanuzi wa kiolesura (opens in a new tab).
Huu ni msimbo wa chanzo uliofafanuliwa. Ikiwa unataka kutekeleza ERC-20, soma mafunzo haya (opens in a new tab).
Kiolesura
Kusudi la kiwango kama ERC-20 ni kuruhusu utekelezaji wa tokeni nyingi zinazoingiliana katika programu, kama vile mikoba na mabadilishano yaliyogatuliwa. Ili kufanikisha hilo, tunaunda kiolesura (opens in a new tab). Msimbo wowote unaohitaji kutumia mkataba wa tokeni unaweza kutumia ufafanuzi sawa katika kiolesura na kuendana na mikataba yote ya tokeni inayoitumia, iwe ni mkoba kama vile MetaMask, programu tumizi iliyogatuliwa (dapp) kama vile etherscan.io, au mkataba tofauti kama vile bwawa la ukwasi.
Ikiwa wewe ni mtayarishaji programu mzoefu, labda unakumbuka kuona miundo sawa katika Java (opens in a new tab) au hata katika faili za kichwa za C (opens in a new tab).
Huu ni ufafanuzi wa Kiolesura cha ERC-20 (opens in a new tab) kutoka OpenZeppelin. Ni tafsiri ya kiwango kinachosomeka na binadamu (opens in a new tab) kuwa msimbo wa Solidity. Bila shaka, kiolesura chenyewe hakifafanui jinsi ya kufanya chochote. Hilo linaelezwa katika msimbo wa chanzo wa mkataba hapa chini.
// SPDX-License-Identifier: MIT
Faili za Solidity zinapaswa kujumuisha kitambulisho cha leseni. Unaweza kuona orodha ya leseni hapa (opens in a new tab). Ikiwa unahitaji leseni tofauti, ieleze tu kwenye maoni.
pragma solidity >=0.6.0 <0.8.0;
Lugha ya Solidity bado inabadilika haraka, na matoleo mapya yanaweza yasiendane na msimbo wa zamani (tazama hapa (opens in a new tab)). Kwa hivyo, ni wazo zuri kubainisha sio tu toleo la chini la lugha, bali pia toleo la juu zaidi, la hivi punde ambalo ulijaribu msimbo nalo.
/**
* @dev Kiolesura cha kiwango cha ERC-20 kama kilivyofafanuliwa katika EIP.
*/
@dev katika maoni ni sehemu ya umbizo la NatSpec (opens in a new tab), linalotumika kutoa
nyaraka kutoka kwa msimbo wa chanzo.
interface IERC20 {
Kwa kawaida, majina ya kiolesura huanza na I.
/**
* @dev Inarejesha kiasi cha tokeni zilizopo.
*/
function totalSupply() external view returns (uint256);
Kazi hii ni external, ikimaanisha inaweza tu kuitwa kutoka nje ya mkataba (opens in a new tab).
Inarudisha jumla ya usambazaji wa tokeni katika mkataba. Thamani hii inarudishwa kwa kutumia aina ya kawaida zaidi katika Ethereum, biti 256 zisizo na saini (biti 256 ni
saizi ya neno asili ya EVM). Kazi hii pia ni view, ambayo inamaanisha kuwa haibadilishi hali, kwa hivyo inaweza kutekelezwa kwenye nodi moja badala ya kuwa na
kila nodi katika mnyororo wa vitalu kuiendesha. Aina hii ya kazi haizalishi muamala na haigharimu gesi.
Kumbuka: Kinadharia inaweza kuonekana kuwa muundaji wa mkataba anaweza kudanganya kwa kurudisha jumla ya usambazaji mdogo kuliko thamani halisi, na kufanya kila tokeni ionekane ya thamani zaidi kuliko ilivyo kweli. Hata hivyo, hofu hiyo inapuuza asili ya kweli ya mnyororo wa vitalu. Kila kitu kinachotokea kwenye mnyororo wa vitalu kinaweza kuthibitishwa na kila nodi. Ili kufanikisha hili, msimbo wa lugha ya mashine ya kila mkataba na hifadhi inapatikana kwenye kila nodi. Ingawa hauhitajiki kuchapisha msimbo wa Solidity kwa mkataba wako, hakuna mtu atakayekuchukulia kwa uzito isipokuwa uchapishe msimbo wa chanzo na toleo la Solidity ambalo lilikusanywa nalo, ili iweze kuthibitishwa dhidi ya msimbo wa lugha ya mashine uliotoa. Kwa mfano, tazama mkataba huu (opens in a new tab).
/**
* @dev Inarejesha kiasi cha tokeni zinazomilikiwa na `akaunti`.
*/
function balanceOf(address account) external view returns (uint256);
Kama jina linavyosema, balanceOf inarudisha salio la akaunti. Akaunti za Ethereum zinatambuliwa katika Solidity kwa kutumia aina ya address, ambayo inashikilia biti 160.
Pia ni external na view.
/**
* @dev Inahamisha tokeni `amount` kutoka kwenye akaunti ya mpigaji kwenda kwa `recipient`.
*
* Inarejesha thamani ya boolean inayoonyesha ikiwa operesheni imefaulu.
*
* Inatoa tukio la {hamisho}.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
Kazi ya transfer inahamisha tokeni kutoka kwa mpigaji kwenda kwa anwani tofauti. Hii inahusisha mabadiliko ya hali, kwa hivyo sio view.
Mtumiaji anapoita kazi hii inaunda muamala na kugharimu gesi. Pia inatoa tukio, Transfer, ili kuarifu kila mtu kwenye
mnyororo wa vitalu kuhusu tukio hilo.
Kazi ina aina mbili za matokeo kwa aina mbili tofauti za wapigaji:
- Watumiaji wanaopiga kazi moja kwa moja kutoka kwa kiolesura cha mtumiaji. Kwa kawaida mtumiaji huwasilisha muamala
na hasubiri jibu, ambalo linaweza kuchukua muda usiojulikana. Mtumiaji anaweza kuona kilichotokea
kwa kutafuta stakabadhi ya muamala (ambayo inatambuliwa na heshi ya muamala) au kwa kutafuta
tukio la
Transfer. - Mikataba mingine, ambayo huita kazi kama sehemu ya muamala wa jumla. Mikataba hiyo hupata matokeo mara moja, kwa sababu inaendeshwa katika muamala huo huo, kwa hivyo inaweza kutumia thamani ya kurudi ya kazi.
Aina sawa ya matokeo inaundwa na kazi zingine zinazobadilisha hali ya mkataba.
Vibali huruhusu akaunti kutumia baadhi ya tokeni ambazo ni za mmiliki tofauti. Hii ni muhimu, kwa mfano, kwa mikataba inayofanya kazi kama wauzaji. Mikataba haiwezi kufuatilia matukio, kwa hivyo ikiwa mnunuzi angehamisha tokeni kwa mkataba wa muuzaji moja kwa moja mkataba huo usingejua umelipwa. Badala yake, mnunuzi anaruhusu mkataba wa muuzaji kutumia kiasi fulani, na muuzaji anahamisha kiasi hicho. Hii inafanywa kupitia kazi ambayo mkataba wa muuzaji unaita, ili mkataba wa muuzaji uweze kujua ikiwa ulifanikiwa.
/**
* @dev Inarejesha idadi iliyobaki ya tokeni ambazo `spender` ataruhusiwa
* kutumia kwa niaba ya `owner` kupitia {transferFrom}. Hii ni
* sifuri kwa chaguo-msingi.
*
* Thamani hii inabadilika wakati {idhinisha} au {transferFrom} inapoitwa.
*/
function allowance(address owner, address spender) external view returns (uint256);
Kazi ya allowance inaruhusu mtu yeyote kuuliza ili kuona ni kibali gani ambacho
anwani moja (owner) inaruhusu anwani nyingine (spender) kutumia.
/**
* @dev Inaweka `amount` kama kibali cha `spender` juu ya tokeni za mpigaji.
*
* Inarejesha thamani ya boolean inayoonyesha ikiwa operesheni imefaulu.
*
* MUHIMU: Jihadharini kwamba kubadilisha kibali kwa njia hii kunaleta hatari
* kwamba mtu anaweza kutumia kibali cha zamani na kipya kwa mpangilio mbaya
* wa muamala. Suluhisho moja linalowezekana la kupunguza hali hii ya
* ushindani ni kwanza kupunguza kibali cha mtumiaji hadi 0 na kuweka
* thamani inayotakikana baadaye:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Inatoa tukio la {Approval}.
*/
function approve(address spender, uint256 amount) external returns (bool);
Kazi ya approve inaunda kibali. Hakikisha unasoma ujumbe kuhusu
jinsi inavyoweza kutumiwa vibaya. Katika Ethereum unadhibiti mpangilio wa miamala yako mwenyewe,
lakini huwezi kudhibiti mpangilio ambao miamala ya watu wengine
itatekelezwa, isipokuwa usipowasilisha muamala wako mwenyewe hadi uone
muamala wa upande mwingine umetokea.
/**
* @dev Inahamisha tokeni `amount` kutoka kwa `sender` kwenda kwa `recipient` kwa kutumia
* utaratibu wa kibali. `amount` kisha inakatwa kutoka kwenye kibali
* cha mpigaji.
*
* Inarejesha thamani ya boolean inayoonyesha ikiwa operesheni imefaulu.
*
* Inatoa tukio la {hamisho}.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
Hatimaye, transferFrom inatumiwa na mtumiaji kutumia kibali hicho.
/**
* @dev Inatolewa wakati tokeni `value` zinahamishwa kutoka akaunti moja (`from`) kwenda
* nyingine (`to`).
*
* Kumbuka kwamba `value` inaweza kuwa sifuri.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Inatolewa wakati kibali cha `spender` kwa `owner` kinawekwa na
* wito kwa {idhinisha}. `value` ni kibali kipya.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
Matukio haya hutolewa wakati hali ya mkataba wa ERC-20 inabadilika.
Mkataba Halisi
Huu ndio mkataba halisi unaotekeleza kiwango cha ERC-20, uliochukuliwa kutoka hapa (opens in a new tab). Haukusudiwi kutumiwa kama ulivyo, lakini unaweza kurithi (opens in a new tab) kutoka kwake ili kuupanua kuwa kitu kinachoweza kutumika.
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;
Taarifa za Uingizaji
Mbali na ufafanuzi wa kiolesura hapo juu, ufafanuzi wa mkataba unaingiza faili zingine mbili:
import "../../GSN/Context.sol";
import "./IERC20.sol";
import "../../math/SafeMath.sol";
GSN/Context.solni ufafanuzi unaohitajika kutumia OpenGSN (opens in a new tab), mfumo unaoruhusu watumiaji wasio na Etha kutumia mnyororo wa vitalu. Kumbuka kuwa hili ni toleo la zamani, ikiwa unataka kuunganisha na OpenGSN tumia mafunzo haya (opens in a new tab).- Maktaba ya SafeMath (opens in a new tab), ambayo inazuia mizidio/upungufu wa hesabu kwa matoleo ya Solidity <0.8.0. Katika Solidity ≥0.8.0, shughuli za hesabu hutengua kiotomatiki kwenye mzidio/upungufu, na kufanya SafeMath isiwe ya lazima. Mkataba huu unatumia SafeMath kwa utangamano wa nyuma na matoleo ya zamani ya kikusanyaji.
Maoni haya yanaelezea madhumuni ya mkataba.
/**
* @dev Utekelezaji wa kiolesura cha {IERC20}.
*
* Utekelezaji huu haujali jinsi tokeni zinavyoundwa. Hii inamaanisha
* kwamba utaratibu wa usambazaji unapaswa kuongezwa katika mkataba unaotokana kwa kutumia {_mint}.
* Kwa utaratibu wa jumla tazama {ERC20PresetMinterPauser}.
*
* DOKEZO: Kwa maelezo ya kina tazama mwongozo wetu
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* Tumefuata miongozo ya jumla ya OpenZeppelin: vitendaji hubatilisha badala
* ya kurejesha `false` vinaposhindwa. Tabia hii hata hivyo ni ya kawaida
* na haipingani na matarajio ya programu za ERC-20.
*
* Zaidi ya hayo, tukio la {Approval} linatolewa kwenye wito wa {transferFrom}.
* Hii inaruhusu programu kuunda upya kibali kwa akaunti zote tu
* kwa kusikiliza matukio hayo. Utekelezaji mwingine wa EIP huenda usitoe
* matukio haya, kwani haihitajiki na vipimo.
*
* Hatimaye, vitendaji visivyo vya kiwango vya {decreaseAllowance} na {increaseAllowance}
* vimeongezwa ili kupunguza matatizo yanayojulikana sana kuhusu kuweka
* vibali. Tazama {IERC20-approve}.
*/
Ufafanuzi wa Mkataba
contract ERC20 is Context, IERC20 {
Mstari huu unabainisha urithi, katika kesi hii kutoka kwa IERC20 kutoka hapo juu na Context, kwa OpenGSN.
using SafeMath for uint256;
Mstari huu unaambatanisha maktaba ya SafeMath kwenye aina ya uint256. Unaweza kupata maktaba hii
hapa (opens in a new tab).
Ufafanuzi wa Vigezo
Ufafanuzi huu unabainisha vigezo vya hali ya mkataba. Vigezo hivi vimetangazwa kama private, lakini
hiyo inamaanisha tu kwamba mikataba mingine kwenye mnyororo wa vitalu haiwezi kuvisoma. Hakuna
siri kwenye mnyororo wa vitalu, programu kwenye kila nodi ina hali ya kila mkataba
katika kila kitalu. Kwa kawaida, vigezo vya hali hupewa jina _<something>.
Vigezo viwili vya kwanza ni ramani (opens in a new tab), ikimaanisha vinafanya kazi takriban sawa na safu shirikishi (opens in a new tab), isipokuwa kwamba funguo ni thamani za nambari. Hifadhi inatengwa tu kwa maingizo ambayo yana thamani tofauti na chaguo-msingi (sifuri).
mapping (address => uint256) private _balances;
Ramani ya kwanza, _balances, ni anwani na salio zao husika za tokeni hii. Ili kufikia
salio, tumia sintaksia hii: _balances[<address>].
mapping (address => mapping (address => uint256)) private _allowances;
Kigezo hiki, _allowances, kinahifadhi vibali vilivyoelezwa hapo awali. Faharisi ya kwanza ni mmiliki
wa tokeni, na ya pili ni mkataba wenye kibali. Ili kufikia kiasi ambacho anwani A inaweza
kutumia kutoka kwa akaunti ya anwani B, tumia _allowances[B][A].
uint256 private _totalSupply;
Kama jina linavyopendekeza, kigezo hiki hufuatilia jumla ya usambazaji wa tokeni.
string private _name;
string private _symbol;
uint8 private _decimals;
Vigezo hivi vitatu vinatumika kuboresha usomaji. Viwili vya kwanza vinajieleza, lakini _decimals
haijielezi.
Kwa upande mmoja, Ethereum haina vigezo vya nambari za desimali au sehemu. Kwa upande mwingine, binadamu wanapenda kuweza kugawanya tokeni. Sababu moja iliyofanya watu wakubaliane kutumia dhahabu kwa sarafu ni kwamba ilikuwa vigumu kutoa chenji wakati mtu alipotaka kununua ng'ombe kwa thamani ya bata.
Suluhisho ni kufuatilia nambari kamili, lakini kuhesabu badala ya tokeni halisi tokeni ya sehemu ambayo inakaribia kukosa thamani. Katika kesi ya Etha, tokeni ya sehemu inaitwa Wei, na Wei 10^18 ni sawa na ETH moja. Wakati wa kuandika, Wei 10,000,000,000,000 ni takriban senti moja ya Marekani au Euro.
Programu zinahitaji kujua jinsi ya kuonyesha salio la tokeni. Ikiwa mtumiaji ana Wei 3,141,000,000,000,000,000, je, hiyo ni
ETH 3.14? ETH 31.41? ETH 3,141? Katika kesi ya Etha inafafanuliwa Wei 10^18 kwa ETH, lakini kwa
tokeni yako unaweza kuchagua thamani tofauti. Ikiwa kugawanya tokeni hakuna maana, unaweza kutumia
thamani ya _decimals ya sifuri. Ikiwa unataka kutumia kiwango sawa na ETH, tumia thamani 18.
Konstrukta
/**
* @dev Inaweka thamani za {name} na {symbol}, inaanzisha {decimals} na
* thamani ya chaguo-msingi ya 18.
*
* Ili kuchagua thamani tofauti kwa {decimals}, tumia {_setupDecimals}.
*
* Thamani zote tatu hizi hazibadiliki: zinaweza tu kuwekwa mara moja wakati
* wa ujenzi.
*/
constructor (string memory name_, string memory symbol_) public {
// Katika Solidity ≥0.7.0, 'public' inaeleweka na inaweza kuachwa.
_name = name_;
_symbol = symbol_;
_decimals = 18;
}
Konstrukta inaitwa wakati mkataba unaundwa kwa mara ya kwanza. Kwa kawaida, vigezo vya kazi hupewa jina <something>_.
Kazi za Kiolesura cha Mtumiaji
/**
* @dev Inarejesha jina la tokeni.
*/
function name() public view returns (string memory) {
return _name;
}
/**
* @dev Inarejesha alama ya tokeni, kwa kawaida toleo fupi la
* jina.
*/
function symbol() public view returns (string memory) {
return _symbol;
}
/**
* @dev Inarejesha idadi ya desimali zinazotumika kupata uwakilishi wake kwa mtumiaji.
* Kwa mfano, ikiwa `decimals` ni sawa na `2`, salio la tokeni `505` linapaswa
* kuonyeshwa kwa mtumiaji kama `5,05` (`505 / 10 ** 2`).
*
* Kwa kawaida tokeni huchagua thamani ya 18, kuiga uhusiano kati ya
* Etha na Wei. Hii ndiyo thamani ambayo {ERC-20} inatumia, isipokuwa {_setupDecimals} iitwe.
*
* KUMBUKA: Taarifa hii inatumika tu kwa madhumuni ya _kuonyesha_: kwa
* vyovyote vile haiathiri hesabu yoyote ya mkataba, ikiwa ni pamoja na
* {IERC20-balanceOf} na {IERC20-transfer}.
*/
function decimals() public view returns (uint8) {
return _decimals;
}
Kazi hizi, name, symbol, na decimals husaidia violesura vya mtumiaji kujua kuhusu mkataba wako ili viweze kuuonyesha ipasavyo.
Aina ya kurudi ni string memory, ikimaanisha kurudisha mfuatano ambao umehifadhiwa kwenye kumbukumbu. Vigezo, kama vile
mifuatano, vinaweza kuhifadhiwa katika maeneo matatu:
| Muda wa Kuishi | Ufikiaji wa Mkataba | Gharama ya Gesi | |
|---|---|---|---|
| Memory | Mwito wa kazi | Soma/Andika | Makumi au mamia (juu zaidi kwa maeneo ya juu) |
| Calldata | Mwito wa kazi | Soma Tu | Haiwezi kutumika kama aina ya kurudi, tu kama aina ya kigezo cha kazi |
| Storage | Hadi ibadilishwe | Soma/Andika | Juu (800 kwa kusoma, 20k kwa kuandika) |
Katika kesi hii, memory ndio chaguo bora.
Soma Taarifa za Tokeni
Hizi ni kazi zinazotoa taarifa kuhusu tokeni, iwe ni jumla ya usambazaji au salio la akaunti.
/**
* @dev Tazama {IERC20-totalSupply}.
*/
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}
Kazi ya totalSupply inarudisha jumla ya usambazaji wa tokeni.
/**
* @dev Tazama {IERC20-balanceOf}.
*/
function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}
Soma salio la akaunti. Kumbuka kwamba mtu yeyote anaruhusiwa kupata salio la akaunti ya mtu mwingine yeyote. Hakuna maana ya kujaribu kuficha taarifa hii, kwa sababu inapatikana kwenye kila nodi hata hivyo. Hakuna siri kwenye mnyororo wa vitalu.
Hamisha Tokeni
/**
* @dev Tazama {IERC20-transfer}.
*
* Mahitaji:
*
* - `recipient` haiwezi kuwa anwani sifuri.
* - mpigaji lazima awe na salio la angalau `amount`.
*/
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
Kazi ya transfer inaitwa kuhamisha tokeni kutoka kwa akaunti ya mtumaji kwenda kwa nyingine. Kumbuka
kwamba ingawa inarudisha thamani ya boolean, thamani hiyo daima ni kweli. Ikiwa hamisho
litashindwa mkataba unatengua mwito.
_transfer(_msgSender(), recipient, amount);
return true;
}
Kazi ya _transfer inafanya kazi halisi. Ni kazi ya kibinafsi ambayo inaweza tu kuitwa na
kazi zingine za mkataba. Kwa kawaida kazi za kibinafsi hupewa jina _<something>, sawa na vigezo
vya hali.
Kawaida katika Solidity tunatumia msg.sender kwa mtumaji wa ujumbe. Hata hivyo, hiyo inavunja
OpenGSN (opens in a new tab). Ikiwa tunataka kuruhusu miamala isiyo na Etha na tokeni yetu, tunahitaji
kutumia _msgSender(). Inarudisha msg.sender kwa miamala ya kawaida, lakini kwa ile isiyo na Etha
inarudisha mtia saini wa asili na sio mkataba uliopitisha ujumbe.
Kazi za Kibali
Hizi ni kazi zinazotekeleza utendaji wa kibali: allowance, approve, transferFrom,
na _approve. Zaidi ya hayo, utekelezaji wa OpenZeppelin unaenda zaidi ya kiwango cha msingi ili kujumuisha baadhi ya vipengele vinavyoboresha
usalama: increaseAllowance, na decreaseAllowance.
Kazi ya kibali
/**
* @dev Tazama {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
Kazi ya allowance inaruhusu kila mtu kuangalia kibali chochote.
Kazi ya kuidhinisha
/**
* @dev Tazama {IERC20-approve}.
*
* Mahitaji:
*
* - `spender` haiwezi kuwa anwani sifuri.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
Kazi hii inaitwa kuunda kibali. Inafanana na kazi ya transfer hapo juu:
- Kazi inaita tu kazi ya ndani (katika kesi hii,
_approve) ambayo inafanya kazi halisi. - Kazi inarudisha
true(ikiwa imefanikiwa) au inatengua (ikiwa sivyo).
_approve(_msgSender(), spender, amount);
return true;
}
Tunatumia kazi za ndani ili kupunguza idadi ya maeneo ambapo mabadiliko ya hali hutokea. Kazi yoyote inayobadilisha hali ni hatari inayowezekana ya usalama ambayo inahitaji kukaguliwa kwa usalama. Kwa njia hii tuna nafasi ndogo za kukosea.
Kazi ya transferFrom
Hii ni kazi ambayo mtumiaji anaita ili kutumia kibali. Hii inahitaji shughuli mbili: kuhamisha kiasi kinachotumiwa na kupunguza kibali kwa kiasi hicho.
/**
* @dev Tazama {IERC20-transferFrom}.
*
* Inatoa tukio la {Approval} kuonyesha kibali kilichosasishwa. Hii
* haihitajiki na EIP. Tazama dokezo mwanzoni mwa {ERC-20}.
*
* Mahitaji:
*
* - `sender` na `recipient` haziwezi kuwa anwani sifuri.
* - `sender` lazima awe na salio la angalau `amount`.
* - mpigaji lazima awe na kibali cha tokeni za ``sender`` cha angalau
* `amount`.
*/
function transferFrom(address sender, address recipient, uint256 amount) public virtual
override returns (bool) {
_transfer(sender, recipient, amount);
Mwito wa kazi wa a.sub(b, "message") unafanya mambo mawili. Kwanza, inakokotoa a-b, ambayo ni kibali kipya.
Pili, inaangalia kuwa matokeo haya sio hasi. Ikiwa ni hasi mwito unatengua na ujumbe uliotolewa. Kumbuka kwamba wakati mwito unatengua usindikaji wowote uliofanywa hapo awali wakati wa mwito huo unapuuzwa kwa hivyo hatuhitaji
kutengua _transfer.
_approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount,
"ERC20: transfer amount exceeds allowance"));
return true;
}
Nyongeza za usalama za OpenZeppelin
Ni hatari kuweka kibali kisicho sifuri kwa thamani nyingine isiyo sifuri, kwa sababu unadhibiti tu mpangilio wa miamala yako mwenyewe, sio ya mtu mwingine yeyote. Fikiria una watumiaji wawili, Alice ambaye ni mjinga na Bill ambaye si mwaminifu. Alice anataka huduma fulani kutoka kwa Bill, ambayo anafikiri inagharimu tokeni tano - kwa hivyo anampa Bill kibali cha tokeni tano.
Kisha kitu kinabadilika na bei ya Bill inapanda hadi tokeni kumi. Alice, ambaye bado anataka huduma, anatuma muamala unaoweka kibali cha Bill kuwa kumi. Wakati Bill anapoona muamala huu mpya katika kusanyiko la miamala anatuma muamala unaotumia tokeni tano za Alice na una bei ya gesi ya juu zaidi ili uchimbwe haraka zaidi. Kwa njia hiyo Bill anaweza kutumia tokeni tano za kwanza na kisha, mara tu kibali kipya cha Alice kinapochimbwa, kutumia kumi zaidi kwa bei ya jumla ya tokeni kumi na tano, zaidi ya vile Alice alivyokusudia kuidhinisha. Mbinu hii inaitwa utangulizaji muamala (opens in a new tab)
| Muamala wa Alice | Nonsi ya Alice | Muamala wa Bill | Nonsi ya Bill | Kibali cha Bill | Jumla ya Mapato ya Bill kutoka kwa Alice |
|---|---|---|---|---|---|
| approve(Bill, 5) | 10 | 5 | 0 | ||
| transferFrom(Alice, Bill, 5) | 10,123 | 0 | 5 | ||
| approve(Bill, 10) | 11 | 10 | 5 | ||
| transferFrom(Alice, Bill, 10) | 10,124 | 0 | 15 |
Ili kuepuka tatizo hili, kazi hizi mbili (increaseAllowance na decreaseAllowance) zinakuruhusu
kurekebisha kibali kwa kiasi maalum. Kwa hivyo ikiwa Bill alikuwa tayari ametumia tokeni tano, ataweza tu
kutumia tano zaidi. Kulingana na muda, kuna njia mbili ambazo hii inaweza kufanya kazi, zote mbili
zinaishia kwa Bill kupata tokeni kumi tu:
A:
| Muamala wa Alice | Nonsi ya Alice | Muamala wa Bill | Nonsi ya Bill | Kibali cha Bill | Jumla ya Mapato ya Bill kutoka kwa Alice |
|---|---|---|---|---|---|
| approve(Bill, 5) | 10 | 5 | 0 | ||
| transferFrom(Alice, Bill, 5) | 10,123 | 0 | 5 | ||
| increaseAllowance(Bill, 5) | 11 | 0+5 = 5 | 5 | ||
| transferFrom(Alice, Bill, 5) | 10,124 | 0 | 10 |
B:
| Muamala wa Alice | Nonsi ya Alice | Muamala wa Bill | Nonsi ya Bill | Kibali cha Bill | Jumla ya Mapato ya Bill kutoka kwa Alice |
|---|---|---|---|---|---|
| approve(Bill, 5) | 10 | 5 | 0 | ||
| increaseAllowance(Bill, 5) | 11 | 5+5 = 10 | 0 | ||
| transferFrom(Alice, Bill, 10) | 10,124 | 0 | 10 |
/**
* @dev Inaongeza kibali kilichotolewa kwa `spender` na mpigaji kwa njia ya atomiki.
*
* Hii ni mbadala wa {idhinisha} ambayo inaweza kutumika kama suluhisho kwa
* matatizo yaliyoelezwa katika {IERC20-approve}.
*
* Inatoa tukio la {Approval} kuonyesha kibali kilichosasishwa.
*
* Mahitaji:
*
* - `spender` haiwezi kuwa anwani sifuri.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
return true;
}
Kazi ya a.add(b) ni nyongeza salama. Katika hali isiyowezekana kwamba a+b>=2^256 haizunguki
kwa njia ambayo nyongeza ya kawaida hufanya.
/**
* @dev Inapunguza kibali kilichotolewa kwa `spender` na mpigaji kwa njia ya atomiki.
*
* Hii ni mbadala wa {idhinisha} ambayo inaweza kutumika kama suluhisho kwa
* matatizo yaliyoelezwa katika {IERC20-approve}.
*
* Inatoa tukio la {Approval} kuonyesha kibali kilichosasishwa.
*
* Mahitaji:
*
* - `spender` haiwezi kuwa anwani sifuri.
* - `spender` lazima awe na kibali cha mpigaji cha angalau
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue,
"ERC20: decreased allowance below zero"));
return true;
}
Kazi Zinazobadilisha Taarifa za Tokeni
Hizi ni kazi nne zinazofanya kazi halisi: _transfer, _mint, _burn, na _approve.
Kazi ya _transfer
/**
* @dev Inahamisha tokeni `amount` kutoka kwa `sender` kwenda kwa `recipient`.
*
* Kitendaji hiki cha ndani ni sawa na {hamisho}, na kinaweza kutumika
* k.m., kutekeleza ada za tokeni za kiotomatiki, taratibu za kukata (slashing), n.k.
*
* Inatoa tukio la {hamisho}.
*
* Mahitaji:
*
* - `sender` haiwezi kuwa anwani sifuri.
* - `recipient` haiwezi kuwa anwani sifuri.
* - `sender` lazima awe na salio la angalau `amount`.
*/
function _transfer(address sender, address recipient, uint256 amount) internal virtual {
Kazi hii, _transfer, inahamisha tokeni kutoka akaunti moja hadi nyingine. Inaitwa na zote mbili
transfer (kwa uhamisho kutoka kwa akaunti ya mtumaji mwenyewe) na transferFrom (kwa kutumia vibali
kuhamisha kutoka kwa akaunti ya mtu mwingine).
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
Hakuna mtu anayemiliki anwani sifuri katika Ethereum (yaani, hakuna anayejua ufunguo wa siri ambao ufunguo wa umma unaolingana nao unabadilishwa kuwa anwani sifuri). Watu wanapotumia anwani hiyo, kwa kawaida ni hitilafu ya programu - kwa hivyo tunashindwa ikiwa anwani sifuri inatumiwa kama mtumaji au mpokeaji.
_beforeTokenTransfer(sender, recipient, amount);
Kuna njia mbili za kutumia mkataba huu:
- Uitumie kama kiolezo kwa msimbo wako mwenyewe
- Urithi kutoka kwake (opens in a new tab), na ubatilishe tu kazi zile unazohitaji kurekebisha
Njia ya pili ni bora zaidi kwa sababu msimbo wa ERC-20 wa OpenZeppelin tayari umekaguliwa na kuonyeshwa kuwa salama. Unapotumia urithi inakuwa wazi ni kazi gani unazorekebisha, na ili kuamini mkataba wako watu wanahitaji tu kukagua kazi hizo maalum.
Mara nyingi ni muhimu kufanya kazi kila wakati tokeni zinapobadilisha mikono. Hata hivyo, _transfer ni kazi muhimu sana na inawezekana
kuiandika kwa njia isiyo salama (tazama hapa chini), kwa hivyo ni bora kutoibatilisha. Suluhisho ni _beforeTokenTransfer,
kazi ya ndoano (opens in a new tab). Unaweza kubatilisha kazi hii, na itaitwa kwenye kila hamisho.
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
_balances[recipient] = _balances[recipient].add(amount);
Hii ndio mistari inayofanya hamisho halisi. Kumbuka kwamba hakuna kitu kati yao, na kwamba tunatoa kiasi kilichohamishwa kutoka kwa mtumaji kabla ya kukiongeza kwa mpokeaji. Hii ni muhimu kwa sababu ikiwa kungekuwa na mwito kwa mkataba tofauti katikati, ingeweza kutumika kudanganya mkataba huu. Kwa njia hii hamisho ni la atomiki, hakuna kinachoweza kutokea katikati yake.
emit Transfer(sender, recipient, amount);
}
Hatimaye, toa tukio la Transfer. Matukio hayafikiki kwa mikataba mahiri, lakini msimbo unaoendeshwa nje ya mnyororo wa vitalu
unaweza kusikiliza matukio na kuyajibu. Kwa mfano, mkoba unaweza kufuatilia wakati mmiliki anapata tokeni zaidi.
Kazi za _mint na _burn
Kazi hizi mbili (_mint na _burn) zinabadilisha jumla ya usambazaji wa tokeni.
Ni za ndani na hakuna kazi inayozipiga katika mkataba huu,
kwa hivyo zinafaa tu ikiwa unarithi kutoka kwa mkataba na kuongeza
mantiki yako mwenyewe kuamua chini ya hali gani ya kufua tokeni mpya au kuteketeza zilizopo.
KUMBUKA: Kila tokeni ya ERC-20 ina mantiki yake ya biashara inayoamuru usimamizi wa tokeni.
Kwa mfano, mkataba wa usambazaji uliowekwa unaweza tu kuita _mint
katika konstrukta na usiwahi kuita _burn. Mkataba unaouza tokeni
utaita _mint unapolipwa, na labda kuita _burn wakati fulani
ili kuepuka mfumuko wa bei unaokimbia.
/** @dev Inaunda tokeni `amount` na kuzikabidhi kwa `akaunti`, na kuongeza
* usambazaji wa jumla.
*
* Inatoa tukio la {hamisho} huku `from` ikiwekwa kuwa anwani sifuri.
*
* Mahitaji:
*
* - `to` haiwezi kuwa anwani sifuri.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}
Hakikisha unasasisha _totalSupply wakati jumla ya idadi ya tokeni inabadilika.
/**
* @dev Inaharibu tokeni `amount` kutoka kwenye `akaunti`, na kupunguza
* usambazaji wa jumla.
*
* Inatoa tukio la {hamisho} huku `to` ikiwekwa kuwa anwani sifuri.
*
* Mahitaji:
*
* - `akaunti` haiwezi kuwa anwani sifuri.
* - `akaunti` lazima iwe na angalau tokeni `amount`.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
_balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
_totalSupply = _totalSupply.sub(amount);
emit Transfer(account, address(0), amount);
}
Kazi ya _burn inakaribia kufanana na _mint, isipokuwa inaenda upande mwingine.
Kazi ya _approve
Hii ndio kazi ambayo inabainisha vibali. Kumbuka kwamba inaruhusu mmiliki kubainisha kibali ambacho ni cha juu kuliko salio la sasa la mmiliki. Hii ni Sawa kwa sababu salio linaangaliwa wakati wa hamisho, ambapo linaweza kuwa tofauti na salio wakati kibali kinaundwa.
/**
* @dev Inaweka `amount` kama kibali cha `spender` juu ya tokeni za `owner`.
*
* Kitendaji hiki cha ndani ni sawa na `idhinisha`, na kinaweza kutumika
* k.m., kuweka vibali vya kiotomatiki kwa mifumo midogo fulani, n.k.
*
* Inatoa tukio la {Approval}.
*
* Mahitaji:
*
* - `owner` haiwezi kuwa anwani sifuri.
* - `spender` haiwezi kuwa anwani sifuri.
*/
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
Toa tukio la Approval. Kulingana na jinsi programu inavyoandikwa, mkataba wa mtumiaji unaweza kuambiwa kuhusu
idhini iwe na mmiliki au na seva inayosikiliza matukio haya.
emit Approval(owner, spender, amount);
}
Rekebisha Kigezo cha Desimali
/**
* @dev Inaweka {decimals} kuwa thamani tofauti na ile ya chaguo-msingi ya 18.
*
* ONYO: Kitendaji hiki kinapaswa kuitwa tu kutoka kwenye konstrukta. Programu
* nyingi zinazoingiliana na mikataba ya tokeni hazitatarajia
* {decimals} kubadilika kamwe, na zinaweza kufanya kazi vibaya ikiwa itabadilika.
*/
function _setupDecimals(uint8 decimals_) internal {
_decimals = decimals_;
}
Kazi hii inarekebisha kigezo cha _decimals ambacho kinatumika kuambia violesura vya mtumiaji jinsi ya kutafsiri kiasi.
Unapaswa kuiita kutoka kwa konstrukta. Ingekuwa si uaminifu kuiita katika hatua yoyote inayofuata, na programu
hazijaundwa kushughulikia hilo.
Ndoano
/**
* @dev Ndoano (Hook) inayoitwa kabla ya hamisho lolote la tokeni. Hii inajumuisha
* uundaji (minting) na uchomaji (burning).
*
* Masharti ya wito:
*
* - wakati `from` na `to` zote sio sifuri, tokeni `amount` za ``from``
* zitahamishwa kwenda kwa `to`.
* - wakati `from` ni sifuri, tokeni `amount` zitaundwa kwa ajili ya `to`.
* - wakati `to` ni sifuri, tokeni `amount` za ``from`` zitachomwa.
* - `from` na `to` haziwezi kuwa sifuri zote mbili.
*
* Ili kujifunza zaidi kuhusu ndoano, nenda kwenye xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}
Hii ni kazi ya ndoano itakayoitwa wakati wa uhamisho. Ni tupu hapa, lakini ikiwa unahitaji ifanye kitu unaibatilisha tu.
Hitimisho
Kwa ukaguzi, haya ni baadhi ya mawazo muhimu zaidi katika mkataba huu (kwa maoni yangu, yako yanaweza kutofautiana):
- Hakuna siri kwenye mnyororo wa vitalu. Taarifa yoyote ambayo mkataba mahiri unaweza kufikia inapatikana kwa ulimwengu mzima.
- Unaweza kudhibiti mpangilio wa miamala yako mwenyewe, lakini sio wakati muamala wa watu wengine unatokea. Hii ndiyo sababu kubadilisha kibali kunaweza kuwa hatari, kwa sababu inaruhusu mtumiaji kutumia jumla ya vibali vyote viwili.
- Thamani za aina ya
uint256huzunguka. Kwa maneno mengine, 0-1=2^256-1. Ikiwa hiyo sio tabia inayotakiwa, lazima uiangalie (au utumie maktaba ya SafeMath inayokufanyia hivyo). Kumbuka kwamba hii ilibadilika katika Solidity 0.8.0 (opens in a new tab). - Fanya mabadiliko yote ya hali ya aina maalum katika eneo maalum, kwa sababu inafanya ukaguzi kuwa rahisi.
Hii ndiyo sababu tunayo, kwa mfano,
_approve, ambayo inaitwa naapprove,transferFrom,increaseAllowance, nadecreaseAllowance - Mabadiliko ya hali yanapaswa kuwa ya atomiki, bila kitendo kingine chochote katikati yao (kama unavyoweza kuona
katika
_transfer). Hii ni kwa sababu wakati wa mabadiliko ya hali unakuwa na hali isiyolingana. Kwa mfano, kati ya wakati unapotoa kutoka kwa salio la mtumaji na wakati unapoongeza kwenye salio la mpokeaji kuna tokeni chache zilizopo kuliko inavyopaswa kuwa. Hii inaweza kutumiwa vibaya ikiwa kuna shughuli kati yao, haswa miito kwa mkataba tofauti.
Sasa kwa kuwa umeona jinsi mkataba wa ERC-20 wa OpenZeppelin unavyoandikwa, na haswa jinsi unavyofanywa kuwa salama zaidi, nenda ukaandike mikataba na programu zako salama.
