Ana içeriğe geç

Optimism standart köprü sözleşmesine genel bakış

solidityköprükatman 2
Gelişmiş
Ori Pomerantz
30 Mart 2022
29 dakikalık okuma minute read

Optimism(opens in a new tab), bir İyimser Toplamadır. İyimser toplamalar, işlemleri Ethereum Mainnet'ten (katman 1 veya K1 olarak da bilinir) çok daha düşük bir fiyata işleyebilir çünkü işlemler ağdaki her düğüm yerine yalnızca birkaç düğüm tarafından işlenir. Aynı zamanda, verilerin tümü K1'e yazılır, böylece her şey kanıtlanabilir ve Mainnet'in tüm bütünlük ve kullanılabilirlik garantileriyle yeniden yapılandırılabilir.

Optimism'de (veya başka herhangi bir K2'de) K1 varlıklarını kullanmak için varlıkların köprülenmesi gerekir. Kullanıcıların varlıkları (ETH ve ERC-20 token'ları en yaygın olanlardır) L1'de kilitlemesi ve L2'de eş değer varlıklar alması bunu başarmanın yollarından biridir. Nihayetinde bu varlıkları alan kişiler bunları tekrar K1'e köprülemek isteyebilir. Bunu yaparken, varlıklar K2'de yakılır ve ardından K1'de kullanıcıya geri verilir.

Optimism standart köprüsü(opens in a new tab) bu şekilde çalışır. Bu makalede, nasıl çalıştığını görmek için bu köprünün kaynak kodunu gözden geçireceğiz ve onu iyi yazılmış bir Solidity kodu örneği olarak inceleyeceğiz.

Kontrol akışları

Köprünün iki ana akışı vardır:

  • Yatırma (K1'den K2'ye)
  • Çekme (K2'den K1'e)

Yatırma akışı

Katman 1

  1. Bir ERC-20 yatırılıyorsa, yatırımcı köprüye yatırılan tutarı harcaması için bir ödenek verir
  2. Yatıran, K1 köprüsünü (depositERC20, depositERC20To, depositETH veya depositETHTo) çağırır
  3. K1 köprüsü, köprülenen varlığın sahibi olur
    • ETH: Varlık, çağrının bir parçası olarak yatıran tarafından aktarılır
    • ERC-20: Varlık, yatıran tarafından sağlanan ödenek kullanılarak köprü tarafından kendisine devredilir
  4. K1 köprüsü, K2 köprüsünde finalizeDeposit'i çağırmak için etki alanları arası mesaj mekanizmasını kullanır

Katman 2

  1. Katman 2 köprüsü, finalizeDeposit çağrısının meşru olduğunu doğrular:
    • Etki alanları arası mesaj sözleşmesinden geldi
    • Aslen K1'deki köprüdendi
  2. K2 köprüsü, K2 üzerindeki ERC-20 token sözleşmesinin doğru olup olmadığını kontrol eder:
  3. K2 sözleşmesi doğruysa, uygun adrese uygun sayıda token basması için onu çağırın. Değilse, kullanıcının K1'deki token'ları talep etmesine izin vermek için bir para çekme işlemi başlatın.

Çekme akışı

Katman 2

  1. Çeken kişi K2 köprüsünü çağırır (draw veya withdrawTo)
  2. K2 köprüsü, msg.sender'a ait uygun sayıda token'ı yakar
  3. K2 köprüsü, K1 köprüsünde finalizeETHWithdrawal veya finalizeERC20Withdrawal'ı çağırmak için etki alanları arası mesaj mekanizmasını kullanır

Katman 1

  1. K1 köprüsü, finalizeETHWithdrawal veya finalizeERC20Withdrawal çağrısının meşru olduğunu doğrular:
    • Etki alanları arası mesaj mekanizmasından geldi
    • Aslen K2'deki köprüdendi
  2. K1 köprüsü, uygun varlığı (ETH veya ERC-20) uygun adrese aktarır

Katman 1 kodu

Bu, Ethereum Mainnet K1 üzerinde çalışan koddur.

IL1ERC20Bridge

Bu arayüz burada tanımlanmıştır(opens in a new tab). ERC-20 token'larını köprülemek için gereken fonksiyonları ve tanımları içerir.

1// SPDX-License-Identifier: MIT
Kopyala

Optimism'in kodunun çoğu MIT lisansı altında yayınlandı(opens in a new tab).

1pragma solidity >0.5.0 <0.9.0;
Kopyala

Yazım sırasında Solidity'nin en son sürümü 0.8.12'dir. Sürüm 0.9.0 yayınlanana kadar, bu kodun onunla uyumlu olup olmadığını bilemeyiz.

1/**
2 * @title IL1ERC20Bridge
3 */
4interface IL1ERC20Bridge {
5 /**********
6 * Events *
7 **********/
8
9 event ERC20DepositInitiated(
Tümünü göster
Kopyala

Optimism köprü terminolojisinde yatırmak, K1'den K2'ye transfer anlamına, çekmek K2'den K1'e transfer anlamına gelir.

1 address indexed _l1Token,
2 address indexed _l2Token,
Kopyala

Çoğu durumda K1 üzerindeki bir ERC-20 adresi aynı ERC-20'nin K2'deki adresinin aynısı değildir. Burada token adreslerinin bir listesini görebilirsiniz(opens in a new tab). chainId'si 1 olan adres K1'de (Mainnet) ve chainId'si 10 olan ise K2'de (Optimism). Diğer iki chainId değerleri ise Kovan test ağı (42) ve Optimistic Kovan test ağı içindir (69).

1 address indexed _from,
2 address _to,
3 uint256 _amount,
4 bytes _data
5 );
Kopyala

Transferlere notlar eklemek mümkündür, bu durumda notlar onları rapor eden olaylara eklenirler.

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

Aynı köprü sözleşmesi her yönde transferleri idare eder. K1 köprüsünün durumunda ise bu, yatırımların başlatımı ve çekimlerin sonlandırılması anlamına gelir.

1
2 /********************
3 * Public Functions *
4 ********************/
5
6 /**
7 * @dev get the address of the corresponding L2 bridge contract.
8 * @return Address of the corresponding L2 bridge contract.
9 */
10 function l2TokenBridge() external returns (address);
Tümünü göster
Kopyala

Bu fonksiyona pek gerek duyulmaz, çünkü K2 üzerinde önden dağıtılmış bir sözleşmedir, yani her zaman 0x4200000000000000000000000000000000000010 adresindedir. Burada K2 köprüsüyle simetri için bulunur, çünkü K1 köprüsünün adresinin bilinmesi önemlidir.

1 /**
2 * @dev deposit an amount of the ERC20 to the caller's balance on L2.
3 * @param _l1Token Address of the L1 ERC20 we are depositing
4 * @param _l2Token Address of the L1 respective L2 ERC20
5 * @param _amount Amount of the ERC20 to deposit
6 * @param _l2Gas Gas limit required to complete the deposit on L2.
7 * @param _data Optional data to forward to L2. This data is provided
8 * solely as a convenience for external contracts. Aside from enforcing a maximum
9 * length, these contracts provide no guarantees about its content.
10 */
11 function depositERC20(
12 address _l1Token,
13 address _l2Token,
14 uint256 _amount,
15 uint32 _l2Gas,
16 bytes calldata _data
17 ) external;
Tümünü göster
Kopyala

_l2Gas parametresi işlemin harcamasına izin verilen K2 gaz miktarıdır. Belirli (yüksek) bir limite kadar bu ücretsizdir(opens in a new tab), yani basım esnasında ERC-20 sözleşmesi gerçekten garip bir şey yapmazsa bu bir sorun olmamalı. Bu fonksiyon, yaygın bir senaryo olan kullanıcının farklı bir blok zincirindeki aynı adrese varlık köprülemesinin üstesinden gelir.

1 /**
2 * @dev deposit an amount of ERC20 to a recipient's balance on L2.
3 * @param _l1Token Address of the L1 ERC20 we are depositing
4 * @param _l2Token Address of the L1 respective L2 ERC20
5 * @param _to L2 address to credit the withdrawal to.
6 * @param _amount Amount of the ERC20 to deposit.
7 * @param _l2Gas Gas limit required to complete the deposit on L2.
8 * @param _data Optional data to forward to L2. This data is provided
9 * solely as a convenience for external contracts. Aside from enforcing a maximum
10 * length, these contracts provide no guarantees about its content.
11 */
12 function depositERC20To(
13 address _l1Token,
14 address _l2Token,
15 address _to,
16 uint256 _amount,
17 uint32 _l2Gas,
18 bytes calldata _data
19 ) external;
Tümünü göster
Kopyala

Bu fonksiyon neredeyse depositERC20 ile özdeştir, ama farklı bir adrese ERC-20 yollamanıza izin verir.

1 /*************************
2 * Cross-chain Functions *
3 *************************/
4
5 /**
6 * @dev Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the
7 * L1 ERC20 token.
8 * This call will fail if the initialized withdrawal from L2 has not been finalized.
9 *
10 * @param _l1Token Address of L1 token to finalizeWithdrawal for.
11 * @param _l2Token Address of L2 token where withdrawal was initiated.
12 * @param _from L2 address initiating the transfer.
13 * @param _to L1 address to credit the withdrawal to.
14 * @param _amount Amount of the ERC20 to deposit.
15 * @param _data Data provided by the sender on L2. This data is provided
16 * solely as a convenience for external contracts. Aside from enforcing a maximum
17 * length, these contracts provide no guarantees about its content.
18 */
19 function finalizeERC20Withdrawal(
20 address _l1Token,
21 address _l2Token,
22 address _from,
23 address _to,
24 uint256 _amount,
25 bytes calldata _data
26 ) external;
27}
Tümünü göster
Kopyala

Optimism'de çekme işlemleri (ve K2'den K1'e diğer tüm mesajlar) iki adımlı bir süreçtir:

  1. K2 üzerinde başlatıcı işlem.
  2. K1 üzerinde sonlandırıcı veya talep eden bir işlem. Bu işlemin, biten K2 işlemi için olan hata meydan okuması süresinden(opens in a new tab) sonra gerçekleşmesi gerekir.

IL1StandardBridge

Bu arayüz burada tanımlanmıştır(opens in a new tab). Bu dosya ETH için olay ve fonksiyon tanımlamalarını içerir. Bu tanımlamalar ERC-20 için yukarıdaki IL1ERC20Bridge'de belirlenenlere gayet benzerler.

Bazı ERC-20 token'ları özel işlem gerektirdiği ve standart köprü tarafından idare edilemedikleri için köprü arayüzü iki dosyaya bölünmüştür. Bu yolla bu tarz bir token'ı idare eden özel köprü IL1ERC20Bridge'i örnek alabilir ve ETH köprülemek zorunda kalmaz.

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 * Events *
12 **********/
13 event ETHDepositInitiated(
14 address indexed _from,
15 address indexed _to,
16 uint256 _amount,
17 bytes _data
18 );
Tümünü göster
Kopyala

Bu olay ERC-20 versiyonunun (ERC20DepositInitiated) neredeyse aynısıdır, tek fark K1 ve K2 token adreslerinin olmamasıdır. Aynısı diğer olaylar ve fonksiyonlar için de geçerlidir.

1 event ETHWithdrawalFinalized(
2 .
3 .
4 .
5 );
6
7 /********************
8 * Public Functions *
9 ********************/
10
11 /**
12 * @dev Deposit an amount of the ETH to the caller's balance on L2.
13 .
14 .
15 .
16 */
17 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable;
18
19 /**
20 * @dev Deposit an amount of ETH to a recipient's balance on L2.
21 .
22 .
23 .
24 */
25 function depositETHTo(
26 address _to,
27 uint32 _l2Gas,
28 bytes calldata _data
29 ) external payable;
30
31 /*************************
32 * Cross-chain Functions *
33 *************************/
34
35 /**
36 * @dev Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the
37 * L1 ETH token. Since only the xDomainMessenger can call this function, it will never be called
38 * before the withdrawal is finalized.
39 .
40 .
41 .
42 */
43 function finalizeETHWithdrawal(
44 address _from,
45 address _to,
46 uint256 _amount,
47 bytes calldata _data
48 ) external;
49}
Tümünü göster
Kopyala

CrossDomainEnabled

Bu sözleşme(opens in a new tab) iki köprü tarafından da (K1 ve K2) diğer katmana mesajlar göndermek için kalıtım ile alınmıştır.

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

Bu arayüz(opens in a new tab), sözleşmeye alan adları arası mesajcısını kullanarak nasıl diğer katmana mesaj göndereceğini anlatır. Alan adları arası mesajcısı tamamen başka bir sistemdir ve gelecekte yazmayı umduğum kendine özel bir makaleyi hak ediyor.

1/**
2 * @title CrossDomainEnabled
3 * @dev Helper contract for contracts performing cross-domain communications
4 *
5 * Compiler used: defined by inheriting contract
6 */
7contract CrossDomainEnabled {
8 /*************
9 * Variables *
10 *************/
11
12 // Diğer etki alanından mesaj göndermek ve almak için kullanılan Messenger sözleşmesi.
13 address public messenger;
14
15 /***************
16 * Constructor *
17 ***************/
18
19 /**
20 * @param _messenger Address of the CrossDomainMessenger on the current layer.
21 */
22 constructor(address _messenger) {
23 messenger = _messenger;
24 }
Tümünü göster
Kopyala

Sözleşmenin bilmesi gereken bir parametre, bu katmandaki alan adları arası mesajcısının adresidir. Bu parametre bir defa yapıcıda belirlenir ve asla değişmez.

1
2 /**********************
3 * Function Modifiers *
4 **********************/
5
6 /**
7 * Enforces that the modified function is only callable by a specific cross-domain account.
8 * @param _sourceDomainAccount The only account on the originating domain which is
9 * authenticated to call this function.
10 */
11 modifier onlyFromCrossDomainAccount(address _sourceDomainAccount) {
Tümünü göster
Kopyala

Alan adları arası mesajlaşması, çalıştığı blok zincirindeki (ya Ethereum ana ağı ya da Optimism) herhangi bir sözleşmeden erişilebilirdir. Ancak belirli mesajlara sadece öbür taraftaki köprüden gelirse güvenmek için iki tarafta da köprüye ihtiyacımız vardır.

1 require(
2 msg.sender == address(getCrossDomainMessenger()),
3 "OVM_XCHAIN: messenger contract unauthenticated"
4 );
Kopyala

Sadece uygun alan adları arası mesajcısından (messenger, aşağıda gördüğünüz üzere) gelen mesajlara güvenilebilir.

1
2 require(
3 getCrossDomainMessenger().xDomainMessageSender() == _sourceDomainAccount,
4 "OVM_XCHAIN: wrong sender of cross-domain message"
5 );
Kopyala

Alan adları arası mesajcısının diğer katman ile mesaj gönderen adresi sağlama yolu, .xDomainMessageSender() fonksiyonudur(opens in a new tab). Mesaj tarafından başlatılan işlemde çağrıldığı sürece bu bilgiyi sağlayabilir.

Aldığımız mesajın öbür köprüden geldiğinden emin olmalıyız.

1
2 _;
3 }
4
5 /**********************
6 * Internal Functions *
7 **********************/
8
9 /**
10 * Gets the messenger, usually from storage. This function is exposed in case a child contract
11 * needs to override.
12 * @return The address of the cross-domain messenger contract which should be used.
13 */
14 function getCrossDomainMessenger() internal virtual returns (ICrossDomainMessenger) {
15 return ICrossDomainMessenger(messenger);
16 }
Tümünü göster
Kopyala

Bu fonksiyon alan adları arası mesajcısını döndürür. Bundan kalıtım ile alan sözleşmelerin, hangi alan adı arası mesajcının kullanılacağını belirtmeleri için bir algoritma kullanmasına izin vermek için messenger değişkeni yerine bir fonksiyon kullanıyoruz.

1
2 /**
3 * Sends a message to an account on another domain
4 * @param _crossDomainTarget The intended recipient on the destination domain
5 * @param _message The data to send to the target (usually calldata to a function with
6 * `onlyFromCrossDomainAccount()`)
7 * @param _gasLimit The gasLimit for the receipt of the message on the target domain.
8 */
9 function sendCrossDomainMessage(
10 address _crossDomainTarget,
11 uint32 _gasLimit,
12 bytes memory _message
Tümünü göster
Kopyala

Son olarak, fonksiyon diğer katmana bir mesaj gönderir.

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

Slither(opens in a new tab) Optimism'in güvenlik açığı ve diğer potansiyel problemleri bulmak için her sözleşmede çalıştırdığı bir statik analizcidir. Bu durumda, sıradaki satır iki açığı tetikler:

  1. Yeniden giriş olayları(opens in a new tab)
  2. İyi huylu yeniden giriş(opens in a new tab)
1 getCrossDomainMessenger().sendMessage(_crossDomainTarget, _message, _gasLimit);
2 }
3}
Kopyala

Bu durumda yeniden giriş hakkında kaygılı değiliz, Slither'ın bunu bilmesi mümkün olmasa bile getCrossDomainMessenger() öğesinin güvenilir bir adres döndürdüğünü biliyoruz.

K1 köprü sözleşmesi

Bu sözleşmenin kaynak kodu buradadır(opens in a new tab).

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

Arayüzler diğer sözleşmelerin bir parçası olabilirler, yani geniş aralıkta Solidity sürümlerini desteklemeleri gerekir. Ancak köprü bizim sözleşmemizdir, ve hangi Solidity sürümünü kullandığı hakkında katı davranabiliriz.

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

IL1ERC20Bridge ve IL1StandardBridge yukarıda açıklanmıştır.

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

Bu arayüz(opens in a new tab) K2 üzerindeki standart köprüyü kontrol etmek için mesajlar oluşturmamızı sağlar.

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

Bu arayüz(opens in a new tab) ERC-20 sözleşmelerini kontrol etmemizi sağlar. Onun hakkında dahasını burada okuyabilirsiniz.

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

Yukarıda açıklandığı gibi, bu sözleşme katmanlar arası mesajlaşma için kullanılır.

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

Lib_PredeployAddresses(opens in a new tab) her zaman aynı adrese sahip olan K2 sözleşmelerinin adreslerine sahiptir. Buna K2 üzerindeki standart köprü de dahildir.

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

OpenZeppelin'in Address yardımcı araçları(opens in a new tab). Sözleşme adresleri ve harici olarak sahiplenilmiş hesapların (EOA) ayrımını yapmak için kullanılır.

Bunun mükemmel bir çözüm olmadığını unutmayın. Bir sözleşmenin yapıcısı tarafından yapılan çağrılar ve doğrudan çağrıların ayrımını yapmanın bir yolu yoktur ama bu en azından bazı yaygın kullanıcı hatalarını tespit etmemizi ve önlememizi sağlar.

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

ERC-20 standardı(opens in a new tab) biz sözleşmenin hata bildirmesi için iki yolu destekler:

  1. Geri döndür
  2. false döndürme

Her iki durumu da ele almak kodumuzu daha karmaşık hâle getirecektir, bu nedenle OpenZeppelin'in SafeERC20(opens in a new tab)'sini kullanıyoruz, bu da tüm hataların bir geri dönüşle sonuçlanmasını(opens in a new tab) sağlar.

1/**
2 * @title L1StandardBridge
3 * @dev The L1 ETH and ERC20 Bridge is a contract which stores deposited L1 funds and standard
4 * tokens that are in use on L2. It synchronizes a corresponding L2 Bridge, informing it of deposits
5 * and listening to it for newly finalized withdrawals.
6 *
7 */
8contract L1StandardBridge is IL1StandardBridge, CrossDomainEnabled {
9 using SafeERC20 for IERC20;
Tümünü göster
Kopyala

Bu satır IERC20 arayüzünü her kullandığımızda SafeERC20 paketleyicisini kullanmasını belirtme yöntemimizdir.

1
2 /********************************
3 * External Contract References *
4 ********************************/
5
6 address public l2TokenBridge;
Kopyala

L2StandardBridge adresi.

1
2 // Maps L1 token to L2 token to balance of the L1 token deposited
3 mapping(address => mapping(address => uint256)) public deposits;
Kopyala

Bunun gibi bir çift eşleştirme(opens in a new tab), iki boyutlu bir seyrek diziyi(opens in a new tab) tanımlama şeklinizdir. Bu veri yapısındaki değerler deposit[L1 token addr][L2 token addr] olarak tanımlanır. Varsayılan değer sıfırdır. Yalnızca farklı bir değere ayarlanmış hücreler depolamaya yazılır.

1
2 /***************
3 * Constructor *
4 ***************/
5
6 // This contract lives behind a proxy, so the constructor parameters will go unused.
7 constructor() CrossDomainEnabled(address(0)) {}
Kopyala

Depodaki tüm değişkenleri kopyalamak zorunda kalmadan bu sözleşmeyi yükseltebilmek istiyoruz. Bunu yapmak için bir Proxy(opens in a new tab) kullanıyoruz. Bu, çağrıları adresi proxy sözleşmesi tarafından saklanan ayrı bir kişiye aktarmak için delegatecall(opens in a new tab) kullanan bir sözleşmedir (yükselttiğinizde proxy'ye bu adresi değiştirmesini söylersiniz). delegatecall kullandığınızda, depolama alanı çağırma sözleşmesinin deposu olarak kalır, bu nedenle tüm sözleşme durumu değişkenlerinin değerleri etkilenmez.

delegecall'un callee'si olan sözleşmenin depolamasının kullanılmaması ve bu nedenle ona iletilen oluşturucu değerlerinin önemli olmaması, bu modelin etkilerinden birisidir. CrossDomainEnabled yapıcısına anlamsız bir değer sağlayabilmemizin nedeni budur. Aşağıdaki başlatmanın yapıcıdan ayrı olmasının nedeni de budur.

1 /******************
2 * Initialization *
3 ******************/
4
5 /**
6 * @param _l1messenger L1 Messenger address being used for cross-chain communications.
7 * @param _l2TokenBridge L2 standard bridge address.
8 */
9 // slither-disable-next-line external-function
Tümünü göster
Kopyala

Bu Slither testi(opens in a new tab), sözleşme kodundan çağrılır ve bu nedenle public yerine external olarak bildirilebilir. external fonksiyonların kullanım maliyeti, çağrı verilerinde parametrelerle sağlanabildikleri için daha düşük olabilir. public olarak tanımlanan fonksiyonlara sözleşme içinden erişilebilir olmalıdır. Sözleşmeler kendi çağrı verilerini değiştiremez, bu nedenle parametrelerin bellekte olması gerekir. Böyle bir fonksiyon harici olarak çağrıldığında, çağrı verilerini belleğe kopyalamak gerekir ve bu da gaz maliyetine neden olur. Bu durumda fonksiyon sadece bir kez çağrılır, bu nedenle verimsizlik bizim için önemli değildir.

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

initialize fonksiyonu yalnızca bir kez çağrılmalıdır. K1 alan adları arası mesajcısının veya K2 token köprüsünün adresi değişirse, yeni bir proxy ve onu çağıran yeni bir köprü oluştururuz. Bunun, çok nadir gerçekleşen tüm sistemin yükseltilmesi dışında gerçekleşmesi pek olası değildir.

Bu fonksiyonun, onu kimin arayabileceğini kısıtlayan herhangi bir mekanizmaya sahip olmadığını unutmayın. Bu, teorik olarak bir saldırganın biz proxy'yi ve köprünün ilk sürümünü dağıtana kadar bekleyebileceği ve ardından meşru kullanıcıdan önce initialize fonksiyonuna ulaşmak için front-run(opens in a new tab) yapabileceği anlamına gelir. Ancak bunu önlemenin iki yöntemi vardır:

  1. Sözleşmeler doğrudan bir EOA tarafından değil de onları oluşturan başka bir sözleşmeye sahip olan bir işlemde(opens in a new tab) dağıtılırsa tüm süreç atomik olabilir ve başka herhangi bir işlem yürütülmeden önce tamamlanabilir.
  2. Geçerli initialize çağrısı başarısız olursa, yeni oluşturulan proxy ve köprüyü yok saymak ve yenilerini oluşturmak her zaman mümkündür.
1 messenger = _l1messenger;
2 l2TokenBridge = _l2TokenBridge;
3 }
Kopyala

Bunlar, köprünün bilmesi gereken iki parametredir.

1
2 /**************
3 * Depositing *
4 **************/
5
6 /** @dev Modifier requiring sender to be EOA. This check could be bypassed by a malicious
7 * contract via initcode, but it takes care of the user error we want to avoid.
8 */
9 modifier onlyEOA() {
10 // Used to stop deposits from contracts (avoid accidentally lost tokens)
11 require(!Address.isContract(msg.sender), "Account not EOA");
12 _;
13 }
Tümünü göster
Kopyala

Bu, OpenZeppelin'in Address yardımcı araçlarına ihtiyaç duymamızın nedenidir.

1 /**
2 * @dev This function can be called with no data
3 * to deposit an amount of ETH to the caller's balance on L2.
4 * Since the receive function doesn't take data, a conservative
5 * default amount is forwarded to L2.
6 */
7 receive() external payable onlyEOA {
8 _initiateETHDeposit(msg.sender, msg.sender, 200_000, bytes(""));
9 }
Tümünü göster
Kopyala

Bu fonksiyon test amaçlı mevcuttur. Arayüz tanımlarında görünmediğine dikkat edin: Normal kullanım için değildir.

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 address _to,
13 uint32 _l2Gas,
14 bytes calldata _data
15 ) external payable {
16 _initiateETHDeposit(msg.sender, _to, _l2Gas, _data);
17 }
Tümünü göster
Kopyala

Bu iki fonksiyon, gerçek ETH yatırma işlemini yöneten fonksiyon olan _initiateETHDeposit etrafındaki paketleyicilerdir.

1 /**
2 * @dev Performs the logic for deposits by storing the ETH and informing the L2 ETH Gateway of
3 * the deposit.
4 * @param _from Account to pull the deposit from on L1.
5 * @param _to Account to give the deposit to on L2.
6 * @param _l2Gas Gas limit required to complete the deposit on L2.
7 * @param _data Optional data to forward to L2. This data is provided
8 * solely as a convenience for external contracts. Aside from enforcing a maximum
9 * length, these contracts provide no guarantees about its content.
10 */
11 function _initiateETHDeposit(
12 address _from,
13 address _to,
14 uint32 _l2Gas,
15 bytes memory _data
16 ) internal {
17 // Construct calldata for finalizeDeposit call
18 bytes memory message = abi.encodeWithSelector(
Tümünü göster
Kopyala

Etki alanları arası mesajların çalışma şekli, hedef sözleşmenin çağrı verileri olarak mesajla birlikte çağrılmasıdır. Solidity sözleşmeleri, çağrı verilerini her zaman aşağıdaki ABI özelliklerine(opens in a new tab) uygun olarak yorumlar. abi.encodeWithSelector(opens in a new tab) Solidity fonksiyonu, bu çağrı verilerini oluşturur.

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

Buradaki mesaj, şu parametrelerle finalizeDeposit fonksiyonunu(opens in a new tab) çağırmaktır:

ParametreDeğerAnlam
_l1Tokenaddress(0)K1'de ETH'yi (ERC-20 token'ı değildir) temsil eden özel değer
_l2TokenLib_PredeployAddresses.OVM_ETHOptimism'de ETH'yi yöneten K2 sözleşmesi, 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000 (bu sözleşme yalnızca dahili Optimism kullanımı içindir)
_from_fromETH'yi gönderen K1 üzerindeki adres
_to_toETH'yi alan K2'deki adres
amountmsg.valueGönderilen wei miktarı (zaten köprüye gönderildi)
_data_dataYatırmaya eklenecek ek tarih
1 // Send calldata into L2
2 // slither-disable-next-line reentrancy-events
3 sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);
Kopyala

Mesajı, alan adları arası mesajcısı ile gönderin.

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

Bu transferi dinleyen herhangi bir merkeziyetsiz uygulamayı bilgilendirmek için bir olay yayınlayın.

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 }
Tümünü göster
Kopyala

Bu iki fonksiyon, gerçek ERC-20 yatırma işlemini yöneten fonksiyon olan _initiateERC20Deposit etrafındaki paketleyicilerdir.

1 /**
2 * @dev Performs the logic for deposits by informing the L2 Deposited Token
3 * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)
4 *
5 * @param _l1Token Address of the L1 ERC20 we are depositing
6 * @param _l2Token Address of the L1 respective L2 ERC20
7 * @param _from Account to pull the deposit from on L1
8 * @param _to Account to give the deposit to on L2
9 * @param _amount Amount of the ERC20 to deposit.
10 * @param _l2Gas Gas limit required to complete the deposit on L2.
11 * @param _data Optional data to forward to L2. This data is provided
12 * solely as a convenience for external contracts. Aside from enforcing a maximum
13 * length, these contracts provide no guarantees about its content.
14 */
15 function _initiateERC20Deposit(
16 address _l1Token,
17 address _l2Token,
18 address _from,
19 address _to,
20 uint256 _amount,
21 uint32 _l2Gas,
22 bytes calldata _data
23 ) internal {
Tümünü göster
Kopyala

Bu fonksiyon, birkaç önemli farklılık dışında yukarıdaki _initiateETHDeposit fonksiyonuna benzer. İlk fark, bu fonksiyonun token adreslerini ve aktarılacak miktarı parametre olarak almasıdır. ETH söz konusu olduğunda köprüye yapılan çağrı, varlığın köprü hesabına (msg.value) transferini zaten içerir.

1 // When a deposit is initiated on L1, the L1 Bridge transfers the funds to itself for future
2 // withdrawals. safeTransferFrom also checks if the contract has code, so this will fail if
3 // _from is an EOA or address(0).
4 // slither-disable-next-line reentrancy-events, reentrancy-benign
5 IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount);
Kopyala

ERC-20 token transferleri, ETH'den farklı bir süreci takip eder:

  1. Kullanıcı (_from), köprüye uygun token'ları aktarması için bir izin verir.
  2. Kullanıcı, token sözleşmesinin adresi, miktarı vb. ile birlikte köprüyü çağırır.
  3. Köprü, token'ları yatırma işleminin bir parçası olarak (kendisine) aktarır.

İlk adım, son ikisinden ayrı bir işlemde gerçekleşebilir. Ancak _initiateERC20Deposit çağıran iki fonksiyon, (depositERC20 ve DepositERC20To), bu fonksiyonu _from parametresi olarak yalnızca msg.sender ile çağırdığından, front-running bir sorun olmaz.

1 // Construct calldata for _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 // Send calldata into L2
13 // slither-disable-next-line reentrancy-events, reentrancy-benign
14 sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);
15
16 // slither-disable-next-line reentrancy-benign
17 deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] + _amount;
Tümünü göster
Kopyala

Yatırılan token miktarını deposits veri yapısına ekleyin. K2'de aynı K1 ERC-20 token'ına karşılık gelen birden fazla adres olabilir, bu nedenle yatırma işlemlerini takip etmek için köprünün K1 ERC-20 token bakiyesini kullanmak yeterli değildir.

1
2 // slither-disable-next-line reentrancy-events
3 emit ERC20DepositInitiated(_l1Token, _l2Token, _from, _to, _amount, _data);
4 }
5
6 /*************************
7 * Cross-chain Functions *
8 *************************/
9
10 /**
11 * @inheritdoc IL1StandardBridge
12 */
13 function finalizeETHWithdrawal(
14 address _from,
15 address _to,
16 uint256 _amount,
17 bytes calldata _data
Tümünü göster
Kopyala

K2 köprüsü, K2 alan adları arası mesajcısına, K1 alan adları arası mesajcısının bu fonksiyonu çağırmasına neden olan bir mesaj gönderir (bir kez mesajı sonlandıran işlem(opens in a new tab) elbette K1'de gönderilir).

1 ) external onlyFromCrossDomainAccount(l2TokenBridge) {
Kopyala

Bunun meşru bir mesaj olduğundan, alan adları arası mesajcısından gelen ve K2 token köprüsünden kaynaklanan bir mesaj olduğundan emin olun. Bu fonksiyon, ETH'yi köprüden çekmek için kullanılır, bu nedenle yalnızca yetkili arayan tarafından çağrıldığından emin olmalıyız.

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

ETH aktarmanın yolu, alıcıyı msg.value içindeki wei miktarıyla aramaktır.

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

Çekme işlemi ile ilgili bir olay yayınlayın.

1 }
2
3 /**
4 * @inheritdoc IL1ERC20Bridge
5 */
6 function finalizeERC20Withdrawal(
7 address _l1Token,
8 address _l2Token,
9 address _from,
10 address _to,
11 uint256 _amount,
12 bytes calldata _data
13 ) external onlyFromCrossDomainAccount(l2TokenBridge) {
Tümünü göster
Kopyala

Bu fonksiyon, ERC-20 token'ları için gerekli değişikliklerle birlikte yukarıdaki finalizeETHWithdrawal fonksiyonuna benzer.

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

deposits veri yapısını güncelleyin.

1
2 // When a withdrawal is finalized on L1, the L1 Bridge transfers the funds to the withdrawer
3 // slither-disable-next-line reentrancy-events
4 IERC20(_l1Token).safeTransfer(_to, _amount);
5
6 // slither-disable-next-line reentrancy-events
7 emit ERC20WithdrawalFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);
8 }
9
10
11 /*****************************
12 * Temporary - Migrating ETH *
13 *****************************/
14
15 /**
16 * @dev Adds ETH balance to the account. This is meant to allow for ETH
17 * to be migrated from an old gateway to a new gateway.
18 * NOTE: This is left for one upgrade only so we are able to receive the migrated ETH from the
19 * old contract
20 */
21 function donateETH() external payable {}
22}
Tümünü göster
Kopyala

Köprünün daha önce bir uygulaması vardı. Uygulamadan buna geçtiğimizde, tüm varlıkları taşımak zorunda kaldık. ERC-20 token'ları sadece taşınabilir. Ancak, ETH'yi bir sözleşmeye aktarmak için o sözleşmenin onayına ihtiyacınız var ve bu da donateETH'in bize sağladığı şeydir.

K2 üzerinde ERC-20 Token'ları

Bir ERC-20 token'ının standart köprüye sığması için standart köprünün, sadece ama sadece standart köprünün token basmasına izin vermesi gerekir. Bu, köprülerin Optimism üzerinde dolaşan token sayısının K1 köprü sözleşmesi içinde kilitli token sayısına eşit olduğundan emin olması gerektiği için gereklidir. K2'de çok fazla token varsa, bazı kullanıcılar varlıklarını K1'e geri köprüleyemez. Güvenilir bir köprü yerine, esasen kısmi rezerv bankacılığını(opens in a new tab) yeniden yaratmış olurduk. K1'de çok fazla token varsa, bu token'lardan bazıları köprü sözleşmesinin içinde sonsuza kadar kilitli kalır çünkü K2 token'larını yakmadan onları serbest bırakmanın bir yolu yoktur.

IL2StandardERC20

Standart köprüyü kullanan K2 üzerindeki her ERC-20 token'ının, standart köprünün ihtiyaç duyduğu fonksiyonlara ve olaylara sahip olan bu arayüzü(opens in a new tab) sağlaması gerekir.

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

Standart ERC-20 arayüzü(opens in a new tab), mint ve burn fonksiyonlarını içermez. Bu yöntemler, token'ları oluşturma ve yok etme mekanizmalarını belirsiz bırakan ERC-20 standardı(opens in a new tab) için gerekli değildir.

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

ERC-165 arayüzü(opens in a new tab), bir sözleşmenin hangi fonksiyonları sağladığını belirtmek için kullanılır. Standardı buradan okuyabilirsiniz(opens in a new tab).

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

Bu fonksiyon, bu sözleşmeye köprülenen K1 token'ının adresini sağlar. Ters yönde benzer bir fonksiyonumuz olmadığını unutmayın. Uygulandığında K2 desteğinin planlanıp planlanmadığına bakılmaksızın herhangi bir K1 token'ını köprüleyebilmemiz gerekir.

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

Token'ları basmak (oluşturmak) ve yakmak (yok etmek) için fonksiyonlar ve olaylar. Köprü, token sayısının doğru (K1'de kilitli token sayısına eşit) olduğundan emin olmak için bu fonksiyonları çalıştırabilen tek varlık olmalıdır.

L2StandardERC20

Bu, IL2StandardERC20 arayüzü uygulamamızdır(opens in a new tab). Bir tür özel mantığa ihtiyacınız yoksa, bunu kullanmalısınız.

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

OpenZeppelin ERC-20 sözleşmesi(opens in a new tab). Optimism, mevcut özellikler iyi denetlendiğinde ve varlıkları elinde tutacak kadar güvenilir olması gerektiğinde yeni özellikler icat edilmemesi gerektiğine inanır.

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

Bunlar, bizim ihtiyaç duyduğumuz ve normalde ERC-20'nin gerektirmediği iki ek yapılandırma parametresidir.

1
2 /**
3 * @param _l2Bridge Address of the L2 standard bridge.
4 * @param _l1Token Address of the corresponding L1 token.
5 * @param _name ERC20 name.
6 * @param _symbol ERC20 symbol.
7 */
8 constructor(
9 address _l2Bridge,
10 address _l1Token,
11 string memory _name,
12 string memory _symbol
13 ) ERC20(_name, _symbol) {
14 l1Token = _l1Token;
15 l2Bridge = _l2Bridge;
16 }
Tümünü göster
Kopyala

Önce kalıtım ile aldığımız sözleşmenin yapıcısını çağırın (ERC20(_name, _symbol)) ve sonra kendi değişkenlerimizi ayarlayın.

1
2 modifier onlyL2Bridge() {
3 require(msg.sender == l2Bridge, "Only L2 Bridge can mint and burn");
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 }
Tümünü göster
Kopyala

ERC-165(opens in a new tab) bu şekilde çalışır. Her arayüz, desteklenen bir dizi fonksiyondur ve özel(opens in a new tab) veya bu fonksiyonların ABI fonksiyon seçicilerine(opens in a new tab) ait olarak tanımlanır.

K2 köprüsü, varlıkları gönderdiği ERC-20 sözleşmesinin bir IL2StandardERC20 olduğundan emin olmak için doğruluk kontrolü olarak ERC-165'i kullanır.

Not: Hileli sözleşmenin supportsInterface için yanlış yanıtlar vermesini önleyecek hiçbir şey yoktur, bu nedenle bu bir güvenlik mekanizması değil, doğruluk kontrol mekanizmasıdır.

1 // slither-disable-next-line external-function
2 function mint(address _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(address _from, uint256 _amount) public virtual onlyL2Bridge {
10 _burn(_from, _amount);
11
12 emit Burn(_from, _amount);
13 }
14}
Tümünü göster
Kopyala

Yalnızca K2 köprüsünün varlıkları basmasına ve yakmasına izin verilir.

_mint ve _burn aslında OpenZeppelin ERC-20 sözleşmesinde tanımlanmıştır. Bu sözleşme onları harici olarak ifşa etmez, çünkü token'ları basma ve yakma koşulları, ERC-20'yi kullanma yollarının sayısı kadar çeşitlidir.

K2 Köprü Kodu

Bu, Optimism üzerindeki köprüyü çalıştıran koddur. Bu sözleşmenin kaynağı buradadır(opens 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";
Kopyala

IL2ERC20Bridge(opens in a new tab) arayüzü, yukarıda gördüğümüz K1 eş değerine çok benzer. İki önemli fark vardır:

  1. K1'de yatırma işlemini başlatır ve çekme işlemlerini sonlandırırsınız. Burada ise çekme işlemlerini başlatır ve yatırma işlemlerini sonlandırırsınız.
  2. K1'de ETH ve ERC-20 token'ları arasında ayrım yapmak gerekir. K2'de aynı fonksiyonları her ikisi için de kullanabiliriz çünkü Optimism üzerindeki dahili ETH bakiyeleri, 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000(opens in a new tab) adresiyle bir ERC-20 token'ı olarak işlenir.
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 The L2 Standard bridge is a contract which works together with the L1 Standard bridge to
12 * enable ETH and ERC20 transitions between L1 and L2.
13 * This contract acts as a minter for new tokens when it hears about deposits into the L1 Standard
14 * bridge.
15 * This contract also acts as a burner of the tokens intended for withdrawal, informing the L1
16 * bridge to release L1 funds.
17 */
18contract L2StandardBridge is IL2ERC20Bridge, CrossDomainEnabled {
19 /********************************
20 * External Contract References *
21 ********************************/
22
23 address public l1TokenBridge;
Tümünü göster
Kopyala

K1 köprüsünün adresini takip edin. K1 eş değerinin aksine, burada bu değişkene ihtiyacımız var. K1 köprüsünün adresi önceden bilinmiyor.

1
2 /***************
3 * Constructor *
4 ***************/
5
6 /**
7 * @param _l2CrossDomainMessenger Cross-domain messenger used by this contract.
8 * @param _l1TokenBridge Address of the L1 bridge deployed to the main chain.
9 */
10 constructor(address _l2CrossDomainMessenger, address _l1TokenBridge)
11 CrossDomainEnabled(_l2CrossDomainMessenger)
12 {
13 l1TokenBridge = _l1TokenBridge;
14 }
15
16 /***************
17 * Withdrawing *
18 ***************/
19
20 /**
21 * @inheritdoc IL2ERC20Bridge
22 */
23 function withdraw(
24 address _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 address _l2Token,
37 address _to,
38 uint256 _amount,
39 uint32 _l1Gas,
40 bytes calldata _data
41 ) external virtual {
42 _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _l1Gas, _data);
43 }
Tümünü göster
Kopyala

Bu iki fonksiyon çekme işlemlerini başlatır. K1 token adresini belirtmeye gerek olmadığını unutmayın. K2 token'larının bize K1 eş değerinin adresini söylemesi bekleniyor.

1
2 /**
3 * @dev Performs the logic for withdrawals by burning the token and informing
4 * the L1 token Gateway of the withdrawal.
5 * @param _l2Token Address of L2 token where withdrawal is initiated.
6 * @param _from Account to pull the withdrawal from on L2.
7 * @param _to Account to give the withdrawal to on L1.
8 * @param _amount Amount of the token to withdraw.
9 * @param _l1Gas Unused, but included for potential forward compatibility considerations.
10 * @param _data Optional data to forward to L1. This data is provided
11 * solely as a convenience for external contracts. Aside from enforcing a maximum
12 * length, these contracts provide no guarantees about its content.
13 */
14 function _initiateWithdrawal(
15 address _l2Token,
16 address _from,
17 address _to,
18 uint256 _amount,
19 uint32 _l1Gas,
20 bytes calldata _data
21 ) internal {
22 // When a withdrawal is initiated, we burn the withdrawer's funds to prevent subsequent L2
23 // usage
24 // slither-disable-next-line reentrancy-events
25 IL2StandardERC20(_l2Token).burn(msg.sender, _amount);
Tümünü göster
Kopyala

_from parametresine değil, sahte olması çok daha zor olan (bildiğim kadarıyla imkansız) msg.sender parametresine güvendiğimize dikkat edin.

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

K1'de ETH ve ERC-20 arasında ayrım yapmak gerekir.

1 message = abi.encodeWithSelector(
2 IL1StandardBridge.finalizeETHWithdrawal.selector,
3 _from,
4 _to,
5 _amount,
6 _data
7 );
8 } else {
9 message = abi.encodeWithSelector(
10 IL1ERC20Bridge.finalizeERC20Withdrawal.selector,
11 l1Token,
12 _l2Token,
13 _from,
14 _to,
15 _amount,
16 _data
17 );
18 }
19
20 // Send message up to L1 bridge
21 // slither-disable-next-line reentrancy-events
22 sendCrossDomainMessage(l1TokenBridge, _l1Gas, message);
23
24 // slither-disable-next-line reentrancy-events
25 emit WithdrawalInitiated(l1Token, _l2Token, msg.sender, _to, _amount, _data);
26 }
27
28 /************************************
29 * Cross-chain Function: Depositing *
30 ************************************/
31
32 /**
33 * @inheritdoc IL2ERC20Bridge
34 */
35 function finalizeDeposit(
36 address _l1Token,
37 address _l2Token,
38 address _from,
39 address _to,
40 uint256 _amount,
41 bytes calldata _data
Tümünü göster
Kopyala

Bu fonksiyon, L1StandardBridge tarafından çağrılır.

1 ) external virtual onlyFromCrossDomainAccount(l1TokenBridge) {
Kopyala

Mesajın kaynağının meşru olduğundan emin olun. Bu, fonksiyon _mint'i çağırdığı ve köprünün Katman 1'de sahip olduğu token'lar tarafından kapsanmayan token'ları vermek için kullanılabileceği için önemlidir.

1 // Check the target token is compliant and
2 // verify the deposited token on L1 matches the L2 deposited token representation here
3 if (
4 // slither-disable-next-line reentrancy-events
5 ERC165Checker.supportsInterface(_l2Token, 0x1d1d8b63) &&
6 _l1Token == IL2StandardERC20(_l2Token).l1Token()
Kopyala

Doğruluk testleri:

  1. Doğru arayüz destekleniyor
  2. L2 ERC-20 sözleşmesinin Katman 1 adresi, token'ların Katman 1 kaynağıyla eşleşiyor
1 ) {
2 // When a deposit is finalized, we credit the account on L2 with the same amount of
3 // tokens.
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);
Kopyala

Doğruluk testlerini geçerse yatırmayı tamamlayın:

  1. Token'ları basma
  2. Uygun olayı yayınlama
1 } else {
2 // Either the L2 token which is being deposited-into disagrees about the correct address
3 // of its L1 token, or does not support the correct interface.
4 // This should only happen if there is a malicious L2 token, or if a user somehow
5 // specified the wrong L2 token address to deposit into.
6 // In either case, we stop the process here and construct a withdrawal
7 // message so that users can get their funds out in some cases.
8 // There is no way to prevent malicious token contracts altogether, but this does limit
9 // user error and mitigate some forms of malicious contract behavior.
Tümünü göster
Kopyala

Bir kullanıcı yanlış Katman 2 token adresini kullanarak tespit edilebilir bir hata yaptıysa, yatırmayı iptal etmek ve tokenları Katman 1'e iade etmek istiyoruz. Bunu Katman 2'den yapabilmemizin tek yolu, hata meydan okuma süresini beklemek zorunda kalacak bir mesaj göndermektir ancak bu, kullanıcı için token'ları kalıcı olarak kaybetmekten çok daha iyidir.

1 bytes memory message = abi.encodeWithSelector(
2 IL1ERC20Bridge.finalizeERC20Withdrawal.selector,
3 _l1Token,
4 _l2Token,
5 _to, // switched the _to and _from here to bounce back the deposit to the sender
6 _from,
7 _amount,
8 _data
9 );
10
11 // Send message up to L1 bridge
12 // slither-disable-next-line reentrancy-events
13 sendCrossDomainMessage(l1TokenBridge, 0, message);
14 // slither-disable-next-line reentrancy-events
15 emit DepositFailed(_l1Token, _l2Token, _from, _to, _amount, _data);
16 }
17 }
18}
Tümünü göster
Kopyala

Sonuç

Standart köprü, varlık aktarımları için en esnek mekanizmadır. Ancak çok genel olduğu için her zaman kullanması en kolay olan mekanizma değildir. Özellikle çekimler için, çoğu kullanıcı meydan okuma süresini beklemeyen ve çekimi sonlandırmak için bir Merkle ispatı gerektirmeyen üçüncü parti köprüleri(opens in a new tab) kullanmayı tercih eder.

Bu köprüler genellikle Katman 1 üzerinde küçük bir ücret (genelde bir standart köprü çekiminin gaz ücretinden daha azına) için anında sağladıkları varlıklara sahip olarak çalışırlar. Köprü (ya da onu çalıştıran insanlar) Katman 1 varlıklarının azaldığını sezdiğinde Katman 2'den yeteri kadar varlığı aktarır. Bunlar çok büyük çekimler olduğu için, çekim ücreti büyük bir miktar üzerinden amorti edilmiştir ve daha küçük bir yüzdeliktir.

Umarım bu makale katman 2'nin nasıl çalıştığı hakkında dahasını anlamanıza; temiz ve güvenli Solidity kodu yazmanıza yardımcı olmuştur.

Son düzenleme: @lukassim(opens in a new tab), 26 Nisan 2024

Bu rehber yararlı oldu mu?