Optimism standart köprü sözleşmesine genel bakış
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
- Bir ERC-20 yatırılıyorsa, yatırımcı köprüye yatırılan tutarı harcaması için bir ödenek verir
- Yatıran, K1 köprüsünü (
depositERC20
,depositERC20To
,depositETH
veyadepositETHTo
) çağırır - 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
- 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
- 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
- K2 köprüsü, K2 üzerindeki ERC-20 token sözleşmesinin doğru olup olmadığını kontrol eder:
- K2 sözleşmesi, K1 karşılığının, token'ların K1'den geldiği ile aynı olduğunu bildiriyor
- K2 sözleşmesi, doğru arayüzü (ERC-165 kullanarak(opens in a new tab)) desteklediğini bildirir.
- 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
- Çeken kişi K2 köprüsünü çağırır (
draw
veyawithdrawTo
) - K2 köprüsü,
msg.sender
'a ait uygun sayıda token'ı yakar - K2 köprüsü, K1 köprüsünde
finalizeETHWithdrawal
veyafinalizeERC20Withdrawal
'ı çağırmak için etki alanları arası mesaj mekanizmasını kullanır
Katman 1
- K1 köprüsü,
finalizeETHWithdrawal
veyafinalizeERC20Withdrawal
çağrısının meşru olduğunu doğrular:- Etki alanları arası mesaj mekanizmasından geldi
- Aslen K2'deki köprüdendi
- 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: MITKopyala
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 IL1ERC20Bridge3 */4interface IL1ERC20Bridge {5 /**********6 * Events *7 **********/89 event ERC20DepositInitiated(Tümünü gösterKopyala
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 _data5 );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 _data8 );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.
12 /********************3 * Public Functions *4 ********************/56 /**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österKopyala
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 depositing4 * @param _l2Token Address of the L1 respective L2 ERC205 * @param _amount Amount of the ERC20 to deposit6 * @param _l2Gas Gas limit required to complete the deposit on L2.7 * @param _data Optional data to forward to L2. This data is provided8 * solely as a convenience for external contracts. Aside from enforcing a maximum9 * 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 _data17 ) external;Tümünü gösterKopyala
_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 depositing4 * @param _l2Token Address of the L1 respective L2 ERC205 * @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 provided9 * solely as a convenience for external contracts. Aside from enforcing a maximum10 * 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 _data19 ) external;Tümünü gösterKopyala
Bu fonksiyon neredeyse depositERC20
ile özdeştir, ama farklı bir adrese ERC-20 yollamanıza izin verir.
1 /*************************2 * Cross-chain Functions *3 *************************/45 /**6 * @dev Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the7 * 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 provided16 * solely as a convenience for external contracts. Aside from enforcing a maximum17 * 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 _data26 ) external;27}Tümünü gösterKopyala
Optimism'de çekme işlemleri (ve K2'den K1'e diğer tüm mesajlar) iki adımlı bir süreçtir:
- K2 üzerinde başlatıcı işlem.
- 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: MIT2pragma solidity >0.5.0 <0.9.0;34import "./IL1ERC20Bridge.sol";56/**7 * @title IL1StandardBridge8 */9interface IL1StandardBridge is IL1ERC20Bridge {10 /**********11 * Events *12 **********/13 event ETHDepositInitiated(14 address indexed _from,15 address indexed _to,16 uint256 _amount,17 bytes _data18 );Tümünü gösterKopyala
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 );67 /********************8 * Public Functions *9 ********************/1011 /**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;1819 /**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 _data29 ) external payable;3031 /*************************32 * Cross-chain Functions *33 *************************/3435 /**36 * @dev Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the37 * L1 ETH token. Since only the xDomainMessenger can call this function, it will never be called38 * before the withdrawal is finalized.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österKopyala
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: MIT2pragma solidity >0.5.0 <0.9.0;34/* 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 CrossDomainEnabled3 * @dev Helper contract for contracts performing cross-domain communications4 *5 * Compiler used: defined by inheriting contract6 */7contract CrossDomainEnabled {8 /*************9 * Variables *10 *************/1112 // Diğer etki alanından mesaj göndermek ve almak için kullanılan Messenger sözleşmesi.13 address public messenger;1415 /***************16 * Constructor *17 ***************/1819 /**20 * @param _messenger Address of the CrossDomainMessenger on the current layer.21 */22 constructor(address _messenger) {23 messenger = _messenger;24 }Tümünü gösterKopyala
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.
12 /**********************3 * Function Modifiers *4 **********************/56 /**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 is9 * authenticated to call this function.10 */11 modifier onlyFromCrossDomainAccount(address _sourceDomainAccount) {Tümünü gösterKopyala
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.
12 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.
12 _;3 }45 /**********************6 * Internal Functions *7 **********************/89 /**10 * Gets the messenger, usually from storage. This function is exposed in case a child contract11 * 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österKopyala
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.
12 /**3 * Sends a message to an account on another domain4 * @param _crossDomainTarget The intended recipient on the destination domain5 * @param _message The data to send to the target (usually calldata to a function with6 * `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 _messageTümünü gösterKopyala
Son olarak, fonksiyon diğer katmana bir mesaj gönderir.
1 ) internal {2 // slither-disable-next-line reentrancy-events, reentrancy-benignKopyala
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 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: MIT2pragma 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:
- Geri döndür
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 L1StandardBridge3 * @dev The L1 ETH and ERC20 Bridge is a contract which stores deposited L1 funds and standard4 * tokens that are in use on L2. It synchronizes a corresponding L2 Bridge, informing it of deposits5 * and listening to it for newly finalized withdrawals.6 *7 */8contract L1StandardBridge is IL1StandardBridge, CrossDomainEnabled {9 using SafeERC20 for IERC20;Tümünü gösterKopyala
Bu satır IERC20
arayüzünü her kullandığımızda SafeERC20
paketleyicisini kullanmasını belirtme yöntemimizdir.
12 /********************************3 * External Contract References *4 ********************************/56 address public l2TokenBridge;Kopyala
L2StandardBridge adresi.
12 // Maps L1 token to L2 token to balance of the L1 token deposited3 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.
12 /***************3 * Constructor *4 ***************/56 // 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 ******************/45 /**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-functionTümünü gösterKopyala
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:
- 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 }Kopyala
Bunlar, köprünün bilmesi gereken iki parametredir.
12 /**************3 * Depositing *4 **************/56 /** @dev Modifier requiring sender to be EOA. This check could be bypassed by a malicious7 * 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österKopyala
Bu, OpenZeppelin'in Address
yardımcı araçlarına ihtiyaç duymamızın nedenidir.
1 /**2 * @dev This function can be called with no data3 * to deposit an amount of ETH to the caller's balance on L2.4 * Since the receive function doesn't take data, a conservative5 * 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österKopyala
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 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österKopyala
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 of3 * 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 provided8 * solely as a convenience for external contracts. Aside from enforcing a maximum9 * 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 _data16 ) internal {17 // Construct calldata for finalizeDeposit call18 bytes memory message = abi.encodeWithSelector(Tümünü gösterKopyala
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 _data8 );Kopyala
Buradaki mesaj, şu parametrelerle finalizeDeposit
fonksiyonunu(opens in a new tab) çağırmaktır:
Parametre | Değer | Anlam |
---|---|---|
_l1Token | address(0) | K1'de ETH'yi (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 üzerindeki adres |
_to | _to | ETH'yi alan K2'deki adres |
amount | msg.value | Gönderilen wei miktarı (zaten köprüye gönderildi) |
_data | _data | Yatırmaya eklenecek ek tarih |
1 // Send calldata into L22 // slither-disable-next-line reentrancy-events3 sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);Kopyala
Mesajı, alan adları arası mesajcısı ile gönderin.
1 // slither-disable-next-line reentrancy-events2 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 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österKopyala
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 Token3 * 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 depositing6 * @param _l2Token Address of the L1 respective L2 ERC207 * @param _from Account to pull the deposit from on L18 * @param _to Account to give the deposit to on L29 * @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 provided12 * solely as a convenience for external contracts. Aside from enforcing a maximum13 * 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 _data23 ) internal {Tümünü gösterKopyala
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 future2 // withdrawals. safeTransferFrom also checks if the contract has code, so this will fail if3 // _from is an EOA or address(0).4 // slither-disable-next-line reentrancy-events, reentrancy-benign5 IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount);Kopyala
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 // 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 _data10 );1112 // Send calldata into L213 // 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österKopyala
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.
12 // slither-disable-next-line reentrancy-events3 emit ERC20DepositInitiated(_l1Token, _l2Token, _from, _to, _amount, _data);4 }56 /*************************7 * Cross-chain Functions *8 *************************/910 /**11 * @inheritdoc IL1StandardBridge12 */13 function finalizeETHWithdrawal(14 address _from,15 address _to,16 uint256 _amount,17 bytes calldata _dataTümünü gösterKopyala
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-events2 (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");23 // slither-disable-next-line reentrancy-events4 emit ETHWithdrawalFinalized(_from, _to, _amount, _data);Kopyala
Ç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österKopyala
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.
12 // When a withdrawal is finalized on L1, the L1 Bridge transfers the funds to the withdrawer3 // slither-disable-next-line reentrancy-events4 IERC20(_l1Token).safeTransfer(_to, _amount);56 // slither-disable-next-line reentrancy-events7 emit ERC20WithdrawalFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);8 }91011 /*****************************12 * Temporary - Migrating ETH *13 *****************************/1415 /**16 * @dev Adds ETH balance to the account. This is meant to allow for ETH17 * 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 the19 * old contract20 */21 function donateETH() external payable {}22}Tümünü gösterKopyala
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: MIT2pragma solidity ^0.8.9;34import { 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.
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}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: MIT2pragma solidity ^0.8.9;34import { 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";23contract 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.
12 /**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 _symbol13 ) ERC20(_name, _symbol) {14 l1Token = _l1Token;15 l2Bridge = _l2Bridge;16 }Tümünü gösterKopyala
Ö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, "Only L2 Bridge can mint and burn");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österKopyala
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-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österKopyala
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: MIT2pragma solidity ^0.8.9;34/* 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:
- 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/* 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";56/* Contract Imports */7import { IL2StandardERC20 } from "../../standards/IL2StandardERC20.sol";89/**10 * @title L2StandardBridge11 * @dev The L2 Standard bridge is a contract which works together with the L1 Standard bridge to12 * 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 Standard14 * bridge.15 * This contract also acts as a burner of the tokens intended for withdrawal, informing the L116 * bridge to release L1 funds.17 */18contract L2StandardBridge is IL2ERC20Bridge, CrossDomainEnabled {19 /********************************20 * External Contract References *21 ********************************/2223 address public l1TokenBridge;Tümünü gösterKopyala
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.
12 /***************3 * Constructor *4 ***************/56 /**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 }1516 /***************17 * Withdrawing *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österKopyala
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.
12 /**3 * @dev Performs the logic for withdrawals by burning the token and informing4 * 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 provided11 * solely as a convenience for external contracts. Aside from enforcing a maximum12 * 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 _data21 ) internal {22 // When a withdrawal is initiated, we burn the withdrawer's funds to prevent subsequent L223 // usage24 // slither-disable-next-line reentrancy-events25 IL2StandardERC20(_l2Token).burn(msg.sender, _amount);Tümünü gösterKopyala
_from
parametresine değil, sahte olması çok daha zor olan (bildiğim kadarıyla imkansız) msg.sender
parametresine güvendiğimize dikkat edin.
12 // Construct calldata for l1TokenBridge.finalizeERC20Withdrawal(_to, _amount)3 // slither-disable-next-line reentrancy-events4 address l1Token = IL2StandardERC20(_l2Token).l1Token();5 bytes memory message;67 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 _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 // Send message up to L1 bridge21 // 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 * Cross-chain Function: Depositing *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österKopyala
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 and2 // verify the deposited token on L1 matches the L2 deposited token representation here3 if (4 // slither-disable-next-line reentrancy-events5 ERC165Checker.supportsInterface(_l2Token, 0x1d1d8b63) &&6 _l1Token == IL2StandardERC20(_l2Token).l1Token()Kopyala
Doğruluk testleri:
- Doğru arayüz destekleniyor
- 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 of3 // tokens.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);Kopyala
Doğruluk testlerini geçerse yatırmayı tamamlayın:
- Token'ları basma
- Uygun olayı yayınlama
1 } else {2 // Either the L2 token which is being deposited-into disagrees about the correct address3 // 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 somehow5 // specified the wrong L2 token address to deposit into.6 // In either case, we stop the process here and construct a withdrawal7 // 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 limit9 // user error and mitigate some forms of malicious contract behavior.Tümünü gösterKopyala
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 sender6 _from,7 _amount,8 _data9 );1011 // Send message up to L1 bridge12 // 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österKopyala
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