Ruka hadi kwenye maudhui makuu

Mwongozo wa mkataba wa daraja la kawaida la Optimism

Solidity
daraja
tabaka la 2
Kati
Ori Pomerantz
30 Machi 2022
33 dakika za kusoma

Optimism (opens in a new tab) ni rollup ya optimistic. Mikusanyiko ya optimistic inaweza kuchakata miamala kwa bei ya chini sana kuliko Mtandao Mkuu wa Ethereum (pia inajulikana kama tabaka la 1 au l1) kwa sababu miamala inachakatwa tu na nodi chache, badala ya kila nodi kwenye mtandao. Wakati huo huo, data zote huandikwa kwenye l1 ili kila kitu kiweze kuthibitishwa na kujengwa upya kwa uhakikisho wote wa uadilifu na upatikanaji wa Mtandao Mkuu.

Ili kutumia rasilimali za l1 kwenye Optimism (au l2 nyingine yoyote), rasilimali zinahitaji kuvushwa. Njia moja ya kufanikisha hili ni kwa watumiaji kufunga rasilimali (ETH na tokeni za ERC-20 ndizo zinazojulikana zaidi) kwenye l1, na kupokea rasilimali sawa za kutumia kwenye l2. Hatimaye, yeyote anayebaki nazo anaweza kutaka kuzivusha kurudi kwenye l1. Wakati wa kufanya hivi, rasilimali huteketezwa kwenye l2 na kisha kutolewa tena kwa mtumiaji kwenye l1.

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

Mtiririko wa udhibiti

Daraja lina mitiririko miwili mikuu:

  • Uwekaji (kutoka l1 hadi l2)
  • Utoaji (kutoka l2 hadi l1)

Mtiririko wa uwekaji

Tabaka la 1

  1. Ikiwa unaweka ERC-20, mwekaji hulipa daraja kibali cha kutumia kiasi kinachowekwa
  2. Mwekaji huita daraja la l1 (depositERC20, depositERC20To, depositETH, au depositETHTo)
  3. Daraja la l1 huchukua umiliki wa rasilimali iliyovushwa
    • ETH: Rasilimali huhamishwa na mwekaji kama sehemu ya mwito
    • ERC-20: Rasilimali huhamishwa na daraja kwenda kwake lenyewe kwa kutumia kibali kilichotolewa na mwekaji
  4. Daraja la l1 hutumia utaratibu wa ujumbe wa kuvuka kikoa kuita finalizeDeposit kwenye daraja la l2

Tabaka la 2

  1. Daraja la l2 huthibitisha mwito kwa finalizeDeposit ni halali:
    • Umetoka kwenye mkataba wa ujumbe wa kuvuka kikoa
    • Ulitoka awali kwenye daraja la l1
  2. Daraja la l2 hukagua ikiwa mkataba wa tokeni ya ERC-20 kwenye l2 ndio sahihi:
  3. Ikiwa mkataba wa l2 ndio sahihi, uite ili kufua idadi inayofaa ya tokeni kwenye anwani inayofaa. Ikiwa sivyo, anza mchakato wa utoaji ili kuruhusu mtumiaji kudai tokeni kwenye l1.

Mtiririko wa utoaji

Tabaka la 2

  1. Mtoaji huita daraja la l2 (withdraw au withdrawTo)
  2. Daraja la l2 huteketeza idadi inayofaa ya tokeni zinazomilikiwa na msg.sender
  3. Daraja la l2 hutumia utaratibu wa ujumbe wa kuvuka kikoa kuita finalizeETHWithdrawal au finalizeERC20Withdrawal kwenye daraja la l1

Tabaka la 1

  1. Daraja la l1 huthibitisha mwito kwa finalizeETHWithdrawal au finalizeERC20Withdrawal ni halali:
    • Umetoka kwenye utaratibu wa ujumbe wa kuvuka kikoa
    • Ulitoka awali kwenye daraja la l2
  2. Daraja la l1 huhamisha rasilimali inayofaa (ETH au ERC-20) kwenye anwani inayofaa

Msimbo wa tabaka la 1

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

IL1ERC20Bridge

Kiolesura hiki kimefafanuliwa hapa (opens in a new tab). Inajumuisha vipengele na ufafanuzi unaohitajika kwa ajili ya kuvusha tokeni za ERC-20.

// SPDX-License-Identifier: MIT

Msimbo mwingi wa Optimism hutolewa chini ya leseni ya MIT (opens in a new tab).

pragma 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 litakapotolewa, hatujui ikiwa msimbo huu unaendana nalo au la.

Katika istilahi za daraja la Optimism uwekaji inamaanisha hamisho kutoka l1 hadi l2, na utoaji inamaanisha hamisho kutoka l2 hadi l1.

        address indexed _l1Token,
        address indexed _l2Token,

Katika hali nyingi anwani ya ERC-20 kwenye l1 sio sawa na anwani ya ERC-20 inayolingana kwenye l2. Unaweza kuona orodha ya anwani za tokeni hapa (opens in a new tab). Anwani yenye chainId 1 iko kwenye l1 (Mtandao Mkuu) na anwani yenye chainId 10 iko kwenye l2 (Optimism). Thamani nyingine mbili za chainId ni za mtandao wa majaribio wa Kovan (42) na mtandao wa majaribio wa Optimistic Kovan (69).

        address indexed _from,
        address _to,
        uint256 _amount,
        bytes _data
    );

Inawezekana kuongeza maelezo kwenye uhamishaji, ambapo huongezwa kwenye matukio yanayoripoti.

    event ERC20WithdrawalFinalized(
        address indexed _l1Token,
        address indexed _l2Token,
        address indexed _from,
        address _to,
        uint256 _amount,
        bytes _data
    );

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

Kipengele hiki hakihitajiki sana, kwa sababu kwenye l2 ni mkataba uliosambazwa mapema, kwa hivyo huwa kwenye anwani 0x4200000000000000000000000000000000000010. Iko hapa kwa ajili ya ulinganifu na daraja la l2, kwa sababu anwani ya daraja la l1 sio rahisi kujua.

Kigezo cha _l2Gas ni kiasi cha gesi ya l2 ambacho muamala unaruhusiwa kutumia. Hadi kikomo fulani (cha juu), hii ni bure (opens in a new tab), kwa hivyo isipokuwa mkataba wa ERC-20 ufanye kitu cha ajabu sana wakati wa kufua, haipaswi kuwa tatizo. Kipengele hiki kinashughulikia hali ya kawaida, ambapo mtumiaji huvusha rasilimali kwenye anwani sawa kwenye mnyororo wa vitalu tofauti.

Kipengele hiki kinakaribia kufanana na depositERC20, lakini kinakuruhusu kutuma ERC-20 kwenye anwani tofauti.

Utoaji (na jumbe zingine kutoka l2 hadi l1) katika Optimism ni mchakato wa hatua mbili:

  1. Muamala wa kuanzisha kwenye l2.
  2. Muamala wa kukamilisha au kudai kwenye l1. Muamala huu unahitaji kufanyika baada ya kipindi cha changamoto ya hitilafu (opens in a new tab) kwa muamala wa l2 kumalizika.

IL1StandardBridge

Kiolesura hiki kimefafanuliwa hapa (opens in a new tab). Faili hili lina ufafanuzi wa tukio na kipengele kwa ajili ya 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 uchakataji maalum na haziwezi kushughulikiwa na daraja la kawaida. Kwa njia hii daraja maalum linaloshughulikia tokeni kama hiyo linaweza kutekeleza IL1ERC20Bridge na lisilazimike pia kuvusha ETH.

Tukio hiki linakaribia kufanana na toleo la ERC-20 (ERC20DepositInitiated), isipokuwa bila anwani za tokeni za l1 na l2. Hali ni hiyo hiyo kwa matukio mengine na vipengele.

CrossDomainEnabled

Mkataba huu (opens in a new tab) unarithiwa na madaraja yote mawili (l1 na l2) ili kutuma jumbe kwenye tabaka lingine.

// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.9.0;

/* Uingizaji wa Violesura */
import { ICrossDomainMessenger } from "./ICrossDomainMessenger.sol";

Kiolesura hiki (opens in a new tab) huambia mkataba jinsi ya kutuma jumbe kwenye tabaka lingine, kwa kutumia mjumbe wa kuvuka kikoa. Mjumbe huyu wa kuvuka kikoa ni mfumo mwingine mzima, na unastahili makala yake yenyewe, ambayo natumai kuandika katika siku zijazo.

Kigezo kimoja ambacho mkataba unahitaji kujua, anwani ya mjumbe wa kuvuka kikoa kwenye tabaka hili. Kigezo hiki huwekwa mara moja, katika konstrukta, na hakibadiliki kamwe.

Ujumbe wa kuvuka kikoa unaweza kufikiwa na mkataba wowote kwenye mnyororo wa vitalu ambapo unaendeshwa (iwe Mtandao Mkuu wa Ethereum au Optimism). Lakini tunahitaji daraja kila upande kuamini tu jumbe fulani ikiwa zinatoka kwenye daraja la upande mwingine.

        require(
            msg.sender == address(getCrossDomainMessenger()),
            "OVM_XCHAIN: messenger contract unauthenticated"
        );

Jumbe tu kutoka kwa mjumbe anayefaa wa kuvuka kikoa (messenger, kama unavyoona hapa chini) ndizo zinazoweza kuaminika.


        require(
            getCrossDomainMessenger().xDomainMessageSender() == _sourceDomainAccount,
            "OVM_XCHAIN: wrong sender of cross-domain message"
        );

Njia ambayo mjumbe wa kuvuka kikoa hutoa anwani iliyotuma ujumbe na tabaka lingine ni kipengele cha .xDomainMessageSender() (opens in a new tab). Ilimradi inaitwa katika muamala ulioanzishwa na ujumbe inaweza kutoa taarifa hii.

Tunahitaji kuhakikisha kuwa ujumbe tuliopokea ulitoka kwenye daraja lingine.

Kipengele hiki hurejesha mjumbe wa kuvuka kikoa. Tunatumia kipengele badala ya kigezo messenger kuruhusu mikataba inayorithi kutoka kwa huu kutumia algoriti kubainisha mjumbe gani wa kuvuka kikoa wa kutumia.

Hatimaye, kipengele kinachotuma ujumbe kwenye tabaka lingine.

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

Slither (opens in a new tab) ni kichanganuzi tuli ambacho Optimism huendesha kwenye kila mkataba kutafuta udhaifu na matatizo mengine yanayoweza kutokea. Katika hali hii, mstari ufuatao husababisha udhaifu miwili:

  1. Matukio ya uingiaji upya (opens in a new tab)
  2. Uingiaji upya usio na madhara (opens in a new tab)
        getCrossDomainMessenger().sendMessage(_crossDomainTarget, _message, _gasLimit);
    }
}

Katika hali hii hatuna wasiwasi kuhusu uingiaji upya tunajua getCrossDomainMessenger() inarejesha anwani inayoaminika, hata kama Slither haina njia ya kujua hilo.

Mkataba wa daraja la l1

Msimbo wa chanzo wa mkataba huu uko hapa (opens in a new tab).

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

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

/* Uingizaji wa Violesura */
import { IL1StandardBridge } from "./IL1StandardBridge.sol";
import { IL1ERC20Bridge } from "./IL1ERC20Bridge.sol";

IL1ERC20Bridge na IL1StandardBridge zimeelezwa hapo juu.

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

Kiolesura hiki (opens in a new tab) kinaturuhusu kuunda jumbe za kudhibiti daraja la kawaida kwenye l2.

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

Kiolesura hiki (opens in a new tab) kinaturuhusu kudhibiti mikataba ya ERC-20. Unaweza kusoma zaidi kuihusu hapa.

/* Uingizaji wa Maktaba */
import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";

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

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

Lib_PredeployAddresses (opens in a new tab) ina anwani za mikataba ya l2 ambayo huwa na anwani sawa kila wakati. Hii inajumuisha daraja la kawaida kwenye l2.

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

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

Kumbuka kuwa hili sio suluhisho kamili, kwa sababu hakuna njia ya kutofautisha kati ya miito ya moja kwa moja na miito inayofanywa kutoka kwa konstrukta wa mkataba, lakini angalau hii inaturuhusu kutambua na kuzuia baadhi ya makosa ya kawaida ya watumiaji.

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

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

  1. Tengua
  2. Rejesha false

Kushughulikia hali zote mbili kungefanya msimbo wetu kuwa mgumu zaidi, kwa hivyo badala yake tunatumia SafeERC20 ya OpenZeppelin (opens in a new tab), ambayo inahakikisha kushindwa kote kunasababisha kutengua (opens in a new tab).

Mstari huu ndio jinsi tunavyobainisha kutumia kanga ya SafeERC20 kila wakati tunapotumia kiolesura cha IERC20.


    /********************************
     * Marejeleo ya Mikataba ya Nje *
     ********************************/

    address public l2TokenBridge;

Anwani ya L2StandardBridge.


    // Inachora tokeni ya tabaka la 1 (l1) kwa tokeni ya tabaka la 2 (l2) kwa salio la tokeni ya tabaka la 1 (l1) iliyowekwa
    mapping(address => mapping(address => uint256)) public deposits;

Uchoraji ramani (opens in a new tab) maradufu kama huu ndio njia unayofafanua safu chache za pande mbili (opens in a new tab). Thamani katika muundo huu wa data zinatambuliwa kama deposit[L1 token addr][L2 token addr]. Thamani chaguo-msingi ni sifuri. Seli tu ambazo zimewekwa kwa thamani tofauti ndizo zinazoandikwa kwenye hifadhi.


    /***************
     * Konstrukta *
     ***************/

    // Mkataba huu unaishi nyuma ya proksi, kwa hivyo vigezo vya konstrukta havitumiki.
    constructor() CrossDomainEnabled(address(0)) {}

Kutaka kuweza kuboresha mkataba huu bila kulazimika kunakili vigezo vyote kwenye hifadhi. Ili kufanya hivyo tunatumia Proxy (opens in a new tab), mkataba wa uwakilishi unaotumia delegatecall (opens in a new tab) kuhamisha miito kwenye mkataba tofauti ambao anwani yake imehifadhiwa na mkataba wa uwakilishi (unapoboresha unauambia uwakilishi ubadilishe anwani hiyo). Unapotumia delegatecall hifadhi inabaki kuwa hifadhi ya mkataba unaoita, kwa hivyo thamani za vigezo vyote vya hali ya mkataba haziathiriwi.

Athari moja ya muundo huu ni kwamba hifadhi ya mkataba ambao umeitwa na delegatecall haitumiki na kwa hivyo thamani za konstrukta zilizopitishwa kwake hazijalishi. Hii ndiyo sababu tunaweza kutoa thamani isiyo na maana kwa konstrukta wa CrossDomainEnabled. Pia ni sababu uanzishaji hapa chini umetenganishwa na konstrukta.

Jaribio hili la Slither (opens in a new tab) hutambua vipengele ambavyo haviitwi kutoka kwenye msimbo wa mkataba na kwa hivyo vinaweza kutangazwa kama external badala ya public. Gharama ya gesi ya vipengele vya external inaweza kuwa chini, kwa sababu vinaweza kutolewa na vigezo katika data za mwito. Vipengele vilivyotangazwa kama public lazima viweze kufikiwa kutoka ndani ya mkataba. Mikataba haiwezi kurekebisha data zake za mwito, kwa hivyo vigezo lazima viwe kwenye kumbukumbu. Wakati kipengele kama hicho kinaitwa kwa nje, ni muhimu kunakili data za mwito kwenye kumbukumbu, ambayo inagharimu gesi. Katika hali hii kipengele kinaitwa mara moja tu, kwa hivyo ukosefu wa ufanisi haujalishi kwetu.

    function initialize(address _l1messenger, address _l2TokenBridge) public {
        require(messenger == address(0), "Contract has already been initialized.");

Kipengele cha initialize kinapaswa kuitwa mara moja tu. Ikiwa anwani ya mjumbe wa kuvuka kikoa wa l1 au daraja la tokeni la l2 itabadilika, tunaunda mkataba mpya wa uwakilishi na daraja jipya linalouita. Hili haliwezekani kutokea isipokuwa wakati mfumo mzima unaboreshwa, tukio nadra sana.

Kumbuka kuwa kipengele hiki hakina utaratibu wowote unaozuia nani anaweza kukiita. Hii inamaanisha kuwa kinadharia mshambuliaji anaweza kusubiri hadi tusambaze mkataba wa uwakilishi na toleo la kwanza la daraja na kisha kutanguliza muamala (opens in a new tab) ili kufika kwenye kipengele cha initialize kabla ya mtumiaji halali kufanya hivyo. Lakini kuna njia mbili za kuzuia hili:

  1. Ikiwa mikataba inasambazwa sio moja kwa moja na EOA bali katika muamala ambao una mkataba mwingine unaoiunda (opens in a new tab) mchakato mzima unaweza kuwa wa atomiki, na kumaliza kabla ya muamala mwingine wowote kutekelezwa.
  2. Ikiwa mwito halali kwa initialize utashindwa inawezekana kila wakati kupuuza mkataba wa uwakilishi na daraja jipya lililoundwa na kuunda mapya.
        messenger = _l1messenger;
        l2TokenBridge = _l2TokenBridge;
    }

Hivi ndivyo vigezo viwili ambavyo daraja linahitaji kujua.

Hii ndiyo sababu tulihitaji zana za Address za OpenZeppelin.

Kipengele hiki kipo kwa madhumuni ya majaribio. Kumbuka kuwa haionekani katika ufafanuzi wa kiolesura - sio kwa matumizi ya kawaida.

Vipengele hivi viwili ni kanga zinazozunguka _initiateETHDeposit, kipengele kinachoshughulikia uwekaji halisi wa ETH.

Njia ambayo jumbe za kuvuka kikoa hufanya kazi ni kwamba mkataba lengwa unaitwa na ujumbe kama data zake za mwito. Mikataba ya Solidity kila wakati hutafsiri data zake za mwito kwa mujibu wa vipimo vya ABI (opens in a new tab). Kipengele cha Solidity abi.encodeWithSelector (opens in a new tab) huunda data hizo za mwito.

            IL2ERC20Bridge.finalizeDeposit.selector,
            address(0),
            Lib_PredeployAddresses.OVM_ETH,
            _from,
            _to,
            msg.value,
            _data
        );

Ujumbe hapa ni kuita kipengele cha finalizeDeposit (opens in a new tab) na vigezo hivi:

KigezoThamaniMaana
_l1Tokenaddress(0)Thamani maalum ya kusimama kwa ETH (ambayo sio 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)
_from_fromAnwani kwenye l1 inayotuma ETH
_to_toAnwani kwenye l2 inayopokea ETH
amountmsg.valueKiasi cha Wei kilichotumwa (ambacho tayari kimetumwa kwenye daraja)
_data_dataData ya ziada ya kuambatisha kwenye uwekaji
        // Tuma data za mwito kwenye tabaka la 2 (l2)
        // slither-disable-next-line reentrancy-events
        sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);

Tuma ujumbe kupitia mjumbe wa kuvuka kikoa.

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

Toa tukio ili kufahamisha programu tumizi iliyogatuliwa yoyote inayosikiliza kuhusu hamisho hili.

Vipengele hivi viwili ni kanga zinazozunguka _initiateERC20Deposit, kipengele kinachoshughulikia uwekaji halisi wa ERC-20.

Kipengele hiki kinafanana na _initiateETHDeposit hapo juu, na tofauti chache muhimu. Tofauti ya kwanza ni kwamba kipengele hiki hupokea anwani za tokeni na kiasi cha kuhamisha kama vigezo. Kwa upande wa ETH mwito kwa daraja tayari unajumuisha hamisho la rasilimali kwenye akaunti ya daraja (msg.value).

        // Wakati uwekaji unapoanzishwa kwenye tabaka la 1 (l1), daraja la tabaka la 1 (l1) linafanya hamisho la fedha kwake lenyewe kwa ajili ya
        // utoaji wa baadaye. safeTransferFrom pia inakagua ikiwa mkataba una msimbo, kwa hivyo hii itashindwa ikiwa
        // _from ni EOA au anwani(0).
        // slither-disable-next-line reentrancy-events, reentrancy-benign
        IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount);

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

  1. Mtumiaji (_from) hutoa kibali kwa daraja kuhamisha tokeni zinazofaa.
  2. Mtumiaji huita daraja na anwani ya mkataba wa tokeni, kiasi, n.k.
  3. Daraja huhamisha tokeni (kwake lenyewe) kama sehemu ya mchakato wa uwekaji.

Hatua ya kwanza inaweza kutokea katika muamala tofauti na mbili za mwisho. Hata hivyo, utangulizaji muamala sio tatizo kwa sababu vipengele viwili vinavyoita _initiateERC20Deposit (depositERC20 na depositERC20To) huita tu kipengele hiki na msg.sender kama kigezo cha _from.

Ongeza kiasi cha tokeni kilichowekwa kwenye muundo wa data wa deposits. Kunaweza kuwa na anwani nyingi kwenye l2 zinazolingana na tokeni sawa ya ERC-20 ya l1, kwa hivyo haitoshi kutumia salio la daraja la tokeni ya ERC-20 ya l1 kufuatilia uwekaji.

Daraja la l2 hutuma ujumbe kwa mjumbe wa kuvuka kikoa wa l2 ambao husababisha mjumbe wa kuvuka kikoa wa l1 kuita kipengele hiki (mara tu muamala unaokamilisha ujumbe (opens in a new tab) unapowasilishwa kwenye l1, bila shaka).

    ) external onlyFromCrossDomainAccount(l2TokenBridge) {

Hakikisha kuwa huu ni ujumbe halali, unaotoka kwa mjumbe wa kuvuka kikoa na kuanzia kwenye daraja la tokeni la l2. Kipengele hiki kinatumika kutoa ETH kutoka kwenye daraja, kwa hivyo tunapaswa kuhakikisha kinaitwa tu na mpigaji aliyeidhinishwa.

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

Njia ya kuhamisha ETH ni kuita mpokeaji na kiasi cha Wei katika msg.value.

        require(success, "TransferHelper::safeTransferETH: ETH transfer failed");

        // slither-disable-next-line reentrancy-events
        emit ETHWithdrawalFinalized(_from, _to, _amount, _data);

Toa tukio kuhusu utoaji.

Kipengele hiki kinafanana na finalizeETHWithdrawal hapo juu, na mabadiliko muhimu kwa tokeni za ERC-20.

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

Sasisha muundo wa data wa deposits.

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

Tokeni za ERC-20 kwenye l2

Ili tokeni ya ERC-20 iingie kwenye daraja la kawaida, inahitaji kuruhusu daraja la kawaida, na daraja la kawaida pekee, kufua 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 hawataweza kuvusha rasilimali zao kurudi kwenye l1. Badala ya daraja linaloaminika, kimsingi tungeunda upya benki ya akiba ya sehemu (opens 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 kuziachilia bila kuteketeza tokeni za l2.

IL2StandardERC20

Kila tokeni ya ERC-20 kwenye l2 inayotumia daraja la kawaida inahitaji kutoa kiolesura hiki (opens in a new tab), ambacho kina vipengele na matukio ambayo daraja la kawaida linahitaji.

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

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

Kiolesura cha kawaida cha ERC-20 (opens in a new tab) hakijumuishi vipengele vya mint na burn. Mbinu hizo hazihitajiki na kiwango cha ERC-20 (opens in a new tab), ambacho huacha bila kubainishwa taratibu za kuunda na kuharibu tokeni.

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

Kiolesura cha ERC-165 (opens in a new tab) kinatumika kubainisha ni vipengele gani mkataba unatoa. Unaweza kusoma kiwango hapa (opens in a new tab).

interface IL2StandardERC20 is IERC20, IERC165 {
    function l1Token() external returns (address);

Kipengele hiki hutoa anwani ya tokeni ya l1 ambayo imevushwa kwenye mkataba huu. Kumbuka kuwa hatuna kipengele sawa katika mwelekeo tofauti. Tunahitaji kuweza kuvusha tokeni yoyote ya l1, bila kujali kama usaidizi wa l2 ulipangwa wakati ilipotekelezwa au la.


    function mint(address _to, uint256 _amount) external;

    function burn(address _from, uint256 _amount) external;

    event Mint(address indexed _account, uint256 _amount);
    event Burn(address indexed _account, uint256 _amount);
}

Vipengele na matukio ya kufua (kuunda) na kuteketeza (kuharibu) tokeni. Daraja linapaswa kuwa chombo pekee kinachoweza kuendesha vipengele hivi ili kuhakikisha idadi ya tokeni ni sahihi (sawa na idadi ya tokeni zilizofungwa kwenye l1).

L2StandardERC20

Huu ni utekelezaji wetu wa kiolesura cha IL2StandardERC20 (opens in a new tab). Isipokuwa unahitaji aina fulani ya mantiki maalum, unapaswa kutumia hii.

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

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

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

import "./IL2StandardERC20.sol";

contract L2StandardERC20 is IL2StandardERC20, ERC20 {
    address public l1Token;
    address public l2Bridge;

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

Kwanza ita konstrukta kwa mkataba tunaorithi kutoka (ERC20(_name, _symbol)) na kisha weka vigezo vyetu wenyewe.

Hivi ndivyo ERC-165 (opens in a new tab) inavyofanya kazi. Kila kiolesura ni idadi ya vipengele vinavyosaidiwa, na kinatambuliwa kama au ya kipekee (opens in a new tab) ya viteuzi vya kipengele cha ABI (opens in a new tab) vya vipengele hivyo.

Daraja la l2 hutumia ERC-165 kama ukaguzi wa uhalali ili kuhakikisha kuwa mkataba wa ERC-20 ambao inatuma rasilimali ni IL2StandardERC20.

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

Daraja la l2 pekee ndilo linaloruhusiwa kufua na kuteketeza rasilimali.

_mint na _burn kwa kweli zimefafanuliwa katika mkataba wa ERC-20 wa OpenZeppelin. Mkataba huo hauzionyeshi kwa nje, kwa sababu masharti ya kufua na kuteketeza tokeni yanatofautiana 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 hapa (opens in a new tab).

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

/* Uingizaji wa Violesura */
import { IL1StandardBridge } from "../../L1/messaging/IL1StandardBridge.sol";
import { IL1ERC20Bridge } from "../../L1/messaging/IL1ERC20Bridge.sol";
import { IL2ERC20Bridge } from "./IL2ERC20Bridge.sol";

Kiolesura cha IL2ERC20Bridge (opens in a new tab) kinafanana sana na sawa na 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 vipengele sawa kwa zote mbili kwa sababu ndani salio la ETH kwenye Optimism linashughulikiwa kama tokeni ya ERC-20 yenye anwani 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000 (opens in a new tab).

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

Vipengele hivi viwili huanzisha utoaji. Kumbuka kuwa hakuna haja ya kubainisha anwani ya tokeni ya l1. Tokeni za l2 zinatarajiwa kutuambia anwani sawa ya l1.

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


        // Jenga data za mwito kwa ajili ya l1TokenBridge.finalizeERC20Withdrawal(_to, _amount)
        // slither-disable-next-line reentrancy-events
        address l1Token = IL2StandardERC20(_l2Token).l1Token();
        bytes memory message;

        if (_l2Token == Lib_PredeployAddresses.OVM_ETH) {

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

Kipengele hiki kinaitwa na L1StandardBridge.

    ) external virtual onlyFromCrossDomainAccount(l1TokenBridge) {

Hakikisha chanzo cha ujumbe ni halali. Hii ni muhimu kwa sababu kipengele hiki huita _mint na kinaweza kutumika kutoa tokeni ambazo hazijafunikwa na tokeni ambazo daraja linamiliki kwenye l1.

        // Kagua tokeni lengwa inatii na
        // thibitisha tokeni iliyowekwa kwenye tabaka la 1 (l1) inalingana na uwakilishi wa tokeni iliyowekwa ya tabaka la 2 (l2) hapa
        if (
            // slither-disable-next-line reentrancy-events
            ERC165Checker.supportsInterface(_l2Token, 0x1d1d8b63) &&
            _l1Token == IL2StandardERC20(_l2Token).l1Token()

Ukaguzi wa uhalali:

  1. Kiolesura sahihi kinasaidiwa
  2. Anwani ya l1 ya mkataba wa ERC-20 wa l2 inalingana na chanzo cha l1 cha tokeni
        ) {
            // Wakati uwekaji unapokamilishwa, tunaweka kwenye akaunti kwenye tabaka la 2 (l2) kiasi kile kile cha
            // tokeni.
            // slither-disable-next-line reentrancy-events
            IL2StandardERC20(_l2Token).mint(_to, _amount);
            // slither-disable-next-line reentrancy-events
            emit DepositFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);

Ikiwa ukaguzi wa uhalali utafaulu, kamilisha uwekaji:

  1. Fua tokeni
  2. Toa tukio linalofaa

Ikiwa mtumiaji alifanya kosa linaloweza kutambulika kwa kutumia anwani mbaya 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 hitilafu, lakini hiyo ni bora zaidi kwa mtumiaji kuliko kupoteza tokeni kabisa.

Hitimisho

Daraja la kawaida ndio utaratibu unaobadilika zaidi kwa uhamishaji wa rasilimali. Hata hivyo, kwa sababu ni la jumla sana sio kila wakati utaratibu rahisi zaidi kutumia. Hasa kwa utoaji, watumiaji wengi wanapendelea kutumia madaraja ya wahusika wengine (opens in a new tab) ambayo hayasubiri kipindi cha changamoto na hayahitaji ushahidi wa Merkle kukamilisha utoaji.

Madaraja haya kwa kawaida hufanya kazi kwa kuwa na rasilimali kwenye l1, ambayo hutoa mara moja kwa ada ndogo (mara nyingi chini ya gharama ya gesi kwa utoaji wa daraja la kawaida). Wakati daraja (au watu wanaoliendesha) linatarajia kupungukiwa na rasilimali za l1 huhamisha rasilimali za kutosha kutoka l2. Kwa kuwa haya ni matoaji makubwa sana, gharama ya utoaji inagawanywa kwa kiasi kikubwa na ni asilimia ndogo sana.

Tunatumai makala haya yamekusaidia kuelewa zaidi kuhusu jinsi tabaka la 2 linavyofanya kazi, na jinsi ya kuandika msimbo wa Solidity ulio wazi na salama.

Tazama hapa kwa kazi zangu zaidi (opens in a new tab).