Optimism standart köprü sözleşmesine genel bakış
Optimism (opens in a new tab) bir İyimser Toplama türüdür. İyimser toplamalar, işlemler ağdaki her düğüm yerine yalnızca birkaç düğüm tarafından işlendiği için Ethereum Ana Ağı'ndan (katman 1 veya K1 olarak da bilinir) çok daha düşük bir fiyata işlem yapabilir. Aynı zamanda, verilerin tümü K1'e yazılır, böylece her şey Ana Ağ'ın tüm bütünlük ve kullanılabilirlik garantileriyle kanıtlanabilir ve 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. Bunu başarmanın yollarından biri, kullanıcıların K1'de varlıkları (en yaygın olanları ETH ve ERC-20 token'larıdır) kilitlemesi ve K2'de kullanmak üzere eşdeğer varlıklar almasıdır. Nihayetinde, bu varlıkları elinde bulunduranlar onları K1'e geri 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 söz konusu köprünün kaynak kodunu gözden geçirecek 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
- Bir ERC-20 yatırılırken, yatıran kişi köprüye yatırılan tutarı harcaması için bir izin verir.
- Yatıran kişi K1 köprüsünü çağırır (
depositERC20,depositERC20To,depositETHveyadepositETHTo) - K1 köprüsü köprülenen varlığın mülkiyetini alır.
- ETH: Varlık, çağrının bir parçası olarak yatıran kişi tarafından aktarılır.
- ERC-20: Varlık, yatıran kişi tarafından sağlanan izni kullanarak köprü tarafından kendisine aktarılır.
- K1 köprüsü, K2 köprüsünde
finalizeDepositfonksiyonunu çağırmak için alanlar arası mesaj mekanizmasını kullanır.
Katman 2
- K2 köprüsü,
finalizeDepositçağrısının meşru olduğunu doğrular:- Alanlar arası mesaj sözleşmesinden gelmiştir
- Aslen K1'deki köprüden gelmiştir
- K2 köprüsü, K2 üzerindeki ERC-20 token sözleşmesinin doğru olup olmadığını kontrol eder:
- K2 sözleşmesi, K1'deki karşılığının, token'ların K1'den geldiği sözleşmeyle aynı olduğunu bildirir.
- K2 sözleşmesi, doğru arayüzü desteklediğini bildirir (ERC-165 (opens in a new tab) kullanarak).
- K2 sözleşmesi doğruysa, uygun adrese uygun sayıda token basması için bu sözleşme çağrılır. Değilse, kullanıcının K1'deki token'ları talep etmesine olanak tanıyan bir çekme işlemi başlatılır.
Çekme akışı
Katman 2
- Çekme işlemini yapan kişi K2 köprüsünü çağırır (
withdrawveyawithdrawTo) - K2 köprüsü,
msg.sender'a ait uygun sayıda token'ı yakar. - K2 köprüsü, K1 köprüsünde
finalizeETHWithdrawalveyafinalizeERC20Withdrawalfonksiyonlarını çağırmak için alanlar arası mesaj mekanizmasını kullanır.
Katman 1
- K1 köprüsü,
finalizeETHWithdrawalveyafinalizeERC20Withdrawalçağrısının meşru olduğunu doğrular:- Alanlar arası mesaj mekanizmasından gelmiştir
- Aslen K2'deki köprüden gelmiştir
- K1 köprüsü, uygun varlığı (ETH veya ERC-20) uygun adrese aktarır.
Katman 1 kodu
Bu, K1'de, yani Ethereum Ana Ağı'nda ç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: MITOptimism'in kodlarının çoğu MIT lisansı altında yayınlanmıştır (opens in a new tab).
1pragma solidity >0.5.0 <0.9.0;Bu yazı yazıldığı sırada 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 IL1ERC20Bridge3 */4interface IL1ERC20Bridge {5 /**********6 * Olaylar *7 **********/89 event ERC20DepositInitiated(Tümünü gösterOptimism köprü terminolojisinde yatırma işlemi K1'den K2'ye transfer, çekme işlemi ise K2'den K1'e transfer anlamına gelir.
1 address indexed _l1Token,2 address indexed _l2Token,Çoğu durumda bir ERC-20'nin K1'deki adresi, K2'deki eşdeğer ERC-20'nin adresiyle aynı değildir.
Token adreslerinin listesini buradan görebilirsiniz (opens in a new tab).
chainId'si 1 olan adres K1'de (Ana Ağ), chainId'si 10 olan adres ise K2'dedir (Optimism).
Diğer iki chainId değeri Kovan test ağı (42) ve Optimistic Kovan test ağı (69) içindir.
1 address indexed _from,2 address _to,3 uint256 _amount,4 bytes _data5 );Transferlere not eklemek mümkündür, bu durumda bu notlar onları bildiren olaylara eklenir.
1 event ERC20WithdrawalFinalized(2 address indexed _l1Token,3 address indexed _l2Token,4 address indexed _from,5 address _to,6 uint256 _amount,7 bytes _data8 );Aynı köprü sözleşmesi her iki yöndeki transferleri de yönetir. K1 köprüsü söz konusu olduğunda bu, yatırma işlemlerinin başlatılması ve çekme işlemlerinin sonlandırılması anlamına gelir.
12 /********************3 * Herkese Açık Fonksiyonlar *4 ********************/56 /**7 * @dev ilgili K2 köprü sözleşmesinin adresini alır.8 * @return İlgili K2 köprü sözleşmesinin adresi.9 */10 function l2TokenBridge() external returns (address);Tümünü gösterBu fonksiyona aslında gerek yoktur çünkü K2'de bu, önceden dağıtılmış bir sözleşmedir ve bu nedenle her zaman 0x4200000000000000000000000000000000000010 adresindedir.
K1 köprüsünün adresini bilmek kolay olmadığından, bu fonksiyon K2 köprüsüyle simetri sağlamak için buradadır.
1 /**2 * @dev bir miktar ERC20'yi K2'deki arayanın bakiyesine yatırır.3 * @param _l1Token Yatırmakta olduğumuz K1 ERC20'sinin adresi4 * @param _l2Token İlgili K2 ERC20'sinin adresi5 * @param _amount Yatırılacak ERC20 miktarı6 * @param _l2Gas K2'de yatırma işlemini tamamlamak için gereken gaz limiti.7 * @param _data K2'ye iletilecek isteğe bağlı veri. Bu veri yalnızca8 * harici sözleşmeler için bir kolaylık olarak sağlanır. Maksimum9 * uzunluğu zorunlu kılmanın yanı sıra, bu sözleşmeler içeriği hakkında hiçbir garanti vermez.10 */11 function depositERC20(12 address _l1Token,13 address _l2Token,14 uint256 _amount,15 uint32 _l2Gas,16 bytes calldata _data17 ) external;Tümünü göster_l2Gas parametresi, işlemin harcamasına izin verilen K2 gazı miktarıdır.
Belirli bir (yüksek) sınıra kadar bu ücretsizdir (opens in a new tab), bu nedenle ERC-20 sözleşmesi basım sırasında gerçekten garip bir şey yapmadığı sürece bu bir sorun olmamalıdır.
Bu fonksiyon, bir kullanıcının varlıkları farklı bir blokzincirdeki aynı adrese köprülediği yaygın senaryoyu ele alır.
1 /**2 * @dev bir miktar ERC20'yi K2'deki bir alıcının bakiyesine yatırır.3 * @param _l1Token Yatırmakta olduğumuz K1 ERC20'sinin adresi4 * @param _l2Token İlgili K2 ERC20'sinin adresi5 * @param _to Çekme işleminin yatırılacağı K2 adresi.6 * @param _amount Yatırılacak ERC20 miktarı.7 * @param _l2Gas K2'de yatırma işlemini tamamlamak için gereken gaz limiti.8 * @param _data K2'ye iletilecek isteğe bağlı veri. Bu veri yalnızca9 * harici sözleşmeler için bir kolaylık olarak sağlanır. Maksimum10 * uzunluğu zorunlu kılmanın yanı sıra, bu sözleşmeler içeriği hakkında hiçbir garanti vermez.11 */12 function depositERC20To(13 address _l1Token,14 address _l2Token,15 address _to,16 uint256 _amount,17 uint32 _l2Gas,18 bytes calldata _data19 ) external;Tümünü gösterBu fonksiyon depositERC20 ile neredeyse aynıdır, ancak ERC-20'yi farklı bir adrese göndermenize olanak tanır.
1 /*************************2 * Zincirler Arası Fonksiyonlar *3 *************************/45 /**6 * @dev K2'den K1'e bir çekme işlemini tamamlar ve fonları alıcının7 * K1 ERC20 token bakiyesine yatırır.8 * K2'den başlatılan çekme işlemi sonlandırılmamışsa bu çağrı başarısız olur.9 *10 * @param _l1Token finalizeWithdrawal işleminin yapılacağı K1 token'ının adresi.11 * @param _l2Token Çekme işleminin başlatıldığı K2 token'ının adresi.12 * @param _from Transferi başlatan K2 adresi.13 * @param _to Çekme işleminin yatırılacağı K1 adresi.14 * @param _amount Yatırılacak ERC20 miktarı.15 * @param _data K2'deki gönderici tarafından sağlanan veri. Bu veri16 * yalnızca harici sözleşmeler için bir kolaylık olarak sağlanır. Maksimum17 * uzunluğu zorunlu kılmanın yanı sıra, bu sözleşmeler içeriği hakkında hiçbir garanti vermez.18 */19 function finalizeERC20Withdrawal(20 address _l1Token,21 address _l2Token,22 address _from,23 address _to,24 uint256 _amount,25 bytes calldata _data26 ) external;27}Tümünü gösterOptimism'de çekme işlemleri (ve K2'den K1'e diğer mesajlar) iki adımlı bir süreçtir:
- K2'de bir başlatma işlemi.
- K1'de bir sonlandırma veya talep etme işlemi. Bu işlemin, K2 işlemi için hata itiraz süresi (opens in a new tab) sona erdikten 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ımlarını içerir.
Bu tanımlar, yukarıda ERC-20 için IL1ERC20Bridge içinde tanımlananlara çok benzer.
Köprü arayüzü iki dosyaya bölünmüştür çünkü bazı ERC-20 token'ları özel işlem gerektirir ve standart köprü tarafından işlenemez.
Bu şekilde, böyle bir token'ı işleyen özel köprü, IL1ERC20Bridge'i uygulayabilir ve ayrıca ETH köprülemek zorunda kalmaz.
1// SPDX-License-Identifier: MIT2pragma solidity >0.5.0 <0.9.0;34import "./IL1ERC20Bridge.sol";56/**7 * @title IL1StandardBridge8 */9interface IL1StandardBridge is IL1ERC20Bridge {10 /**********11 * Olaylar *12 **********/13 event ETHDepositInitiated(14 address indexed _from,15 address indexed _to,16 uint256 _amount,17 bytes _data18 );Tümünü gösterBu olay, K1 ve K2 token adresleri dışında ERC-20 sürümüyle (ERC20DepositInitiated) neredeyse aynıdır.
Aynısı diğer olaylar ve fonksiyonlar için de geçerlidir.
1 event ETHWithdrawalFinalized(2 .3 .4 .5 );67 /********************8 * Herkese Açık Fonksiyonlar *9 ********************/1011 /**12 * @dev Bir miktar ETH'yi K2'deki çağıranın bakiyesine yatırır.13 .14 .15 .16 */17 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable;1819 /**20 * @dev Bir miktar ETH'yi K2'deki bir alıcının bakiyesine yatırır.21 .22 .23 .24 */25 function depositETHTo(26 address _to,27 uint32 _l2Gas,28 bytes calldata _data29 ) external payable;3031 /*************************32 * Zincirler Arası Fonksiyonlar *33 *************************/3435 /**36 * @dev K2'den K1'e bir çekme işlemini tamamlar ve fonları alıcının37 * K1 ETH token bakiyesine yatırır. Bu fonksiyonu yalnızca xDomainMessenger çağırabildiğinden, çekme işlemi38 * sonlandırılmadan asla çağrılmaz.39 .40 .41 .42 */43 function finalizeETHWithdrawal(44 address _from,45 address _to,46 uint256 _amount,47 bytes calldata _data48 ) external;49}Tümünü gösterCrossDomainEnabled
Bu sözleşme (opens in a new tab), diğer katmana mesaj göndermek için her iki köprü (K1 ve K2) tarafından miras alınır.
1// SPDX-License-Identifier: MIT2pragma solidity >0.5.0 <0.9.0;34/* Arayüz İçe Aktarmaları */5import { ICrossDomainMessenger } from "./ICrossDomainMessenger.sol";Bu arayüz (opens in a new tab), alanlar arası mesajcıyı kullanarak sözleşmeye diğer katmana nasıl mesaj gönderileceğini bildirir. Bu alanlar arası mesajcı tamamen ayrı bir sistemdir ve gelecekte yazmayı umduğum kendi makalesini hak etmektedir.
1/**2 * @title CrossDomainEnabled3 * @dev Alanlar arası iletişim gerçekleştiren sözleşmeler için yardımcı sözleşme4 *5 * Kullanılan derleyici: miras alan sözleşme tarafından tanımlanır6 */7contract CrossDomainEnabled {8 /*************9 * Değişkenler *10 *************/1112 // Diğer alandan mesaj göndermek ve almak için kullanılan Mesajcı sözleşmesi.13 address public messenger;1415 /***************16 * Yapıcı *17 ***************/1819 /**20 * @param _messenger Mevcut katmandaki CrossDomainMessenger'ın adresi.21 */22 constructor(address _messenger) {23 messenger = _messenger;24 }Tümünü gösterSözleşmenin bilmesi gereken tek parametre, bu katmandaki alanlar arası mesajcının adresidir. Bu parametre yapılandırıcıda bir kez ayarlanır ve asla değişmez.
12 /**********************3 * Fonksiyon Değiştiricileri *4 **********************/56 /**7 * Değiştirilen fonksiyonun yalnızca belirli bir alanlar arası hesap tarafından çağrılabilir olmasını zorunlu kılar.8 * @param _sourceDomainAccount Kaynak alandaki, bu fonksiyonu çağırmak için9 * doğrulanmış tek hesap.10 */11 modifier onlyFromCrossDomainAccount(address _sourceDomainAccount) {Tümünü gösterAlanlar arası mesajlaşma, çalıştığı blokzincirdeki (Ethereum Ana Ağı veya Optimism) herhangi bir sözleşme tarafından erişilebilirdir. Ancak her iki taraftaki köprünün de, belirli mesajlara yalnızca diğer taraftaki köprüden geldiklerinde güvenmesi gerekir.
1 require(2 msg.sender == address(getCrossDomainMessenger()),3 "OVM_XCHAIN: mesajcı sözleşmesi doğrulanmadı"4 );Yalnızca uygun alanlar arası mesajcıdan (messenger, aşağıda gördüğünüz gibi) gelen mesajlara güvenilebilir.
12 require(3 getCrossDomainMessenger().xDomainMessageSender() == _sourceDomainAccount,4 "OVM_XCHAIN: alanlar arası mesajın yanlış göndericisi"5 );Alanlar arası mesajcının, diğer katmandan bir mesaj gönderen adresi sağlama şekli, .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 diğer köprüden geldiğinden emin olmalıyız.
12 _;3 }45 /**********************6 * Dahili Fonksiyonlar *7 **********************/89 /**10 * Genellikle depolamadan mesajcıyı alır. Bu fonksiyon, bir alt sözleşmenin11 * geçersiz kılması gerekmesi durumunda kullanıma sunulur.12 * @return Kullanılması gereken alanlar arası mesajcı sözleşmesinin adresi.13 */14 function getCrossDomainMessenger() internal virtual returns (ICrossDomainMessenger) {15 return ICrossDomainMessenger(messenger);16 }Tümünü gösterBu fonksiyon, alanlar arası mesajcıyı döndürür.
Bundan miras alan sözleşmelerin hangi alanlar arası mesajcıyı kullanacağını belirtmek için bir algoritma kullanmasına izin vermek amacıyla messenger değişkeni yerine bir fonksiyon kullanırız.
12 /**3 * Başka bir alandaki bir hesaba mesaj gönderir4 * @param _crossDomainTarget Hedef alandaki amaçlanan alıcı5 * @param _message Hedefe gönderilecek veri (genellikle `onlyFromCrossDomainAccount()` içeren bir fonksiyona6 * çağrı verisi)7 * @param _gasLimit Hedef alandaki mesajın alınması için gaz limiti.8 */9 function sendCrossDomainMessage(10 address _crossDomainTarget,11 uint32 _gasLimit,12 bytes memory _messageTümünü gösterSon olarak, diğer katmana mesaj gönderen fonksiyon.
1 ) internal {2 // slither-disable-next-line reentrancy-events, reentrancy-benignSlither (opens in a new tab), Optimism'in güvenlik açıklarını ve diğer potansiyel sorunları aramak için her sözleşmede çalıştırdığı bir statik analizördür. Bu durumda, aşağıdaki satır iki güvenlik açığını tetikler:
1 getCrossDomainMessenger().sendMessage(_crossDomainTarget, _message, _gasLimit);2 }3}Bu durumda yeniden giriş konusunda endişelenmiyoruz çünkü Slither'ın bunu bilmesinin bir yolu olmasa bile getCrossDomainMessenger()'ın 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: MIT2pragma solidity ^0.8.9;Arayüzler diğer sözleşmelerin bir parçası olabilir, bu nedenle çok çeşitli Solidity sürümlerini desteklemeleri gerekir. Ancak köprünün kendisi bizim sözleşmemizdir ve hangi Solidity sürümünü kullandığı konusunda katı olabiliriz.
1/* Arayüz İçe Aktarmaları */2import { IL1StandardBridge } from "./IL1StandardBridge.sol";3import { IL1ERC20Bridge } from "./IL1ERC20Bridge.sol";IL1ERC20Bridge ve IL1StandardBridge yukarıda açıklanmıştır.
1import { IL2ERC20Bridge } from "../../L2/messaging/IL2ERC20Bridge.sol";Bu arayüz (opens in a new tab) K2'deki standart köprüyü kontrol etmek için mesajlar oluşturmamızı sağlar.
1import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";Bu arayüz (opens in a new tab) ERC-20 sözleşmelerini kontrol etmemizi sağlar. Buradan daha fazlasını okuyabilirsiniz.
1/* Kütüphane İçe Aktarmaları */2import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";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";Lib_PredeployAddresses (opens in a new tab) her zaman aynı adrese sahip olan K2 sözleşmelerinin adreslerini içerir. Buna K2'deki standart köprü de dahildir.
1import { Address } from "@openzeppelin/contracts/utils/Address.sol";OpenZeppelin'in Adres yardımcı programları (opens in a new tab). Sözleşme adresleri ile harici olarak sahip olunan hesaplara (EOA) ait olanlar arasında ayrım 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";ERC-20 standardı (opens in a new tab) bir sözleşmenin hata bildirmesi için iki yolu destekler:
- Geri Al
falsedöndür
Her iki durumu da ele almak kodumuzu daha karmaşık hale getirirdi, bu yüzden bunun yerine tüm hataların bir geri almaya neden olmasını sağlayan (opens in a new tab) OpenZeppelin'in SafeERC20'sini (opens in a new tab) kullanıyoruz.
1/**2 * @title L1StandardBridge3 * @dev L1 ETH ve ERC20 Köprüsü, yatırılan L1 fonlarını ve K2'de kullanımda olan standart4 * token'ları saklayan bir sözleşmedir. İlgili bir K2 Köprüsü ile senkronize olur, ona yatırma işlemleri hakkında bilgi verir5 * ve yeni sonuçlandırılan çekme işlemleri için onu dinler.6 *7 */8contract L1StandardBridge is IL1StandardBridge, CrossDomainEnabled {9 using SafeERC20 for IERC20;Tümünü gösterBu satır, IERC20 arayüzünü her kullandığımızda SafeERC20 sarmalayıcısını kullanmayı nasıl belirlediğimizdir.
12 /********************************3 * Harici Sözleşme Referansları *4 ********************************/56 address public l2TokenBridge;L2StandardBridge adresi.
12 // K1 token'ını K2 token'ına, yatırılan K1 token bakiyesine eşler3 mapping(address => mapping(address => uint256)) public deposits;Bunun gibi çift eşleme (opens in a new tab), iki boyutlu seyrek bir dizi (opens in a new tab) tanımlama yöntemidir.
Bu veri yapısındaki değerler deposit[K1 token adresi][K2 token adresi] 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.
12 /***************3 * Yapıcı *4 ***************/56 // Bu sözleşme bir proxy arkasında bulunur, bu nedenle yapıcı parametreleri kullanılmayacaktır.7 constructor() CrossDomainEnabled(address(0)) {}Depolamadaki 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 sözleşmeye 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.
delegatecall'un çağrılanı olan sözleşmenin depolamasının kullanılmaması ve bu nedenle ona iletilen yapıcı değerlerinin önemli olmaması, bu modelin etkilerinden biridir.
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 * Başlatma *3 ******************/45 /**6 * @param _l1messenger Zincirler arası iletişim için kullanılan K1 Mesajcı adresi.7 * @param _l2TokenBridge K2 standart köprü adresi.8 */9 // slither-disable-next-line external-functionTümünü gösterBu Slither testi (opens in a new tab), sözleşme kodundan çağrılmayan ve bu nedenle public yerine external olarak bildirilebilecek fonksiyonları tanımlar.
external fonksiyonların gaz 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), "Sözleşme zaten başlatıldı.");initialize fonksiyonu yalnızca bir kez çağrılmalıdır.
K1 alanlar 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:
- 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.
- 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 }Bunlar, köprünün bilmesi gereken iki parametredir.
12 /**************3 * Yatırma *4 **************/56 /** @dev Göndericinin EOA olmasını gerektiren değiştirici. Bu kontrol, kötü niyetli bir7 * sözleşme tarafından initcode aracılığıyla atlatılabilir, ancak kaçınmak istediğimiz kullanıcı hatasını önler.8 */9 modifier onlyEOA() {10 // Sözleşmelerden yatırma işlemlerini durdurmak için kullanılır (yanlışlıkla kaybedilen token'ları önlemek için)11 require(!Address.isContract(msg.sender), "Hesap EOA değil");12 _;13 }Tümünü gösterOpenZeppelin'in Address yardımcı programlarına ihtiyaç duymamızın nedeni budur.
1 /**2 * @dev Bu fonksiyon, bir miktar ETH'yi çağıranın K2'deki bakiyesine3 * yatırmak için veri olmadan çağrılabilir.4 * Alım fonksiyonu veri almadığından,5 * K2'ye muhafazakar bir varsayılan miktar iletilir.6 */7 receive() external payable onlyEOA {8 _initiateETHDeposit(msg.sender, msg.sender, 200_000, bytes(""));9 }Tümünü gösterBu 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 IL1StandardBridge3 */4 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable onlyEOA {5 _initiateETHDeposit(msg.sender, msg.sender, _l2Gas, _data);6 }78 /**9 * @inheritdoc IL1StandardBridge10 */11 function depositETHTo(12 address _to,13 uint32 _l2Gas,14 bytes calldata _data15 ) external payable {16 _initiateETHDeposit(msg.sender, _to, _l2Gas, _data);17 }Tümünü gösterBu iki fonksiyon, gerçek ETH yatırma işlemini yöneten fonksiyon olan _initiateETHDeposit etrafındaki sarmalayıcılardır.
1 /**2 * @dev ETH'yi saklayarak ve K2 ETH Ağ Geçidi'ne yatırma hakkında bilgi vererek3 * para yatırma mantığını gerçekleştirir.4 * @param _from K1'de para yatırma işleminin çekileceği hesap.5 * @param _to K2'de para yatırma işleminin verileceği hesap.6 * @param _l2Gas K2'de para yatırma işlemini tamamlamak için gereken gaz limiti.7 * @param _data K2'ye iletilecek isteğe bağlı veri. Bu veri yalnızca8 * harici sözleşmeler için bir kolaylık olarak sağlanır. Maksimum9 * uzunluğu zorunlu kılmanın yanı sıra, bu sözleşmeler içeriği hakkında hiçbir garanti vermez.10 */11 function _initiateETHDeposit(12 address _from,13 address _to,14 uint32 _l2Gas,15 bytes memory _data16 ) internal {17 // finalizeDeposit çağrısı için çağrı verisi oluştur18 bytes memory message = abi.encodeWithSelector(Tümünü gösterAlanlar 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
ABI özelliklerine (opens in a new tab) uygun olarak yorumlar.
Solidity'nin abi.encodeWithSelector (opens in a new tab) fonksiyonu bu çağrı verisini oluşturur.
1 IL2ERC20Bridge.finalizeDeposit.selector,2 address(0),3 Lib_PredeployAddresses.OVM_ETH,4 _from,5 _to,6 msg.value,7 _data8 );Buradaki mesaj, bu parametrelerle finalizeDeposit fonksiyonunu (opens in a new tab) çağırmaktır:
| Parametre | Değer | Anlamı |
|---|---|---|
| _l1Token | address(0) | K1'de ETH'yi (bir ERC-20 token'ı değildir) temsil eden özel değer |
| _l2Token | Lib_PredeployAddresses.OVM_ETH | Optimism'de ETH'yi yöneten K2 sözleşmesi, 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000 (bu sözleşme yalnızca dahili Optimism kullanımı içindir) |
| _from | _from | ETH'yi gönderen K1'deki adres |
| _to | _to | ETH'yi alan K2'deki adres |
| miktar | msg.value | Gönderilen wei miktarı (zaten köprüye gönderildi) |
| _data | _data | Yatırmaya eklenecek ek veri |
1 // Çağrı verisini K2'ye gönder2 // slither-disable-next-line reentrancy-events3 sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);Mesajı, alanlar arası mesajcısı ile gönderin.
1 // slither-disable-next-line reentrancy-events2 emit ETHDepositInitiated(_from, _to, msg.value, _data);3 }Bu transferi dinleyen herhangi bir merkeziyetsiz uygulamayı bilgilendirmek için bir olay yayınlayın.
1 /**2 * @inheritdoc IL1ERC20Bridge3 */4 function depositERC20(5 .6 .7 .8 ) external virtual onlyEOA {9 _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _l2Gas, _data);10 }1112 /**13 * @inheritdoc IL1ERC20Bridge14 */15 function depositERC20To(16 .17 .18 .19 ) external virtual {20 _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _l2Gas, _data);21 }Tümünü gösterBu iki fonksiyon, gerçek ERC-20 yatırma işlemini yöneten fonksiyon olan _initiateERC20Deposit etrafındaki sarmalayıcılardır.
1 /**2 * @dev K2 Yatırılan Token'ı bilgilendirerek yatırma mantığını gerçekleştirir3 * sözleşmesi ve K1 fonlarını kilitlemek için bir işleyici çağırır. (örneğin, transferFrom)4 *5 * @param _l1Token Yatırmakta olduğumuz K1 ERC20'sinin adresi6 * @param _l2Token İlgili K2 ERC20'sinin adresi7 * @param _from K1'de para yatırma işleminin çekileceği hesap.8 * @param _to K2'de para yatırma işleminin verileceği hesap.9 * @param _amount Yatırılacak ERC20 miktarı.10 * @param _l2Gas K2'de yatırma işlemini tamamlamak için gereken gaz limiti.11 * @param _data K2'ye iletilecek isteğe bağlı veri. Bu veri yalnızca12 * harici sözleşmeler için bir kolaylık olarak sağlanır. Maksimum13 * uzunluğu zorunlu kılmanın yanı sıra, bu sözleşmeler içeriği hakkında hiçbir garanti vermez.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 _data23 ) internal {Tümünü gösterBu 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 // K1'de bir yatırma başlatıldığında, K1 Köprüsü gelecekteki2 // para çekme işlemleri için fonları kendisine aktarır. safeTransferFrom ayrıca sözleşmede kod olup olmadığını3 // kontrol eder, bu nedenle _from bir EOA veya address(0) ise bu işlem başarısız olur.4 // slither-disable-next-line reentrancy-events, reentrancy-benign5 IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount);ERC-20 token transferleri, ETH'den farklı bir süreci takip eder:
- Kullanıcı (
_from), köprüye uygun token'ları aktarması için bir izin verir. - Kullanıcı, token sözleşmesinin adresi, miktarı vb. ile birlikte köprüyü çağırır.
- 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 // _l2Token.finalizeDeposit(_to, _amount) için çağrı verisi oluştur2 bytes memory message = abi.encodeWithSelector(3 IL2ERC20Bridge.finalizeDeposit.selector,4 _l1Token,5 _l2Token,6 _from,7 _to,8 _amount,9 _data10 );1112 // Çağrı verisini K2'ye gönder13 // slither-disable-next-line reentrancy-events, reentrancy-benign14 sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);1516 // slither-disable-next-line reentrancy-benign17 deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] + _amount;Tümünü gösterYatı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.
12 // slither-disable-next-line reentrancy-events3 emit ERC20DepositInitiated(_l1Token, _l2Token, _from, _to, _amount, _data);4 }56 /*************************7 * Zincirler Arası Fonksiyonlar *8 *************************/910 /**11 * @inheritdoc IL1StandardBridge12 */13 function finalizeETHWithdrawal(14 address _from,15 address _to,16 uint256 _amount,17 bytes calldata _dataTümünü gösterK2 köprüsü, K2 alanlar arası mesajcısına, K1 alanlar arası mesajcısının bu fonksiyonu çağırmasına neden olan bir mesaj gönderir (tabii ki, mesajı sonlandıran işlem (opens in a new tab) K1'de gönderildikten sonra).
1 ) external onlyFromCrossDomainAccount(l2TokenBridge) {Bunun meşru bir mesaj olduğundan, alanlar 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-events2 (bool success, ) = _to.call{ value: _amount }(new bytes(0));ETH aktarmanın yolu, alıcıyı msg.value içindeki wei miktarıyla aramaktır.
1 require(success, "TransferHelper::safeTransferETH: ETH transferi başarısız");23 // slither-disable-next-line reentrancy-events4 emit ETHWithdrawalFinalized(_from, _to, _amount, _data);Çekme işlemi ile ilgili bir olay yayınlayın.
1 }23 /**4 * @inheritdoc IL1ERC20Bridge5 */6 function finalizeERC20Withdrawal(7 address _l1Token,8 address _l2Token,9 address _from,10 address _to,11 uint256 _amount,12 bytes calldata _data13 ) external onlyFromCrossDomainAccount(l2TokenBridge) {Tümünü gösterBu 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;deposits veri yapısını güncelleyin.
1 // K1'de bir çekme sonlandırıldığında, K1 Köprüsü fonları çekene aktarır2 // slither-disable-next-line reentrancy-events3 IERC20(_l1Token).safeTransfer(_to, _amount);45 // slither-disable-next-line reentrancy-events6 emit ERC20WithdrawalFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);7 }8910 /*****************************11 * Geçici - ETH Taşıma *12 *****************************/1314 /**15 * @dev ETH bakiyesini hesaba ekler. Bu, ETH'nin16 * eski bir ağ geçidinden yeni bir ağ geçidine taşınmasına izin vermek içindir.17 * NOT: Bu, yalnızca bir yükseltme için bırakılmıştır, böylece taşınan ETH'yi18 * eski sözleşmeden alabiliriz19 */20 function donateETH() external payable {}21}Tümünü gösterKö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'deki ERC-20 Token'ları
Bir ERC-20 token'ının standart köprüye sığması için standart köprünün ve 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'deki her ERC-20 token'ı, standart köprünün ihtiyaç duyduğu fonksiyonları ve olayları içeren bu arayüzü (opens in a new tab) sağlamalıdır.
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;34import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";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";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);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.
12 function mint(address _to, uint256 _amount) external;34 function burn(address _from, uint256 _amount) external;56 event Mint(address indexed _account, uint256 _amount);7 event Burn(address indexed _account, uint256 _amount);8}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ünün bizim 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: MIT2pragma solidity ^0.8.9;34import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";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";23contract L2StandardERC20 is IL2StandardERC20, ERC20 {4 address public l1Token;5 address public l2Bridge;Bunlar, bizim ihtiyaç duyduğumuz ve normalde ERC-20'nin gerektirmediği iki ek yapılandırma parametresidir.
12 /**3 * @param _l2Bridge K2 standart köprüsünün adresi.4 * @param _l1Token İlgili K1 token'ının adresi.5 * @param _name ERC20 adı.6 * @param _symbol ERC20 sembolü.7 */8 constructor(9 address _l2Bridge,10 address _l1Token,11 string memory _name,12 string memory _symbol13 ) ERC20(_name, _symbol) {14 l1Token = _l1Token;15 l2Bridge = _l2Bridge;16 }Tümünü gösterÖ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.
12 modifier onlyL2Bridge() {3 require(msg.sender == l2Bridge, "Yalnızca K2 Köprüsü basım ve yakım yapabilir");4 _;5 }678 // slither-disable-next-line external-function9 function supportsInterface(bytes4 _interfaceId) public pure returns (bool) {10 bytes4 firstSupportedInterface = bytes4(keccak256("supportsInterface(bytes4)")); // ERC16511 bytes4 secondSupportedInterface = IL2StandardERC20.l1Token.selector ^12 IL2StandardERC20.mint.selector ^13 IL2StandardERC20.burn.selector;14 return _interfaceId == firstSupportedInterface || _interfaceId == secondSupportedInterface;15 }Tümünü gösterERC-165 (opens in a new tab) bu şekilde çalışır. Her arayüz bir dizi desteklenen fonksiyondur ve bu fonksiyonların ABI fonksiyon seçicilerinin (opens in a new tab) özel veya (opens in a new tab) işlemi 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-function2 function mint(address _to, uint256 _amount) public virtual onlyL2Bridge {3 _mint(_to, _amount);45 emit Mint(_to, _amount);6 }78 // slither-disable-next-line external-function9 function burn(address _from, uint256 _amount) public virtual onlyL2Bridge {10 _burn(_from, _amount);1112 emit Burn(_from, _amount);13 }14}Tümünü gösterYalnı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: MIT2pragma solidity ^0.8.9;34/* Arayüz İçe Aktarmaları */5import { IL1StandardBridge } from "../../L1/messaging/IL1StandardBridge.sol";6import { IL1ERC20Bridge } from "../../L1/messaging/IL1ERC20Bridge.sol";7import { IL2ERC20Bridge } from "./IL2ERC20Bridge.sol";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:
- 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.
- 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/* Kütüphane İçe Aktarmaları */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";56/* Sözleşme İçe Aktarmaları */7import { IL2StandardERC20 } from "../../standards/IL2StandardERC20.sol";89/**10 * @title L2StandardBridge11 * @dev L2 Standart köprüsü, K1 ve K2 arasında ETH ve ERC20 geçişlerini sağlamak için12 * K1 Standart köprüsü ile birlikte çalışan bir sözleşmedir.13 * Bu sözleşme, K1 Standart köprüsüne yapılan yatırmaları duyduğunda yeni token'lar için bir basıcı olarak görev yapar.14 *15 * Bu sözleşme aynı zamanda çekme amaçlı token'ların yakıcısı olarak da görev yapar ve K116 * köprüsüne K1 fonlarını serbest bırakması için bilgi verir.17 */18contract L2StandardBridge is IL2ERC20Bridge, CrossDomainEnabled {19 /********************************20 * Harici Sözleşme Referansları *21 ********************************/2223 address public l1TokenBridge;Tümünü gösterK1 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.
12 /***************3 * Yapıcı *4 ***************/56 /**7 * @param _l2CrossDomainMessenger Bu sözleşme tarafından kullanılan alanlar arası mesajcı.8 * @param _l1TokenBridge Ana zincire dağıtılan K1 köprüsünün adresi.9 */10 constructor(address _l2CrossDomainMessenger, address _l1TokenBridge)11 CrossDomainEnabled(_l2CrossDomainMessenger)12 {13 l1TokenBridge = _l1TokenBridge;14 }1516 /***************17 * Çekme *18 ***************/1920 /**21 * @inheritdoc IL2ERC20Bridge22 */23 function withdraw(24 address _l2Token,25 uint256 _amount,26 uint32 _l1Gas,27 bytes calldata _data28 ) external virtual {29 _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _l1Gas, _data);30 }3132 /**33 * @inheritdoc IL2ERC20Bridge34 */35 function withdrawTo(36 address _l2Token,37 address _to,38 uint256 _amount,39 uint32 _l1Gas,40 bytes calldata _data41 ) external virtual {42 _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _l1Gas, _data);43 }Tümünü gösterBu 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.
12 /**3 * @dev Token'ı yakarak ve K1 token Ağ Geçidi'ne4 * çekme hakkında bilgi vererek çekme mantığını gerçekleştirir.5 * @param _l2Token Çekme işleminin başlatıldığı K2 token adresi.6 * @param _from K2'de para çekme işleminin çekileceği hesap.7 * @param _to K1'de para çekme işleminin verileceği hesap.8 * @param _amount Çekilecek token miktarı.9 * @param _l1Gas Kullanılmıyor, ancak potansiyel ileriye dönük uyumluluk considerations için dahil edilmiştir.10 * @param _data K1'e iletilecek isteğe bağlı veri. Bu veri yalnızca11 * harici sözleşmeler için bir kolaylık olarak sağlanır. Maksimum12 * uzunluğu zorunlu kılmanın yanı sıra, bu sözleşmeler içeriği hakkında hiçbir garanti vermez.13 */14 function _initiateWithdrawal(15 address _l2Token,16 address _from,17 address _to,18 uint256 _amount,19 uint32 _l1Gas,20 bytes calldata _data21 ) internal {22 // Bir çekme başlatıldığında, sonraki K2 kullanımını önlemek için23 // çekenin fonlarını yakarız24 // slither-disable-next-line reentrancy-events25 IL2StandardERC20(_l2Token).burn(msg.sender, _amount);Tümünü göster_from parametresine değil, sahte olması çok daha zor olan (bildiğim kadarıyla imkansız) msg.sender'a güvendiğimize dikkat edin.
12 // l1TokenBridge.finalizeERC20Withdrawal(_to, _amount) için çağrı verisi oluştur3 // slither-disable-next-line reentrancy-events4 address l1Token = IL2StandardERC20(_l2Token).l1Token();5 bytes memory message;67 if (_l2Token == Lib_PredeployAddresses.OVM_ETH) {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 _data7 );8 } else {9 message = abi.encodeWithSelector(10 IL1ERC20Bridge.finalizeERC20Withdrawal.selector,11 l1Token,12 _l2Token,13 _from,14 _to,15 _amount,16 _data17 );18 }1920 // Mesajı K1 köprüsüne gönder21 // slither-disable-next-line reentrancy-events22 sendCrossDomainMessage(l1TokenBridge, _l1Gas, message);2324 // slither-disable-next-line reentrancy-events25 emit WithdrawalInitiated(l1Token, _l2Token, msg.sender, _to, _amount, _data);26 }2728 /************************************29 * Çapraz zincir Fonksiyonu: Yatırma *30 ************************************/3132 /**33 * @inheritdoc IL2ERC20Bridge34 */35 function finalizeDeposit(36 address _l1Token,37 address _l2Token,38 address _from,39 address _to,40 uint256 _amount,41 bytes calldata _dataTümünü gösterBu fonksiyon, L1StandardBridge tarafından çağrılır.
1 ) external virtual onlyFromCrossDomainAccount(l1TokenBridge) {Mesajın kaynağının meşru olduğundan emin olun.
Bu, fonksiyon _mint'i çağırdığı ve köprünün K1'de sahip olduğu token'lar tarafından kapsanmayan token'ları vermek için kullanılabileceği için önemlidir.
1 // Hedef token'ın uyumlu olup olmadığını kontrol edin ve2 // K1'de yatırılan token'ın buradaki K2 yatırılan token gösterimiyle eşleştiğini doğrulayın3 if (4 // slither-disable-next-line reentrancy-events5 ERC165Checker.supportsInterface(_l2Token, 0x1d1d8b63) &&6 _l1Token == IL2StandardERC20(_l2Token).l1Token()Doğruluk testleri:
- Doğru arayüz destekleniyor
- K2 ERC-20 sözleşmesinin K1 adresi, token'ların K1 kaynağıyla eşleşiyor
1 ) {2 // Bir yatırma sonlandırıldığında, K2'deki hesaba aynı miktarda3 // token yatırırız.4 // slither-disable-next-line reentrancy-events5 IL2StandardERC20(_l2Token).mint(_to, _amount);6 // slither-disable-next-line reentrancy-events7 emit DepositFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);Doğruluk testlerini geçerse yatırmayı tamamlayın:
- Token'ları basma
- Uygun olayı yayınlama
1 } else {2 // Yatırılan K2 token'ı, K1 token'ının doğru adresi3 // konusunda aynı fikirde değil veya doğru arayüzü desteklemiyor.4 // Bu yalnızca kötü niyetli bir K2 token'ı olduğunda veya bir kullanıcı bir şekilde5 // yatırmak için yanlış K2 token adresi belirttiğinde gerçekleşmelidir.6 // Her iki durumda da süreci burada durdurur ve bir çekme7 // mesajı oluştururuz, böylece kullanıcılar bazı durumlarda fonlarını geri alabilir.8 // Kötü niyetli token sözleşmelerini tamamen önlemenin bir yolu yoktur, ancak bu9 // kullanıcı hatasını sınırlar ve bazı kötü niyetli sözleşme davranışlarını azaltır.Tümünü gösterBir kullanıcı yanlış K2 token adresini kullanarak tespit edilebilir bir hata yaptıysa, yatırmayı iptal etmek ve token'ları K1'e iade etmek istiyoruz. Bunu K2'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, // yatırmayı gönderene geri göndermek için _to ve _from'u burada değiştirdik6 _from,7 _amount,8 _data9 );1011 // Mesajı K1 köprüsüne gönder12 // slither-disable-next-line reentrancy-events13 sendCrossDomainMessage(l1TokenBridge, 0, message);14 // slither-disable-next-line reentrancy-events15 emit DepositFailed(_l1Token, _l2Token, _from, _to, _amount, _data);16 }17 }18}Tümünü gösterSonuç
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 K1 ü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) K1 varlıklarının azaldığını sezdiğinde K2'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.
Çalışmalarımdan daha fazlası için buraya bakın (opens in a new tab).
Sayfanın son güncellenmesi: 22 Ekim 2025