Ruka kwenda kwenye maudhui makuu

Mwongozo wa mkataba wa daraja la kawaida la Optimism

solidity
bridge
layer 2
Intermediate
Ori Pomerantz
30 Machi 2022
31 minute read

Optimismopens in a new tab ni Optimistic Rollup. Optimistic rollups zinaweza kuchakata miamala kwa bei ya chini zaidi kuliko Mtandao Mkuu wa Ethereum (pia inajulikana kama safu 1 au L1) kwa sababu miamala inachakatwa na nodi chache tu, badala ya kila nodi kwenye mtandao. Wakati huo huo, data yote imeandikwa kwa L1 ili kila kitu kiweze kuthibitishwa na kujengwa upya kwa uadilifu wote na dhamana ya upatikanaji wa Mtandao Mkuu.

Ili kutumia rasilimali za L1 kwenye Optimism (au L2 nyingine yoyote), rasilimali zinahitaji kuvushwa kwa daraja. Njia moja ya kufanikisha hili ni kwa watumiaji kufunga rasilimali (ETH na tokeni za ERC-20 ndizo za kawaida zaidi) kwenye L1, na kupokea rasilimali sawa za kutumia kwenye L2. Hatimaye, yeyote atakayeishia nazo anaweza kutaka kuzivusha kwa daraja kurudi L1. Wakati wa kufanya hivi, rasilimali huchomwa kwenye L2 na kisha kurejeshwa kwa mtumiaji kwenye L1.

Hivi ndivyo daraja la kawaida la Optimismopens in a new tab linavyofanya kazi. Katika makala haya tunapitia msimbo chanzo wa daraja hilo ili kuona jinsi linavyofanya kazi na kulichunguza kama mfano wa msimbo wa Solidity ulioandikwa vizuri.

Mtiririko wa udhibiti

Daraja lina mitiririko mikuu miwili:

  • Kuweka (kutoka L1 hadi L2)
  • Kutoa (kutoka L2 hadi L1)

Mtiririko wa uwekaji

Safu 1

  1. Ikiwa unaweka ERC-20, mwekaji anatoa ruhusa kwa daraja kutumia kiasi kinachowekwa
  2. Mwekaji anapiga simu daraja la L1 (depositERC20, depositERC20To, depositETH, au depositETHTo)
  3. Daraja la L1 linachukua umiliki wa rasilimali iliyovushwa kwa daraja
    • ETH: Rasilimali inahamishwa na mwekaji kama sehemu ya simu
    • ERC-20: Rasilimali inahamishwa na daraja kwake yenyewe kwa kutumia ruhusa iliyotolewa na mwekaji
  4. Daraja la L1 linatumia utaratibu wa ujumbe wa vikoa tofauti kupiga simu finalizeDeposit kwenye daraja la L2

Safu 2

  1. Daraja la L2 huhakikisha simu kwa finalizeDeposit ni halali:
    • Imetoka kwenye mkataba wa ujumbe wa vikoa tofauti
    • Hapo awali ilitoka kwenye daraja kwenye L1
  2. Daraja la L2 huangalia ikiwa mkataba wa tokeni ya ERC-20 kwenye L2 ni sahihi:
    • Mkataba wa L2 unaripoti kuwa mwenzake wa L1 ni sawa na ule ambao tokeni zilitoka kwenye L1
    • Mkataba wa L2 unaripoti kuwa unaauni kiolesura sahihi (kwa kutumia ERC-165opens in a new tab).
  3. Ikiwa mkataba wa L2 ni sahihi, piga simu ili kutoa idadi ifaayo ya tokeni kwa anwani ifaayo. Ikiwa sivyo, anza mchakato wa kutoa ili kumruhusu mtumiaji kudai tokeni kwenye L1.

Mtiririko wa utoaji

Safu 2

  1. Mtoaji anapiga simu daraja la L2 (withdraw au withdrawTo)
  2. Daraja la L2 huchoma idadi ifaayo ya tokeni za msg.sender
  3. Daraja la L2 hutumia utaratibu wa ujumbe wa vikoa tofauti kupiga simu finalizeETHWithdrawal au finalizeERC20Withdrawal kwenye daraja la L1

Safu 1

  1. Daraja la L1 huhakikisha simu kwa finalizeETHWithdrawal au finalizeERC20Withdrawal ni halali:
    • Imetoka kwenye utaratibu wa ujumbe wa vikoa tofauti
    • Hapo awali ilitoka kwenye daraja kwenye L2
  2. Daraja la L1 huhamisha rasilimali ifaayo (ETH au ERC-20) kwa anwani ifaayo

Msimbo wa Safu 1

Huu ndio msimbo unaoendeshwa kwenye L1, Mtandao Mkuu wa Ethereum.

IL1ERC20Bridge

Kiolesura hiki kimefafanuliwa hapaopens in a new tab. Inajumuisha kazi na ufafanuzi unaohitajika kwa kuvusha tokeni za ERC-20 kwa daraja.

1// SPDX-License-Identifier: MIT

Sehemu kubwa ya msimbo wa Optimism hutolewa chini ya leseni ya MITopens in a new tab.

1pragma solidity >0.5.0 <0.9.0;

Wakati wa kuandika toleo la hivi karibuni la Solidity ni 0.8.12. Hadi toleo la 0.9.0 litolewe, hatujui kama msimbo huu unaendana nalo au la.

1/**
2 * @title IL1ERC20Bridge
3 */
4interface IL1ERC20Bridge {
5 /**********
6 * Matukio *
7 **********/
8
9 event ERC20DepositInitiated(
Onyesha yote

Katika istilahi za daraja la Optimism kuweka kunamaanisha uhamisho kutoka L1 hadi L2, na kutoa kunamaanisha uhamisho kutoka L2 hadi L1.

1 anwani iliyoorodheshwa _l1Token,
2 anwani iliyoorodheshwa _l2Token,

Katika hali nyingi anwani ya ERC-20 kwenye L1 si sawa na anwani ya ERC-20 inayolingana kwenye L2. Unaweza kuona orodha ya anwani za tokeni hapaopens in a new tab. Anwani yenye chainId 1 iko kwenye L1 (Mtandao Mkuu) na anwani yenye chainId 10 iko kwenye L2 (Optimism). Thamani zingine mbili za chainId ni za mtandao wa majaribio wa Kovan (42) na mtandao wa majaribio wa Optimistic Kovan (69).

1 anwani iliyoorodheshwa _from,
2 anwani _to,
3 uint256 _amount,
4 bytes _data
5 );

Inawezekana kuongeza maelezo kwenye uhamisho, ambapo huongezwa kwenye matukio yanayoyaripoti.

1 event ERC20WithdrawalFinalized(
2 anwani iliyoorodheshwa _l1Token,
3 anwani iliyoorodheshwa _l2Token,
4 anwani iliyoorodheshwa _from,
5 anwani _to,
6 uint256 _amount,
7 bytes _data
8 );

Mkataba huo huo wa daraja hushughulikia uhamisho katika pande zote mbili. Kwa upande wa daraja la L1, hii inamaanisha uanzishaji wa uwekaji na ukamilishaji wa utoaji.

1
2 /********************
3 * Kazi za Umma *
4 ********************/
5
6 /**
7 * @dev pata anwani ya mkataba wa daraja la L2 unaolingana.
8 * @return Anwani ya mkataba wa daraja la L2 unaolingana.
9 */
10 function l2TokenBridge() external returns (anwani);
Onyesha yote

Kazi hii haihitajiki sana, kwa sababu kwenye L2 ni mkataba uliotumwa awali, kwa hivyo huwa kwenye anwani 0x4200000000000000000000000000000000000010. Iko hapa kwa ulinganifu na daraja la L2, kwa sababu anwani ya daraja la L1 si rahisi kujua.

1 /**
2 * @dev weka kiasi cha ERC20 kwenye salio la mpigaji simu kwenye L2.
3 * @param _l1Token Anwani ya ERC20 ya L1 tunayoweka
4 * @param _l2Token Anwani ya ERC20 ya L2 inayolingana na L1
5 * @param _amount Kiasi cha ERC20 cha kuweka
6 * @param _l2Gas Kikomo cha gesi kinachohitajika kukamilisha uwekaji kwenye L2.
7 * @param _data Data ya hiari ya kupeleka kwa L2. Data hii hutolewa
8 * kama urahisi tu kwa mikataba ya nje. Mbali na kutekeleza urefu wa juu,
9 * mikataba hii haitoi dhamana yoyote kuhusu maudhui yake.
10 */
11 function depositERC20(
12 anwani _l1Token,
13 anwani _l2Token,
14 uint256 _amount,
15 uint32 _l2Gas,
16 bytes calldata _data
17 ) external;
Onyesha yote

Kigezo cha _l2Gas ni kiasi cha gesi ya L2 ambacho muamala unaruhusiwa kutumia. Hadi kikomo fulani (cha juu), hii ni bureopens in a new tab, kwa hivyo isipokuwa mkataba wa ERC-20 unafanya jambo la ajabu sana wakati wa kutoa, haipaswi kuwa suala. Kazi hii inashughulikia hali ya kawaida, ambapo mtumiaji huvusha rasilimali kwa daraja kwenda anwani ileile kwenye mnyororo wa bloku tofauti.

1 /**
2 * @dev weka kiasi cha ERC20 kwenye salio la mpokeaji kwenye L2.
3 * @param _l1Token Anwani ya ERC20 ya L1 tunayoweka
4 * @param _l2Token Anwani ya ERC20 ya L2 inayolingana na L1
5 * @param _to Anwani ya L2 ya kuweka utoaji.
6 * @param _amount Kiasi cha ERC20 cha kuweka.
7 * @param _l2Gas Kikomo cha gesi kinachohitajika kukamilisha uwekaji kwenye L2.
8 * @param _data Data ya hiari ya kupeleka kwa L2. Data hii hutolewa
9 * kama urahisi tu kwa mikataba ya nje. Mbali na kutekeleza urefu wa juu,
10 * mikataba hii haitoi dhamana yoyote kuhusu maudhui yake.
11 */
12 function depositERC20To(
13 anwani _l1Token,
14 anwani _l2Token,
15 anwani _to,
16 uint256 _amount,
17 uint32 _l2Gas,
18 bytes calldata _data
19 ) external;
Onyesha yote

Kazi hii ni karibu sawa na depositERC20, lakini inakuwezesha kutuma ERC-20 kwa anwani tofauti.

1 /*************************
2 * Kazi za Mnyororo-Msalaba *
3 *************************/
4
5 /**
6 * @dev Kamilisha utoaji kutoka L2 hadi L1, na weka fedha kwenye salio la mpokeaji la
7 * tokeni ya L1 ERC20.
8 * Simu hii itashindwa ikiwa utoaji ulioanzishwa kutoka L2 haujakamilishwa.
9 *
10 * @param _l1Token Anwani ya tokeni ya L1 ya kukamilisha utoaji.
11 * @param _l2Token Anwani ya tokeni ya L2 ambapo utoaji ulianzishwa.
12 * @param _from Anwani ya L2 inayoanzisha uhamisho.
13 * @param _to Anwani ya L1 ya kuweka utoaji.
14 * @param _amount Kiasi cha ERC20 cha kuweka.
15 * @param _data Data iliyotolewa na mtumaji kwenye L2. Data hii hutolewa
16 * kama urahisi tu kwa mikataba ya nje. Mbali na kutekeleza urefu wa juu,
17 * mikataba hii haitoi dhamana yoyote kuhusu maudhui yake.
18 */
19 function finalizeERC20Withdrawal(
20 anwani _l1Token,
21 anwani _l2Token,
22 anwani _from,
23 anwani _to,
24 uint256 _amount,
25 bytes calldata _data
26 ) external;
27}
Onyesha yote

Utoaji (na ujumbe mwingine kutoka L2 hadi L1) katika Optimism ni mchakato wa hatua mbili:

  1. Muamala wa kuanzisha kwenye L2.
  2. Muamala wa ukamilishaji au kudai kwenye L1. Muamala huu unahitaji kufanyika baada ya kipindi cha changamoto ya makosaopens in a new tab kwa muamala wa L2 kuisha.

IL1StandardBridge

Kiolesura hiki kimefafanuliwa hapaopens in a new tab. Faili hili lina ufafanuzi wa matukio na kazi kwa ETH. Ufafanuzi huu unafanana sana na ule uliofafanuliwa katika IL1ERC20Bridge hapo juu kwa ERC-20.

Kiolesura cha daraja kimegawanywa kati ya faili mbili kwa sababu baadhi ya tokeni za ERC-20 zinahitaji usindikaji maalum na haziwezi kushughulikiwa na daraja la kawaida. Kwa njia hii daraja maalum linaloshughulikia tokeni kama hiyo linaweza kutekeleza IL1ERC20Bridge na sio lazima pia kuvusha ETH kwa daraja.

1// SPDX-License-Identifier: MIT
2pragma solidity >0.5.0 <0.9.0;
3
4import "./IL1ERC20Bridge.sol";
5
6/**
7 * @title IL1StandardBridge
8 */
9interface IL1StandardBridge is IL1ERC20Bridge {
10 /**********
11 * Matukio *
12 **********/
13 event ETHDepositInitiated(
14 anwani iliyoorodheshwa _from,
15 anwani iliyoorodheshwa _to,
16 uint256 _amount,
17 bytes _data
18 );
Onyesha yote

Tukio hili ni karibu sawa na toleo la ERC-20 (ERC20DepositInitiated), isipokuwa bila anwani za tokeni za L1 na L2. Hali kadhalika kwa matukio mengine na kazi.

1 event ETHWithdrawalFinalized(
2 .
3 .
4 .
5 );
6
7 /********************
8 * Kazi za Umma *
9 ********************/
10
11 /**
12 * @dev Weka kiasi cha ETH kwenye salio la mpigaji simu kwenye L2.
13 .
14 .
15 .
16 */
17 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable;
18
19 /**
20 * @dev Weka kiasi cha ETH kwenye salio la mpokeaji kwenye L2.
21 .
22 .
23 .
24 */
25 function depositETHTo(
26 anwani _to,
27 uint32 _l2Gas,
28 bytes calldata _data
29 ) external payable;
30
31 /*************************
32 * Kazi za Mnyororo-Msalaba *
33 *************************/
34
35 /**
36 * @dev Kamilisha utoaji kutoka L2 hadi L1, na weka fedha kwenye salio la mpokeaji la
37 * tokeni ya L1 ETH. Kwa kuwa ni xDomainMessenger pekee anayeweza kupiga simu kazi hii, haitapigwa kamwe
38 * kabla ya utoaji kukamilishwa.
39 .
40 .
41 .
42 */
43 function finalizeETHWithdrawal(
44 anwani _from,
45 anwani _to,
46 uint256 _amount,
47 bytes calldata _data
48 ) external;
49}
Onyesha yote

CrossDomainEnabled

Mkataba huuopens in a new tab unarithiwa na madaraja yote mawili (L1 na L2) kutuma ujumbe kwa safu nyingine.

1// SPDX-License-Identifier: MIT
2pragma solidity >0.5.0 <0.9.0;
3
4/* Interface Imports */
5import { ICrossDomainMessenger } from "./ICrossDomainMessenger.sol";

Kiolesura hikiopens in a new tab kinaeleza mkataba jinsi ya kutuma ujumbe kwa safu nyingine, kwa kutumia mtumaji ujumbe wa vikoa tofauti. Mtumaji ujumbe huu wa vikoa tofauti ni mfumo mwingine kabisa, na unastahili makala yake, ambayo natumai kuandika katika siku zijazo.

1/**
2 * @title CrossDomainEnabled
3 * @dev Mkataba msaidizi kwa mikataba inayofanya mawasiliano ya vikoa tofauti
4 *
5 * Mkusanyaji uliotumika: umefafanuliwa na mkataba unaorithi
6 */
7contract CrossDomainEnabled {
8 /*************
9 * Vigeu *
10 *************/
11
12 // Mkataba wa Mtumaji Ujumbe unaotumika kutuma na kupokea ujumbe kutoka kwa kikoa kingine.
13 anwani public messenger;
14
15 /***************
16 * Kijenzi *
17 ***************/
18
19 /**
20 * @param _messenger Anwani ya CrossDomainMessenger kwenye safu ya sasa.
21 */
22 constructor(anwani _messenger) {
23 messenger = _messenger;
24 }
Onyesha yote

Kigezo kimoja ambacho mkataba unahitaji kujua, anwani ya mtumaji ujumbe wa vikoa tofauti kwenye safu hii. Kigezo hiki kinawekwa mara moja, katika kijenzi, na hakibadiliki kamwe.

1
2 /**********************
3 * Virekebishaji Kazi *
4 **********************/
5
6 /**
7 * Hutekeleza kwamba kazi iliyorekebishwa inaweza kupigwa simu tu na akaunti maalum ya kikoa tofauti.
8 * @param _sourceDomainAccount Akaunti pekee kwenye kikoa cha asili ambayo
9 * imethibitishwa kupiga simu kazi hii.
10 */
11 modifier onlyFromCrossDomainAccount(anwani _sourceDomainAccount) {
Onyesha yote

Ujumbe wa vikoa tofauti unaweza kufikiwa na mkataba wowote kwenye mnyororo wa bloku ambapo unaendeshwa (iwe Mtandao Mkuu wa Ethereum au Optimism). Lakini tunahitaji daraja kwa kila upande tu kuamini ujumbe fulani ikiwa unatoka kwenye daraja kwa upande mwingine.

1 require(
2 msg.sender == address(getCrossDomainMessenger()),
3 "OVM_XCHAIN: mkataba wa mtumaji ujumbe haujathibitishwa"
4 );

Ujumbe tu kutoka kwa mtumaji ujumbe wa vikoa tofauti unaofaa (messenger, kama unavyoona hapa chini) unaweza kuaminiwa.

1
2 require(
3 getCrossDomainMessenger().xDomainMessageSender() == _sourceDomainAccount,
4 "OVM_XCHAIN: mtumaji asiye sahihi wa ujumbe wa kikoa tofauti"
5 );

Njia ambayo mtumaji ujumbe wa vikoa tofauti anatoa anwani iliyotuma ujumbe na safu nyingine ni kazi ya .xDomainMessageSender()opens in a new tab. Muda wote inapoitwa katika muamala ulioanzishwa na ujumbe inaweza kutoa habari hii.

Tunahitaji kuhakikisha kuwa ujumbe tuliopokea ulitoka kwenye daraja lingine.

1
2 _;
3 }
4
5 /**********************
6 * Kazi za Ndani *
7 **********************/
8
9 /**
10 * Hupata mtumaji ujumbe, kawaida kutoka kwenye hifadhi. Kazi hii inawekwa wazi iwapo mkataba mtoto
11 * unahitaji kubatilisha.
12 * @rejesha Anwani ya mkataba wa mtumaji ujumbe wa vikoa tofauti ambayo inapaswa kutumika.
13 */
14 function getCrossDomainMessenger() internal virtual returns (ICrossDomainMessenger) {
15 return ICrossDomainMessenger(messenger);
16 }
Onyesha yote

Kazi hii inarejesha mtumaji ujumbe wa vikoa tofauti. Tunatumia kazi badala ya kigezo messenger kuruhusu mikataba inayorithi kutoka kwa hii kutumia algoriti kubainisha ni mtumaji ujumbe gani wa vikoa tofauti wa kutumia.

1
2 /**
3 * Hutuma ujumbe kwa akaunti kwenye kikoa kingine
4 * @param _crossDomainTarget Mpokeaji aliyekusudiwa kwenye kikoa lengwa
5 * @param _message Data ya kutuma kwa lengo (kawaida calldata kwa kazi yenye
6 * `onlyFromCrossDomainAccount()`)
7 * @param _gasLimit Kikomo cha gesi kwa risiti ya ujumbe kwenye kikoa lengwa.
8 */
9 function sendCrossDomainMessage(
10 anwani _crossDomainTarget,
11 uint32 _gasLimit,
12 bytes memory _message
Onyesha yote

Hatimaye, kazi inayotuma ujumbe kwa safu nyingine.

1 ) internal {
2 // slither-disable-next-line reentrancy-events, reentrancy-benign

Slitheropens in a new tab ni kichanganuzi tuli ambacho Optimism huendesha kwenye kila mkataba kutafuta udhaifu na matatizo mengine yanayoweza kutokea. Katika kesi hii, mstari unaofuata unasababisha udhaifu mbili:

  1. Matukio ya Kuingia Tenaopens in a new tab
  2. Kuingia tena kusiko na madharaopens in a new tab
1 getCrossDomainMessenger().sendMessage(_crossDomainTarget, _message, _gasLimit);
2 }
3}

Katika kesi hii hatuna wasiwasi kuhusu kuingia tena tunajua getCrossDomainMessenger() inarejesha anwani inayoaminika, hata kama Slither haina njia ya kujua hilo.

Mkataba wa daraja la L1

Msimbo chanzo wa mkataba huu uko hapaopens in a new tab.

1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.9;

Violesura vinaweza kuwa sehemu ya mikataba mingine, kwa hivyo vinapaswa kuunga mkono anuwai ya matoleo ya Solidity. Lakini daraja lenyewe ni mkataba wetu, na tunaweza kuwa wakali kuhusu toleo gani la Solidity linatumia.

1/* Interface Imports */
2import { IL1StandardBridge } from "./IL1StandardBridge.sol";
3import { IL1ERC20Bridge } from "./IL1ERC20Bridge.sol";

IL1ERC20Bridge na IL1StandardBridge zimeelezwa hapo juu.

1import { IL2ERC20Bridge } from "../../L2/messaging/IL2ERC20Bridge.sol";

Kiolesura hikiopens in a new tab kinaturuhusu kuunda ujumbe wa kudhibiti daraja la kawaida kwenye L2.

1import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

Kiolesura hikiopens in a new tab kinaturuhusu kudhibiti mikataba ya ERC-20. Unaweza kusoma zaidi kuhusu hilo hapa.

1/* Library Imports */
2import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";

Kama ilivyoelezwa hapo juu, mkataba huu unatumika kwa ujumbe wa kati ya safu.

1import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";

Lib_PredeployAddressesopens in a new tab ina anwani za mikataba ya L2 ambayo huwa na anwani sawa kila wakati. Hii inajumuisha daraja la kawaida kwenye L2.

1import { Address } from "@openzeppelin/contracts/utils/Address.sol";

Huduma za Anwani za OpenZeppelinopens in a new tab. Inatumika kutofautisha kati ya anwani za mkataba na zile za akaunti zinazomilikiwa na watu wa nje (EOA).

Kumbuka kuwa hili si suluhisho kamilifu, kwa sababu hakuna njia ya kutofautisha kati ya simu za moja kwa moja na simu zilizopigwa kutoka kwa kijenzi cha mkataba, lakini angalau hii inatuwezesha kutambua na kuzuia baadhi ya makosa ya kawaida ya watumiaji.

1import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

Kiwango cha ERC-20opens in a new tab kinaauni njia mbili kwa mkataba kuripoti kutofaulu:

  1. Batilisha
  2. Rejesha false

Kushughulikia kesi zote mbili kungefanya msimbo wetu kuwa mgumu zaidi, kwa hivyo badala yake tunatumia SafeERC20 ya OpenZeppelinopens in a new tab, ambayo inahakikisha kushindwa kote kunasababisha ubatilishajiopens in a new tab.

1/**
2 * @title L1StandardBridge
3 * @dev L1 ETH na ERC20 Bridge ni mkataba unaohifadhi fedha za L1 zilizowekwa na tokeni za kawaida
4 * ambazo zinatumika kwenye L2. Inasawazisha Daraja la L2 linalolingana, kulijulisha kuhusu uwekaji
5 * na kulisikiliza kwa utoaji mpya uliokamilishwa.
6 *
7 */
8contract L1StandardBridge is IL1StandardBridge, CrossDomainEnabled {
9 using SafeERC20 for IERC20;
Onyesha yote

Mstari huu ndivyo tunavyobainisha kutumia kifuniko cha SafeERC20 kila tunapotumia kiolesura cha IERC20.

1
2 /********************************
3 * Marejeleo ya Mkataba wa Nje *
4 ********************************/
5
6 anwani public l2TokenBridge;

Anwani ya L2StandardBridge.

1
2 // Ramani za tokeni ya L1 kwa tokeni ya L2 kwa salio la tokeni ya L1 iliyowekwa
3 mapping(anwani => mapping(anwani => uint256)) public deposits;

Ramani maradufu kama hii ndiyo njia unayofafanua safu ya pande mbili isiyo na mpangilioopens in a new tab. Thamani katika muundo huu wa data zinatambuliwa kama deposit[anwani ya tokeni ya L1][anwani ya tokeni ya L2]. Thamani chaguo-msingi ni sufuri. Seli tu zilizowekwa kwa thamani tofauti ndizo huandikwa kwenye hifadhi.

1
2 /***************
3 * Kijenzi *
4 ***************/
5
6 // Mkataba huu unaishi nyuma ya wakala, kwa hivyo vigezo vya kijenzi havitatumika.
7 constructor() CrossDomainEnabled(anwani(0)) {}

Ili kutaka kuweza kusasisha mkataba huu bila kulazimika kunakili vigezo vyote kwenye hifadhi. Ili kufanya hivyo tunatumia Wakalaopens in a new tab, mkataba unaotumia delegatecallopens in a new tab kuhamisha simu kwa mkataba tofauti ambao anwani yake imehifadhiwa na mkataba wa wakala (unaposasisha unamwambia wakala kubadilisha anwani hiyo). Unapotumia delegatecall hifadhi inabaki kuwa hifadhi ya mkataba unaopiga simu, kwa hivyo thamani za vigezo vyote vya hali ya mkataba haziathiriwi.

Moja ya athari za muundo huu ni kwamba hifadhi ya mkataba ambao ni uliopigiwa simu wa delegatecall haitumiwi na kwa hivyo thamani za kijenzi zilizopitishwa kwake hazijalishi. Hii ndiyo sababu tunaweza kutoa thamani isiyo na maana kwa kijenzi cha CrossDomainEnabled. Pia ndiyo sababu uanzishaji hapa chini umetenganishwa na kijenzi.

1 /******************
2 * Uanzishaji *
3 ******************/
4
5 /**
6 * @param _l1messenger Anwani ya L1 Messenger inayotumika kwa mawasiliano ya mnyororo-msalaba.
7 * @param _l2TokenBridge Anwani ya daraja la kawaida la L2.
8 */
9 // slither-disable-next-line external-function
Onyesha yote

Jaribio hili la Slitheropens in a new tab linatambua kazi ambazo hazipigwi simu kutoka kwa msimbo wa mkataba na kwa hivyo zinaweza kutangazwa kuwa za nje badala ya za umma. Gharama ya gesi ya kazi za nje inaweza kuwa chini, kwa sababu zinaweza kupewa vigezo katika calldata. Kazi zilizotangazwa kuwa za umma zinapaswa kufikiwa kutoka ndani ya mkataba. Mikataba haiwezi kurekebisha calldata zao wenyewe, kwa hivyo vigezo vinapaswa kuwa kwenye kumbukumbu. Wakati kazi kama hiyo inapoitwa kutoka nje, ni muhimu kunakili calldata kwenye kumbukumbu, ambayo inagharimu gesi. Katika kesi hii kazi inaitwa mara moja tu, kwa hivyo ufanisi haujalishi kwetu.

1 function initialize(anwani _l1messenger, anwani _l2TokenBridge) public {
2 require(messenger == address(0), "Mkataba tayari umeanzishwa.");

Kazi ya initialize inapaswa kuitwa mara moja tu. Ikiwa anwani ya mtumaji ujumbe wa vikoa tofauti wa L1 au daraja la tokeni la L2 itabadilika, tunaunda wakala mpya na daraja jipya linaloiita. Hili haliwezekani kutokea isipokuwa mfumo mzima unaposasishwa, tukio la nadra sana.

Kumbuka kuwa kazi hii haina utaratibu wowote unaozuia nani anaweza kuiita. Hii inamaanisha kuwa kwa nadharia mshambuliaji anaweza kusubiri hadi tutumie wakala na toleo la kwanza la daraja na kisha kufanya shughuli harakaopens in a new tab kufikia kazi ya initialize kabla ya mtumiaji halali kufanya hivyo. Lakini kuna njia mbili za kuzuia hili:

  1. Ikiwa mikataba haitumwi moja kwa moja na EOA lakini katika muamala ambao una mkataba mwingine uwaumbeopens in a new tab mchakato mzima unaweza kuwa wa atomiki, na kumalizika kabla ya muamala mwingine wowote kutekelezwa.
  2. Ikiwa simu halali kwa initialize itashindwa daima inawezekana kupuuza wakala na daraja jipya lililoundwa na kuunda mpya.
1 messenger = _l1messenger;
2 l2TokenBridge = _l2TokenBridge;
3 }

Hivi ni vigezo viwili ambavyo daraja linahitaji kujua.

1
2 /**************
3 * Uwekaji *
4 **************/
5
6 /** @dev Kirekebishaji kinachohitaji mtumaji awe EOA. Ukaguzi huu unaweza kuepukwa na mkataba hasidi
7 * kupitia initcode, lakini inashughulikia kosa la mtumiaji tunalotaka kuepuka.
8 */
9 modifier onlyEOA() {
10 // Inatumika kusimamisha uwekaji kutoka kwa mikataba (epuka tokeni zilizopotea kwa bahati mbaya)
11 require(!Address.isContract(msg.sender), "Akaunti si EOA");
12 _;
13 }
Onyesha yote

Hii ndiyo sababu tulihitaji huduma za Anwani za OpenZeppelin.

1 /**
2 * @dev Kazi hii inaweza kuitwa bila data
3 * kuweka kiasi cha ETH kwenye salio la mpigaji simu kwenye L2.
4 * Kwa kuwa kazi ya kupokea haichukui data, kiasi
5 * chaguo-msingi cha kihafidhina hupelekwa L2.
6 */
7 receive() external payable onlyEOA {
8 _initiateETHDeposit(msg.sender, msg.sender, 200_000, bytes(""));
9 }
Onyesha yote

Kazi hii ipo kwa madhumuni ya majaribio. Kumbuka kuwa haionekani katika ufafanuzi wa kiolesura - si kwa matumizi ya kawaida.

1 /**
2 * @inheritdoc IL1StandardBridge
3 */
4 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable onlyEOA {
5 _initiateETHDeposit(msg.sender, msg.sender, _l2Gas, _data);
6 }
7
8 /**
9 * @inheritdoc IL1StandardBridge
10 */
11 function depositETHTo(
12 anwani _to,
13 uint32 _l2Gas,
14 bytes calldata _data
15 ) external payable {
16 _initiateETHDeposit(msg.sender, _to, _l2Gas, _data);
17 }
Onyesha yote

Kazi hizi mbili ni vifuniko vinavyozunguka _initiateETHDeposit, kazi inayoshughulikia uwekaji halisi wa ETH.

1 /**
2 * @dev Hufanya mantiki ya uwekaji kwa kuhifadhi ETH na kumjulisha Lango la ETH la L2 kuhusu
3 * uwekaji.
4 * @param _from Akaunti ya kuchukua uwekaji kutoka L1.
5 * @param _to Akaunti ya kumpa uwekaji kwenye L2.
6 * @param _l2Gas Kikomo cha gesi kinachohitajika kukamilisha uwekaji kwenye L2.
7 * @param _data Data ya hiari ya kupeleka kwa L2. Data hii hutolewa
8 * kama urahisi tu kwa mikataba ya nje. Mbali na kutekeleza urefu wa juu,
9 * mikataba hii haitoi dhamana yoyote kuhusu maudhui yake.
10 */
11 function _initiateETHDeposit(
12 anwani _from,
13 anwani _to,
14 uint32 _l2Gas,
15 bytes memory _data
16 ) internal {
17 // Jenga calldata kwa simu ya finalizeDeposit
18 bytes memory message = abi.encodeWithSelector(
Onyesha yote

Njia ambayo ujumbe wa vikoa tofauti hufanya kazi ni kwamba mkataba lengwa unaitwa na ujumbe kama calldata yake. Mikataba ya Solidity daima hutafsiri calldata zao kulingana na vipimo vya ABIopens in a new tab. Kazi ya Solidity abi.encodeWithSelectoropens in a new tab huunda calldata hiyo.

1 IL2ERC20Bridge.finalizeDeposit.selector,
2 anwani(0),
3 Lib_PredeployAddresses.OVM_ETH,
4 _from,
5 _to,
6 msg.value,
7 _data
8 );

Ujumbe hapa ni kupiga simu kazi ya finalizeDepositopens in a new tab na vigezo hivi:

KigezoThamaniMaana
_l1Tokenanwani(0)Thamani maalum ya kuwakilisha ETH (ambayo si tokeni ya ERC-20) kwenye L1
_l2TokenLib_PredeployAddresses.OVM_ETHMkataba wa L2 unaosimamia ETH kwenye Optimism, 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000 (mkataba huu ni kwa matumizi ya ndani ya Optimism pekee)
_kutoka_kutokaAnwani kwenye L1 inayotuma ETH
_kwa_kwaAnwani kwenye L2 inayopokea ETH
kiasimsg.valueKiasi cha wei kilichotumwa (ambacho tayari kimetumwa kwenye daraja)
_data_dataData ya ziada ya kuambatisha kwenye uwekaji
1 // Tuma calldata kwenye L2
2 // slither-disable-next-line reentrancy-events
3 sendCrossDomainMessage(l2TokenBridge, _l2Gas, ujumbe);

Tuma ujumbe kupitia mtumaji ujumbe wa vikoa tofauti.

1 // slither-disable-next-line reentrancy-events
2 emit ETHDepositInitiated(_from, _to, msg.value, _data);
3 }

Toa tukio la kujulisha programu yoyote iliyogatuliwa inayosikiliza kuhusu uhamisho huu.

1 /**
2 * @inheritdoc IL1ERC20Bridge
3 */
4 function depositERC20(
5 .
6 .
7 .
8 ) external virtual onlyEOA {
9 _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _l2Gas, _data);
10 }
11
12 /**
13 * @inheritdoc IL1ERC20Bridge
14 */
15 function depositERC20To(
16 .
17 .
18 .
19 ) external virtual {
20 _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _l2Gas, _data);
21 }
Onyesha yote

Kazi hizi mbili ni vifuniko vinavyozunguka _initiateERC20Deposit, kazi inayoshughulikia uwekaji halisi wa ERC-20.

1 /**
2 * @dev Hufanya mantiki ya uwekaji kwa kumjulisha mkataba wa Tokeni Iliyowekwa ya L2
3 * kuhusu uwekaji na kuita mshughulikiaji kufunga fedha za L1. (k.m., transferFrom)
4 *
5 * @param _l1Token Anwani ya ERC20 ya L1 tunayoweka
6 * @param _l2Token Anwani ya ERC20 ya L2 inayolingana na L1
7 * @param _from Akaunti ya kuchukua uwekaji kutoka L1
8 * @param _to Akaunti ya kumpa uwekaji kwenye L2
9 * @param _amount Kiasi cha ERC20 cha kuweka.
10 * @param _l2Gas Kikomo cha gesi kinachohitajika kukamilisha uwekaji kwenye L2.
11 * @param _data Data ya hiari ya kupeleka kwa L2. Data hii hutolewa
12 * kama urahisi tu kwa mikataba ya nje. Mbali na kutekeleza urefu wa juu,
13 * mikataba hii haitoi dhamana yoyote kuhusu maudhui yake.
14 */
15 function _initiateERC20Deposit(
16 anwani _l1Token,
17 anwani _l2Token,
18 anwani _from,
19 anwani _to,
20 uint256 _amount,
21 uint32 _l2Gas,
22 bytes calldata _data
23 ) internal {
Onyesha yote

Kazi hii inafanana na _initiateETHDeposit hapo juu, na tofauti chache muhimu. Tofauti ya kwanza ni kwamba kazi hii inapokea anwani za tokeni na kiasi cha kuhamisha kama vigezo. Kwa upande wa ETH simu kwa daraja tayari inajumuisha uhamisho wa rasilimali kwenda akaunti ya daraja (msg.value).

1 // Uwekaji unapoanzishwa kwenye L1, Daraja la L1 huhamisha fedha kwake lenyewe kwa ajili ya
2 // utoaji wa siku zijazo. safeTransferFrom pia huangalia ikiwa mkataba una msimbo, kwa hivyo hii itashindwa ikiwa
3 // _from ni EOA au anwani(0).
4 // slither-disable-next-line reentrancy-events, reentrancy-benign
5 IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount);

Uhamisho wa tokeni za ERC-20 hufuata mchakato tofauti na ETH:

  1. Mtumiaji (_from) anatoa ruhusa kwa daraja kuhamisha tokeni zinazofaa.
  2. Mtumiaji anapiga simu daraja na anwani ya mkataba wa tokeni, kiasi, n.k.
  3. Daraja linahamisha tokeni (kwake lenyewe) kama sehemu ya mchakato wa uwekaji.

Hatua ya kwanza inaweza kutokea katika muamala tofauti na mbili za mwisho. Hata hivyo, uendeshaji-wa-mbele si tatizo kwa sababu kazi mbili zinazoita _initiateERC20Deposit (depositERC20 na depositERC20To) huita kazi hii tu na msg.sender kama kigezo cha _from.

1 // Jenga calldata kwa _l2Token.finalizeDeposit(_to, _amount)
2 bytes memory message = abi.encodeWithSelector(
3 IL2ERC20Bridge.finalizeDeposit.selector,
4 _l1Token,
5 _l2Token,
6 _from,
7 _to,
8 _amount,
9 _data
10 );
11
12 // Tuma calldata kwenye L2
13 // slither-disable-next-line reentrancy-events, reentrancy-benign
14 sendCrossDomainMessage(l2TokenBridge, _l2Gas, ujumbe);
15
16 // slither-disable-next-line reentrancy-benign
17 deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] + _amount;
Onyesha yote

Ongeza kiasi cha tokeni zilizowekwa kwenye muundo wa data wa deposits. Kunaweza kuwa na anwani nyingi kwenye L2 zinazolingana na tokeni ileile ya L1 ERC-20, kwa hivyo haitoshi kutumia salio la daraja la tokeni ya L1 ERC-20 kufuatilia uwekaji.

1
2 // slither-disable-next-line reentrancy-events
3 emit ERC20DepositInitiated(_l1Token, _l2Token, _from, _to, _amount, _data);
4 }
5
6 /*************************
7 * Kazi za Mnyororo-Msalaba *
8 *************************/
9
10 /**
11 * @inheritdoc IL1StandardBridge
12 */
13 function finalizeETHWithdrawal(
14 anwani _from,
15 anwani _to,
16 uint256 _amount,
17 bytes calldata _data
Onyesha yote

Daraja la L2 hutuma ujumbe kwa mtumaji ujumbe wa vikoa tofauti wa L2 ambao husababisha mtumaji ujumbe wa vikoa tofauti wa L1 kupiga simu kazi hii (mara tu muamala unaokamilisha ujumbeopens in a new tab utakapowasilishwa kwenye L1, bila shaka).

1 ) external onlyFromCrossDomainAccount(l2TokenBridge) {

Hakikisha kuwa huu ni ujumbe halali, unaotoka kwa mtumaji ujumbe wa vikoa tofauti na unaotokana na daraja la tokeni la L2. Kazi hii inatumika kutoa ETH kutoka kwa daraja, kwa hivyo tunapaswa kuhakikisha inaitwa tu na mpigaji simu aliyeidhinishwa.

1 // slither-disable-next-line reentrancy-events
2 (bool success, ) = _to.call{ value: _amount }(new bytes(0));

Njia ya kuhamisha ETH ni kumpigia simu mpokeaji na kiasi cha wei katika msg.value.

1 require(success, "TransferHelper::safeTransferETH: Uhamisho wa ETH umeshindwa");
2
3 // slither-disable-next-line reentrancy-events
4 emit ETHWithdrawalFinalized(_from, _to, _amount, _data);

Toa tukio kuhusu utoaji.

1 }
2
3 /**
4 * @inheritdoc IL1ERC20Bridge
5 */
6 function finalizeERC20Withdrawal(
7 anwani _l1Token,
8 anwani _l2Token,
9 anwani _from,
10 anwani _to,
11 uint256 _amount,
12 bytes calldata _data
13 ) external onlyFromCrossDomainAccount(l2TokenBridge) {
Onyesha yote

Kazi hii inafanana na finalizeETHWithdrawal hapo juu, na mabadiliko muhimu kwa tokeni za ERC-20.

1 deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] - _amount;

Sasisha muundo wa data wa deposits.

1 // Utoaji unapokamilishwa kwenye L1, Daraja la L1 huhamisha fedha kwa mtoaji
2 // slither-disable-next-line reentrancy-events
3 IERC20(_l1Token).safeTransfer(_to, _amount);
4
5 // slither-disable-next-line reentrancy-events
6 emit ERC20WithdrawalFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);
7 }
8
9
10 /*****************************
11 * Ya muda - Kuhamisha ETH *
12 *****************************/
13
14 /**
15 * @dev Huongeza salio la ETH kwenye akaunti. Hii inakusudiwa kuruhusu ETH
16 * kuhamishwa kutoka lango la zamani hadi lango jipya.
17 * KUMBUKA: Hii imeachwa kwa sasisho moja tu ili tuweze kupokea ETH iliyohamishwa kutoka
18 * mkataba wa zamani
19 */
20 function donateETH() external payable {}
21}
Onyesha yote

Kulikuwa na utekelezaji wa awali wa daraja. Tulipohamia kutoka kwa utekelezaji huo hadi huu, tulilazimika kuhamisha rasilimali zote. Tokeni za ERC-20 zinaweza tu kuhamishwa. Hata hivyo, ili kuhamisha ETH kwa mkataba unahitaji idhini ya mkataba huo, ambayo ndiyo donateETH inatupatia.

Tokeni za ERC-20 kwenye L2

Ili tokeni ya ERC-20 iingie kwenye daraja la kawaida, inahitaji kuruhusu daraja la kawaida, na tu daraja la kawaida, kutoa tokeni. Hii ni muhimu kwa sababu madaraja yanahitaji kuhakikisha kuwa idadi ya tokeni zinazozunguka kwenye Optimism ni sawa na idadi ya tokeni zilizofungwa ndani ya mkataba wa daraja la L1. Ikiwa kuna tokeni nyingi sana kwenye L2 baadhi ya watumiaji wasingeweza kuvusha rasilimali zao kwa daraja kurudi L1. Badala ya daraja linaloaminika, kimsingi tungeunda upya benki ya hifadhi ya sehemuopens in a new tab. Ikiwa kuna tokeni nyingi sana kwenye L1, baadhi ya tokeni hizo zingebaki zimefungwa ndani ya mkataba wa daraja milele kwa sababu hakuna njia ya kuzitoa bila kuchoma tokeni za L2.

IL2StandardERC20

Kila tokeni ya ERC-20 kwenye L2 inayotumia daraja la kawaida inahitaji kutoa kiolesura hikiopens in a new tab, ambacho kina kazi na matukio ambayo daraja la kawaida linahitaji.

1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.9;
3
4import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

Kiolesura cha kawaida cha ERC-20opens in a new tab hakijumuishi kazi za mint na burn. Njia hizo hazihitajiki na kiwango cha ERC-20opens in a new tab, ambacho kinaacha taratibu za kuunda na kuharibu tokeni bila kubainishwa.

1import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

Kiolesura cha ERC-165opens in a new tab kinatumika kubainisha kazi ambazo mkataba unatoa. Unaweza kusoma kiwango hapaopens in a new tab.

1interface IL2StandardERC20 is IERC20, IERC165 {
2 function l1Token() external returns (anwani);

Kazi hii inatoa anwani ya tokeni ya L1 ambayo imevushwa kwa daraja kwenda mkataba huu. Kumbuka kuwa hatuna kazi kama hiyo kwa upande mwingine. Tunahitaji kuweza kuvusha kwa daraja tokeni yoyote ya L1, bila kujali kama msaada wa L2 ulipangwa wakati ilitekelezwa au la.

1
2 function mint(anwani _to, uint256 _amount) external;
3
4 function burn(anwani _from, uint256 _amount) external;
5
6 event Mint(anwani iliyoorodheshwa _account, uint256 _amount);
7 event Burn(anwani iliyoorodheshwa _account, uint256 _amount);
8}

Kazi na matukio ya kutoa (kuunda) na kuchoma (kuharibu) tokeni. Daraja linapaswa kuwa chombo pekee kinachoweza kuendesha kazi hizi ili kuhakikisha idadi ya tokeni ni sahihi (sawa na idadi ya tokeni zilizofungwa kwenye L1).

L2StandardERC20

Huu ndio utekelezaji wetu wa kiolesura cha IL2StandardERC20opens in a new tab. Isipokuwa unahitaji aina fulani ya mantiki maalum, unapaswa kutumia hii.

1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.9;
3
4import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

Mkataba wa OpenZeppelin ERC-20opens in a new tab. Optimism haiamini katika kuunda upya gurudumu, hasa wakati gurudumu limekaguliwa vizuri na linahitaji kuwa la kuaminika vya kutosha kushikilia rasilimali.

1import "./IL2StandardERC20.sol";
2
3contract L2StandardERC20 is IL2StandardERC20, ERC20 {
4 anwani public l1Token;
5 anwani public l2Bridge;

Hivi ni vigezo viwili vya ziada vya usanidi ambavyo tunahitaji na ERC-20 kawaida haihitaji.

1
2 /**
3 * @param _l2Bridge Anwani ya daraja la kawaida la L2.
4 * @param _l1Token Anwani ya tokeni ya L1 inayolingana.
5 * @param _name jina la ERC20.
6 * @param _symbol alama ya ERC20.
7 */
8 constructor(
9 anwani _l2Bridge,
10 anwani _l1Token,
11 string memory _name,
12 string memory _symbol
13 ) ERC20(_name, _symbol) {
14 l1Token = _l1Token;
15 l2Bridge = _l2Bridge;
16 }
Onyesha yote

Kwanza piga simu kijenzi cha mkataba tunachorithi kutoka (ERC20(_name, _symbol)) na kisha weka vigezo vyetu wenyewe.

1
2 modifier onlyL2Bridge() {
3 require(msg.sender == l2Bridge, "Ni Daraja la L2 pekee linaweza kutoa na kuchoma");
4 _;
5 }
6
7
8 // slither-disable-next-line external-function
9 function supportsInterface(bytes4 _interfaceId) public pure returns (bool) {
10 bytes4 firstSupportedInterface = bytes4(keccak256("supportsInterface(bytes4)")); // ERC165
11 bytes4 secondSupportedInterface = IL2StandardERC20.l1Token.selector ^
12 IL2StandardERC20.mint.selector ^
13 IL2StandardERC20.burn.selector;
14 return _interfaceId == firstSupportedInterface || _interfaceId == secondSupportedInterface;
15 }
Onyesha yote

Hivi ndivyo ERC-165opens in a new tab inavyofanya kazi. Kila kiolesura ni idadi ya kazi zinazoungwa mkono, na kinatambuliwa kama au ya kipekeeopens in a new tab ya viteuzi vya kazi vya ABIopens in a new tab vya kazi hizo.

Daraja la L2 linatumia ERC-165 kama ukaguzi wa usalama kuhakikisha kuwa mkataba wa ERC-20 ambao linatuma rasilimali ni IL2StandardERC20.

Kumbuka: Hakuna kinachozuia mkataba hasidi kutoa majibu ya uwongo kwa supportsInterface, kwa hivyo huu ni utaratibu wa ukaguzi wa usalama, sio utaratibu wa usalama.

1 // slither-disable-next-line external-function
2 function mint(anwani _to, uint256 _amount) public virtual onlyL2Bridge {
3 _mint(_to, _amount);
4
5 emit Mint(_to, _amount);
6 }
7
8 // slither-disable-next-line external-function
9 function burn(anwani _from, uint256 _amount) public virtual onlyL2Bridge {
10 _burn(_from, _amount);
11
12 emit Burn(_from, _amount);
13 }
14}
Onyesha yote

Ni daraja la L2 pekee linaloruhusiwa kutoa na kuchoma rasilimali.

_mint na _burn kwa kweli zimefafanuliwa katika mkataba wa OpenZeppelin ERC-20. Mkataba huo hauwaweki wazi nje, kwa sababu masharti ya kutoa na kuchoma tokeni ni tofauti kama idadi ya njia za kutumia ERC-20.

Msimbo wa Daraja la L2

Huu ni msimbo unaoendesha daraja kwenye Optimism. Chanzo cha mkataba huu kiko hapaopens in a new tab.

1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.9;
3
4/* Interface Imports */
5import { IL1StandardBridge } from "../../L1/messaging/IL1StandardBridge.sol";
6import { IL1ERC20Bridge } from "../../L1/messaging/IL1ERC20Bridge.sol";
7import { IL2ERC20Bridge } from "./IL2ERC20Bridge.sol";

Kiolesura cha IL2ERC20Bridgeopens in a new tab kinafanana sana na sawa ya L1 tuliyoona hapo juu. Kuna tofauti mbili muhimu:

  1. Kwenye L1 unaanzisha uwekaji na kukamilisha utoaji. Hapa unaanzisha utoaji na kukamilisha uwekaji.
  2. Kwenye L1 ni muhimu kutofautisha kati ya ETH na tokeni za ERC-20. Kwenye L2 tunaweza kutumia kazi zile zile kwa zote mbili kwa sababu ndani ya Optimism salio za ETH hushughulikiwa kama tokeni ya ERC-20 yenye anwani 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000opens in a new tab.
1/* Library Imports */
2import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
3import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";
4import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";
5
6/* Contract Imports */
7import { IL2StandardERC20 } from "../../standards/IL2StandardERC20.sol";
8
9/**
10 * @title L2StandardBridge
11 * @dev Daraja la Kawaida la L2 ni mkataba unaofanya kazi pamoja na daraja la Kawaida la L1
12 * kuwezesha mabadiliko ya ETH na ERC20 kati ya L1 na L2.
13 * Mkataba huu hufanya kazi kama mtoaji wa tokeni mpya inaposikia kuhusu uwekaji kwenye daraja la Kawaida la L1
14 *.
15 * Mkataba huu pia hufanya kazi kama mchomaji wa tokeni zilizokusudiwa kwa utoaji, ukijulisha daraja la L1
16 * kutoa fedha za L1.
17 */
18contract L2StandardBridge is IL2ERC20Bridge, CrossDomainEnabled {
19 /********************************
20 * Marejeleo ya Mkataba wa Nje *
21 ********************************/
22
23 anwani public l1TokenBridge;
Onyesha yote

Fuatilia anwani ya daraja la L1. Kumbuka kuwa tofauti na sawa ya L1, hapa tunahitaji kigezo hiki. Anwani ya daraja la L1 haijulikani mapema.

1
2 /***************
3 * Kijenzi *
4 ***************/
5
6 /**
7 * @param _l2CrossDomainMessenger Mtumaji ujumbe wa vikoa tofauti unaotumiwa na mkataba huu.
8 * @param _l1TokenBridge Anwani ya daraja la L1 iliyotumwa kwenye mnyororo mkuu.
9 */
10 constructor(anwani _l2CrossDomainMessenger, anwani _l1TokenBridge)
11 CrossDomainEnabled(_l2CrossDomainMessenger)
12 {
13 l1TokenBridge = _l1TokenBridge;
14 }
15
16 /***************
17 * Utoaji *
18 ***************/
19
20 /**
21 * @inheritdoc IL2ERC20Bridge
22 */
23 function withdraw(
24 anwani _l2Token,
25 uint256 _amount,
26 uint32 _l1Gas,
27 bytes calldata _data
28 ) external virtual {
29 _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _l1Gas, _data);
30 }
31
32 /**
33 * @inheritdoc IL2ERC20Bridge
34 */
35 function withdrawTo(
36 anwani _l2Token,
37 anwani _to,
38 uint256 _amount,
39 uint32 _l1Gas,
40 bytes calldata _data
41 ) external virtual {
42 _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _l1Gas, _data);
43 }
Onyesha yote

Kazi hizi mbili zinaanzisha utoaji. Kumbuka kuwa hakuna haja ya kubainisha anwani ya tokeni ya L1. Tokeni za L2 zinatarajiwa kutuambia anwani sawa ya L1.

1
2 /**
3 * @dev Hufanya mantiki ya utoaji kwa kuchoma tokeni na kumjulisha
4 * Lango la tokeni la L1 kuhusu utoaji.
5 * @param _l2Token Anwani ya tokeni ya L2 ambapo utoaji unaanzishwa.
6 * @param _from Akaunti ya kuchukua utoaji kutoka L2.
7 * @param _to Akaunti ya kumpa utoaji kwenye L1.
8 * @param _amount Kiasi cha tokeni ya kutoa.
9 * @param _l1Gas Haijatumiwa, lakini imejumuishwa kwa mazingatio ya utangamano wa mbele.
10 * @param _data Data ya hiari ya kupeleka kwa L1. Data hii hutolewa
11 * kama urahisi tu kwa mikataba ya nje. Mbali na kutekeleza urefu wa juu,
12 * mikataba hii haitoi dhamana yoyote kuhusu maudhui yake.
13 */
14 function _initiateWithdrawal(
15 anwani _l2Token,
16 anwani _from,
17 anwani _to,
18 uint256 _amount,
19 uint32 _l1Gas,
20 bytes calldata _data
21 ) internal {
22 // Utoaji unapoanzishwa, tunachoma fedha za mtoaji ili kuzuia matumizi ya L2
23 //.
24 // slither-disable-next-line reentrancy-events
25 IL2StandardERC20(_l2Token).burn(msg.sender, _amount);
Onyesha yote

Kumbuka kuwa hatutegemei kigezo cha _from bali msg.sender ambayo ni ngumu zaidi kughushi (haiwezekani, kwa kadiri ninavyojua).

1
2 // Jenga calldata kwa l1TokenBridge.finalizeERC20Withdrawal(_to, _amount)
3 // slither-disable-next-line reentrancy-events
4 anwani l1Token = IL2StandardERC20(_l2Token).l1Token();
5 bytes memory message;
6
7 if (_l2Token == Lib_PredeployAddresses.OVM_ETH) {

Kwenye L1 ni muhimu kutofautisha kati ya ETH na ERC-20.

1 ujumbe = abi.encodeWithSelector(
2 IL1StandardBridge.finalizeETHWithdrawal.selector,
3 _from,
4 _to,
5 _amount,
6 _data
7 );
8 } else {
9 ujumbe = abi.encodeWithSelector(
10 IL1ERC20Bridge.finalizeERC20Withdrawal.selector,
11 l1Token,
12 _l2Token,
13 _from,
14 _to,
15 _amount,
16 _data
17 );
18 }
19
20 // Tuma ujumbe hadi daraja la L1
21 // slither-disable-next-line reentrancy-events
22 sendCrossDomainMessage(l1TokenBridge, _l1Gas, ujumbe);
23
24 // slither-disable-next-line reentrancy-events
25 emit WithdrawalInitiated(l1Token, _l2Token, msg.sender, _to, _amount, _data);
26 }
27
28 /************************************
29 * Kazi ya Mnyororo-Msalaba: Uwekaji *
30 ************************************/
31
32 /**
33 * @inheritdoc IL2ERC20Bridge
34 */
35 function finalizeDeposit(
36 anwani _l1Token,
37 anwani _l2Token,
38 anwani _from,
39 anwani _to,
40 uint256 _amount,
41 bytes calldata _data
Onyesha yote

Kazi hii inaitwa na L1StandardBridge.

1 ) external virtual onlyFromCrossDomainAccount(l1TokenBridge) {

Hakikisha chanzo cha ujumbe ni halali. Hii ni muhimu kwa sababu kazi hii huita _mint na inaweza kutumika kutoa tokeni ambazo hazifunikwa na tokeni ambazo daraja linamiliki kwenye L1.

1 // Angalia tokeni lengwa inatii na
2 // hakikisha tokeni iliyowekwa kwenye L1 inalingana na uwakilishi wa tokeni iliyowekwa ya L2 hapa
3 if (
4 // slither-disable-next-line reentrancy-events
5 ERC165Checker.supportsInterface(_l2Token, 0x1d1d8b63) &&
6 _l1Token == IL2StandardERC20(_l2Token).l1Token()

Ukaguzi wa usalama:

  1. Kiolesura sahihi kinaauniwa
  2. Anwani ya L1 ya mkataba wa L2 ERC-20 inalingana na chanzo cha L1 cha tokeni
1 ) {
2 // Uwekaji unapokamilishwa, tunaweka kiasi sawa cha
3 // tokeni kwenye akaunti kwenye L2.
4 // slither-disable-next-line reentrancy-events
5 IL2StandardERC20(_l2Token).mint(_to, _amount);
6 // slither-disable-next-line reentrancy-events
7 emit DepositFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);

Ikiwa ukaguzi wa usalama utapita, kamilisha uwekaji:

  1. Toa tokeni
  2. Toa tukio linalofaa
1 } else {
2 // Labda tokeni ya L2 ambayo inawekwa-ndani haikubaliani kuhusu anwani sahihi
3 // ya tokeni yake ya L1, au haiauni kiolesura sahihi.
4 // Hii inapaswa kutokea tu ikiwa kuna tokeni hasidi ya L2, au ikiwa mtumiaji kwa namna fulani
5 // alibainisha anwani isiyo sahihi ya tokeni ya L2 ya kuweka.
6 // Katika hali yoyote, tunasimamisha mchakato hapa na kuunda ujumbe wa
7 // utoaji ili watumiaji waweze kutoa fedha zao katika baadhi ya kesi.
8 // Hakuna njia ya kuzuia mikataba ya tokeni hasidi kabisa, lakini hii inapunguza
9 // kosa la mtumiaji na kupunguza baadhi ya aina za tabia ya mkataba hasidi.
Onyesha yote

Ikiwa mtumiaji alifanya kosa linaloweza kugunduliwa kwa kutumia anwani isiyo sahihi ya tokeni ya L2, tunataka kughairi uwekaji na kurudisha tokeni kwenye L1. Njia pekee tunayoweza kufanya hivi kutoka L2 ni kutuma ujumbe ambao utalazimika kusubiri kipindi cha changamoto ya makosa, lakini hiyo ni bora zaidi kwa mtumiaji kuliko kupoteza tokeni kabisa.

1 bytes memory message = abi.encodeWithSelector(
2 IL1ERC20Bridge.finalizeERC20Withdrawal.selector,
3 _l1Token,
4 _l2Token,
5 _to, // tulibadilisha _to na _from hapa kurudisha uwekaji kwa mtumaji
6 _from,
7 _amount,
8 _data
9 );
10
11 // Tuma ujumbe hadi daraja la L1
12 // slither-disable-next-line reentrancy-events
13 sendCrossDomainMessage(l1TokenBridge, 0, ujumbe);
14 // slither-disable-next-line reentrancy-events
15 emit DepositFailed(_l1Token, _l2Token, _from, _to, _amount, _data);
16 }
17 }
18}
Onyesha yote

Hitimisho

Daraja la kawaida ndio utaratibu unaobadilika zaidi kwa uhamisho wa rasilimali. Hata hivyo, kwa sababu ni ya jumla sana si rahisi kila wakati kutumia utaratibu huu. Hasa kwa utoaji, watumiaji wengi wanapendelea kutumia madaraja ya watu wengineopens in a new tab ambayo hayasubiri kipindi cha changamoto na hayahitaji uthibitisho wa Merkle kukamilisha utoaji.

Madaraja haya kwa kawaida hufanya kazi kwa kuwa na rasilimali kwenye L1, ambazo hutoa mara moja kwa ada ndogo (mara nyingi chini ya gharama ya gesi kwa utoaji wa daraja la kawaida). Wakati daraja (au watu wanaoliendesha) linapotarajia kupungukiwa na rasilimali za L1 linahamisha rasilimali za kutosha kutoka L2. Kwa kuwa haya ni utoaji mkubwa sana, gharama ya utoaji inapunguzwa kwa kiasi kikubwa na ni asilimia ndogo zaidi.

Tunatumai makala hii imekusaidia kuelewa zaidi kuhusu jinsi safu 2 inavyofanya kazi, na jinsi ya kuandika msimbo wa Solidity ulio wazi na salama.

Tazama hapa kwa kazi zangu zaidiopens in a new tab.

Ukurasa ulihaririwa mwisho: 22 Oktoba 2025

Umesaidika na mafunzo haya?