Introduction au contrat de passerelle standard Optimism
Optimism(opens in a new tab) est un rollup optimiste. Les rollups Optimistics peuvent traiter les transactions à un prix beaucoup plus bas que le réseau principal Ethereum (également connu sous le nom de couche 1 ou L1), car les transactions sont traitées uniquement par quelques nœuds en lieu et place de tous les nœuds du réseau. En même temps, les données sont toutes écrites sur L1 afin que tout puisse être prouvé et reconstruit avec toutes les garanties d'intégrité et de disponibilité du réseau principal.
Pour utiliser les actifs L1 sur Optimism (ou n'importe quel autre L2), les actifs doivent être connectés. Une façon d'y arriver est pour les utilisateurs de verrouiller les actifs (ETH et les jetons ERC-20 sont les plus communs) sur L1 et de recevoir des actifs équivalents à utiliser sur L2. Finalement, celui qui se retrouve avec souhaitera peut-être les ramener en L1. En faisant cela, les actifs sont brûlés sur L2 puis redistribués à l'utilisateur sur L1.
C'est ainsi que fonctionne la passerelle standard Optimism(opens in a new tab). Dans cet article, nous passerons en revue le code source de cette passerelle pour comprendre comment elle fonctionne et l'étudier comme un exemple de code Solidity parfaitement écrit.
Flux de contrôle
La passerelle dispose de deux flux principaux :
- Dépôt (de L1 vers L2)
- Retrait (de L2 vers L1)
Flux de dépôt
Couche 1
- En cas de dépôt d'un ERC-20, le déposant affecte à la passerelle une provision pour dépenser le montant déposé
- Le déposant appelle la passerelle L1 (
depositERC20
,depositERC20To
,depositETH
, oudepositETHTo
) - La passerelle L1 prend possession de l'actif connecté
- ETH : l'actif est transféré par le déposant dans le cadre de l'appel
- ERC-20 : l'actif est transféré par la passerelle à elle-même en utilisant la provision fournie par le déposant
- La passerelle de connexion L1 utilise le mécanisme de message inter-domaine pour appeler
finalizeDeposit
sur la passerelle de connexion L2
Couche 2
- La passerelle de connexion L2 vérifie que l'appel
finalizeDeposit
est légitime :- Provient du contrat de message inter-domaine
- Était à l'origine en provenance de la passerelle de connexion sur L1
- La passerelle de connexion L2 vérifie si le contrat de jeton ERC-20 sur L2 est le bon :
- Le contrat L2 signale que son homologue L1 est identique à celui dont les jetons provenaient sur L1
- Le contrat L2 signale qu'il prend en charge l'interface correcte (en utilisant ERC-165(opens in a new tab)).
- Si le contrat L2 est le bon, appelez-le pour frapper le nombre approprié de jetons à l'adresse appropriée. Sinon, commencez un processus de retrait pour permettre à l'utilisateur de réclamer les jetons sur L1.
Flux de retrait
Couche 2
- Le retirant appelle la passerelle de connexion L2 (
withdraw
ouwithdrawTo
) - La passerelle de connexion L2 brûle le nombre approprié de jetons appartenant à
msg.sender
- La passerelle de connexion L2 utilise le mécanisme de message inter-domaine pour appeler
finalizeETHWithdrawal
oufinalizeERC20Withdrawal
de la passerelle L1
Couche 1
- La passerelle de connexion L1 vérifie que l'appel à
finalizeETHWithal
ou àfinalizeERC20Withal
est légitime :- Provient du mécanisme de message inter-domaine
- Était à l'origine en provenance de la passerelle de connexion sur L2
- La passerelle L1 transfère l'actif approprié (ETH ou ERC-20) à l'adresse appropriée
Code de la couche 1
C'est le code qui s'exécute sur L1, le réseau principal Ethereum.
IL1ERC20Bridge
Cette interface est définie ici(opens in a new tab). Elle comprend les fonctions et les définitions requises pour la connexion en passerelle des jetons ERC-20.
1// SPDX-License-Identifier: MITCopier
La plupart du code Optimism est publié sous la licence MIT(opens in a new tab).
1pragma solidity >0.5.0 <0.9.0;Copier
Lors de l'écriture de cet article, la dernière version de Solidity était 0.8.12. Jusqu'à la publication de la version 0.9.0, nous ne saurons pas si ce code est compatible ou non.
1/**2 * @title IL1ERC20Bridge3 */4interface IL1ERC20Bridge {5 /**********6 * Events *7 **********/89 event ERC20DepositInitiated(Afficher toutCopier
Dans le terminologique des passerelles pour Optimism deposit signifie transférer de L1 vers L2, et withdrawal signifie un transfert de L2 vers L1.
1 address indexed _l1Token,2 address indexed _l2Token,Copier
Dans la plupart des cas, l'adresse d'un ERC-20 sur L1 n'est pas la même que celle de l'équivalent ERC-20 sur L2. Vous pouvez consulter la liste des adresses de jetons ici(opens in a new tab). L'adresse avec chainId
1 est sur L1 (le réseau principal) et l'adresse avec chainId
10 est sur L2 (Optimism). Les deux autres valeurs chainId
sont pour le réseau de test Kovan (42) et le réseau de test Optimistic Kovan (69).
1 address indexed _from,2 address _to,3 uint256 _amount,4 bytes _data5 );Copier
Il est possible d'ajouter des notes aux transferts, auquel cas elles sont ajoutées aux événements qui les signalent.
1 event ERC20WithdrawalFinalized(2 address indexed _l1Token,3 address indexed _l2Token,4 address indexed _from,5 address _to,6 uint256 _amount,7 bytes _data8 );Copier
Le même contrat passerelle gère les transferts dans les deux sens. Dans le cas de la passerelle L1, cela signifie l'initialisation des dépôts et la finalisation des retraits.
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);Afficher toutCopier
Cette fonction n'est pas vraiment nécessaire, car sur L2 c'est un contrat prédéployé, donc il sera toujours à l'adresse 0x420000000000000000000000000000000000000000000010
. Il est ici pour la symétrie avec la passerelle L2, car l'adresse de la passerelle de connexion L1 n'est pas à connaître.
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;Afficher toutCopier
Le paramètre _l2Gas
est le montant de gaz L2 que la transaction est autorisée à dépenser. Jusqu'à une certaine limite (haute), c'est gratuit(opens in a new tab), donc à moins que le contrat ERC-20 ne fasse quelque chose de vraiment étrange lors de la frappe, il ne devrait pas y avoir de problème. Cette fonction prend en charge le scénario commun où un utilisateur relie les actifs à la même adresse sur une blockchain différente.
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;Afficher toutCopier
Cette fonction est presque identique à depositERC20
, mais elle vous permet d'envoyer l'ERC-20 à une adresse différente.
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}Afficher toutCopier
Les retraits (et autres messages de L2 vers L1) dans Optimism sont des processus en deux étapes :
- Une transaction d'initialisation sur L2.
- Une transaction de finalisation ou de réclamation sur L1. Cette transaction doit être réalisée après la période de contestation des défauts(opens in a new tab) pour que la transaction L2 se termine.
IL1StandardBridge
Cette interface est définie ici(opens in a new tab). Ce fichier contient des définitions d'événements et de fonctions pour ETH. Ces définitions sont très similaires à celles définies ci-dessus dans IL1ERC20Bridge
pour ERC-20.
L'interface de passerelle est divisée entre deux fichiers puisque certains jetons ERC-20 nécessitent un traitement personnalisé et ne peuvent pas être traités par la passerelle de connexion standard. De cette façon, la passerelle personnalisée de connexion qui gère un tel jeton peut implémenter IL1ERC20Bridge
et ne pas nécessiter une passerelle pour ETH.
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 );Afficher toutCopier
Cet événement est presque identique à la version ERC-20 (ERC20DepositInitiated
), mais sans les adresses de jeton L1 et L2. Il en va de même pour les autres événements et les fonctions.
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}Afficher toutCopier
CrossDomainEnabled
Ce contrat(opens in a new tab) a hérité des deux passerelles (L1 et L2) pour envoyer des messages à l'autre couche.
1// SPDX-License-Identifier: MIT2pragma solidity >0.5.0 <0.9.0;34/* Interface Imports */5import { ICrossDomainMessenger } from "./ICrossDomainMessenger.sol";Copier
Cette interface(opens in a new tab) indique au contrat comment envoyer des messages à l'autre couche, en utilisant le messager inter-domaine. Cette messagerie transversale de domaine est un autre système à part entière et mériterait son propre article que j'espère écrire à l'avenir.
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 // Messenger contract used to send and receive messages from the other domain.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 }Afficher toutCopier
Le seul paramètre que le contrat a besoin de connaître est l'adresse du messager de domaines croisés sur cette couche. Ce paramètre est défini une seule fois, dans le constructeur, et ne change jamais.
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) {Afficher toutCopier
La messagerie inter-domaine est accessible par n'importe quel contrat sur la blockchain où elle est exécutée (soit le réseau principal Ethereum, soit Optimism). Mais nous avons besoin du pont de chaque côté pour faire confiance uniquement à certains messages qui viennent de la passerelle de l'autre côté.
1 require(2 msg.sender == address(getCrossDomainMessenger()),3 "OVM_XCHAIN: messenger contract unauthenticated"4 );Copier
Seuls les messages du messager inter-domaine approprié (messenger
, comme indiqué ci-dessous) peuvent être fiables.
12 require(3 getCrossDomainMessenger().xDomainMessageSender() == _sourceDomainAccount,4 "OVM_XCHAIN: wrong sender of cross-domain message"5 );Copier
La façon dont la messagerie inter-domaine fournit l'adresse qui a envoyé un message avec l'autre couche est la fonction .xDomainMessageSender()
(opens in a new tab). Tant qu'elle est appelée dans la transaction qui a été initiée par le message, elle peut fournir ces informations.
Nous devons nous assurer que le message que nous avons reçu provient bien de l'autre passerelle.
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 }Afficher toutCopier
Cette fonction retourne le messager inter-domaine. Nous utilisons une fonction plutôt que la variable messenger
pour permettre aux contrats qui héritent de celui-ci d'utiliser un algorithme pour spécifier quel messager de domaine croisé utiliser.
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 _messageAfficher toutCopier
Enfin, la fonction qui envoie un message à l'autre couche.
1 ) internal {2 // slither-disable-next-line reentrancy-events, reentrancy-benignCopier
Slither(opens in a new tab) est un analyseur statique Optimism qui fonctionne sur chaque contrat pour rechercher des vulnérabilités et d'autres problèmes potentiels. Dans notre cas, la ligne suivante déclenche deux vulnérabilités :
1 getCrossDomainMessenger().sendMessage(_crossDomainTarget, _message, _gasLimit);2 }3}Copier
Dans notre cas, nous ne devons pas nous inquiéter de la réentrance, nous savons que getCrossDomainMessenger()
retourne une adresse digne de confiance, même si Slither n'a aucun moyen de le savoir.
Le contrat de passerelle L1
Le code source de ce contrat se trouve ici(opens in a new tab).
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;Copier
Les interfaces peuvent faire partie d'autres contrats, elles doivent donc supporter un large éventail de versions Solidity. Mais la passerelle en elle-même est notre contrat, et nous pouvons être stricts quant à la version de Solidity utilisée.
1/* Interface Imports */2import { IL1StandardBridge } from "./IL1StandardBridge.sol";3import { IL1ERC20Bridge } from "./IL1ERC20Bridge.sol";Copier
IL1ERC20Bridge et IL1StandardBridge sont expliqués ci-dessus.
1import { IL2ERC20Bridge } from "../../L2/messaging/IL2ERC20Bridge.sol";Copier
Cette interface(opens in a new tab) nous permet de créer des messages pour contrôler la passerelle de connexion standard sur L2.
1import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";Copier
Cette interface(opens in a new tab) nous permet de piloter les contrats ERC-20. Vous pouvez en savoir plus sur ce sujet ici.
1/* Library Imports */2import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";Copier
Comme expliqué ci-dessus, ce contrat est utilisé pour la messagerie intercouche.
1import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";Copier
Lib_PredeployAdresses
(opens in a new tab)dispose des adresses pour les contrats L2 qui ont toujours la même adresse. Cela inclut la passerelle standard sur la L2.
1import { Address } from "@openzeppelin/contracts/utils/Address.sol";Copier
Utilitaires d'adresses OpenZeppelin(opens in a new tab). Ils servent à distinguer les adresses contractuelles de celles appartenant à des comptes propriétaires externes (EOA).
Notez que ce n'est pas une solution parfaite, car il n'y a aucun moyen de distinguer les appels directs de ceux réalisés par le constructeur d'un contrat, mais au moins cela nous permet d'identifier et de prévenir certaines erreurs utilisateurs courantes.
1import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";Copier
La norme ERC-20(opens in a new tab) prend en charge deux manières pour un contrat de signaler un échec :
- Rétablir
- Renvoyer
false
La gestion des deux cas rendrait notre code plus compliqué donc à la place nous utilisons le SafeERC20
d'OpenZeppelin(opens in a new tab), qui s'assure que tous les échecs aboutissent à un rétablissement(opens in a new tab).
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;Afficher toutCopier
Cette ligne montre comment nous spécifions d'utiliser le wrapper SafeERC20
chaque fois que nous utilisons l'interface IERC20
.
12 /********************************3 * External Contract References *4 ********************************/56 address public l2TokenBridge;Copier
L'adresse de L2StandardBridge.
12 // Maps L1 token to L2 token to balance of the L1 token deposited3 mapping(address => mapping(address => uint256)) public deposits;Copier
Un double mapping(opens in a new tab) comme celui-ci est la façon dont vous définissez un rare tableau bidimensionnel(opens in a new tab). Les valeurs dans cette structure de données sont identifiées comme deposit[L1 token addr][L2 token addr]
. La valeur par défaut est zéro. Seules les cellules qui sont définies à une valeur différente sont écrites pour le stockage.
12 /***************3 * Constructor *4 ***************/56 // This contract lives behind a proxy, so the constructor parameters will go unused.7 constructor() CrossDomainEnabled(address(0)) {}Copier
Pour pouvoir mettre à jour ce contrat sans avoir à copier toutes les variables dans le stockage. Pour cela, nous utilisons un Proxy
(opens in a new tab), un contrat qui utilise delegatecall
(opens in a new tab) pour transférer des appels à un contact distinct dont l'adresse est stockée par le contrat de proxy (lorsque vous mettez à jour, vous ordonnez au proxy de changer cette adresse). Lorsque vous utilisez delegatecall
le stockage reste le stockage du contrat appelant, donc les valeurs de toutes les variables d'état du contrat ne sont pas affectées.
Un effet de cette pratique est que le stockage du contrat qui est appelé de delegatecall
n'est pas utilisé et donc les valeurs du constructeur qui lui sont passées n'ont pas d'importance. C'est la raison pour laquelle nous pouvons fournir une valeur absurde au constructeur CrossDomainEnabled
. C'est aussi la raison pour laquelle l'initialisation ci-dessous est séparée du constructeur.
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-functionAfficher toutCopier
Ce test Slither(opens in a new tab) identifie les fonctions qui ne sont pas appelées à partir du code du contrat et pourrait donc être déclarées external
au lieu de public
. Le coût en gaz des fonctions external
peut être diminué, car elles peuvent être fournies avec des paramètres dans le calldata. Les fonctions déclarées public
doivent être accessibles depuis le contrat. Les contrats ne peuvent pas modifier leurs propres calldata ainsi, les paramètres doivent être en mémoire. Lorsqu'une telle fonction est appelée en externe, il est nécessaire de copier le calldata dans la mémoire, ce qui coûte du gaz. Dans ce cas, la fonction n'est appelée qu'une seule fois, donc son inefficacité n'a pas d'importance pour nous.
1 function initialize(address _l1messenger, address _l2TokenBridge) public {2 require(messenger == address(0), "Contract has already been initialized.");Copier
La fonction initialize
ne doit être appelée qu'une seule fois. Si l'adresse du messager inter-domaine L1 ou du jeton de connexion L2 changent, nous créons un nouveau proxy et une nouvelle passerelle qui l'appellera. Il est peu probable que cela se produise sauf lorsque le système dans son entier est mis à jour, ce qui est très rare.
Notez que cette fonction ne dispose d'aucun mécanisme qui délimite qui peut l'appeler. Cela signifie qu'en théorie, un attaquant pourrait attendre que nous déployions le proxy et la première version de la passerelle de connexion et donc front-run(opens in a new tab) pour accéder à la fonction initialize
avant que l'utilisateur légitime ne le fasse. Il existe deux méthodes pour éviter cela :
- Si les contrats ne sont pas déployés directement par un EOA, mais dans une transaction qui contient un autre contrat les créant(opens in a new tab) l'ensemble du processus peut être atomique, et se terminer avant que toute autre transaction soit exécutée.
- Si l'appel légitime à
initialize
échoue, il est toujours possible d'ignorer le proxy et la passerelle de connexion nouvellement créé et d'en créer de nouveaux.
1 messenger = _l1messenger;2 l2TokenBridge = _l2TokenBridge;3 }Copier
Ce sont les deux paramètres que la passerelle a besoin de connaître.
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 }Afficher toutCopier
C'est la raison pour laquelle nous avions besoin des utilitaires d'Address
d'OpenZeppelin.
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 }Afficher toutCopier
Cette fonction existe à des fins de test. Notez qu'il n'apparaît pas dans les définitions d'interface - elle n'est pas prévue pour une utilisation normale.
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 }Afficher toutCopier
Ces deux fonctions sont enveloppées autour de _initiateETHDeposit
, la fonction qui gère le dépôt réel ETH.
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(Afficher toutCopier
La façon dont les messages transversaux fonctionnent est que le contrat de destination est appelé avec le message comme ses calldata. Les contrats Solidity interprètent toujours si leurs calldata sont conformes aux spécifications de l'ABI(opens in a new tab). La fonction Solidity abi.encodeWithSelector
(opens in a new tab) crée ces calldata.
1 IL2ERC20Bridge.finalizeDeposit.selector,2 address(0),3 Lib_PredeployAddresses.OVM_ETH,4 _from,5 _to,6 msg.value,7 _data8 );Copier
Le message ici est destiné à appeler la fonction finalizeDeposit
(opens in a new tab) avec ces paramètres :
Paramètre | Valeur | Signification |
---|---|---|
_l1Token | address(0) | Valeur spéciale pour représenter ETH (qui n'est pas un jeton ERC-20) sur L1 |
_l2Token | Lib_PredeployAddresses.OVM_ETH | Le contrat L2 qui gère ETH sur Optimism, 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000 (ce contrat est destiné à un usage Optimism uniquement interne) |
_from | _from | L'adresse sur L1 qui envoie l'ETH |
_to | _to | L'adresse sur L2 qui reçoit l'ETH |
amount | msg.value | Montant de Wei envoyé (qui a déjà été envoyé sur la passerelle) |
_data | _data | Date supplémentaire à joindre au dépôt |
1 // Send calldata into L22 // slither-disable-next-line reentrancy-events3 sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);Copier
Envoyez le message à travers le messager inter-domaine.
1 // slither-disable-next-line reentrancy-events2 emit ETHDepositInitiated(_from, _to, msg.value, _data);3 }Copier
Émettre un événement pour informer toute application décentralisée qui écoute ce transfert.
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 }Afficher toutCopier
Ces deux fonctions sont enveloppées autour de _initiateERC20Deposit
, la fonction qui gère le dépôt réel ETH.
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 {Afficher toutCopier
Cette fonction est similaire à _initiateETHDeposit
ci-dessus, avec quelques différences importantes. La première différence est que cette fonction reçoit les adresses de jetons et le montant à transférer en tant que paramètres. Dans le cas d'ETH, l'appel à la passerelle comprend déjà le transfert de l'actif à la passerelle de connexion (msg.value
).
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);Copier
Les transferts de jetons ERC-20 suivent un processus différent de celui pour ETH :
- L'utilisateur (
_from
) apporte une provision à la passerelle de connexion pour transférer les jetons appropriés. - L'utilisateur appelle la passerelle de connexion avec l'adresse du contrat de jeton, le montant, etc.
- La passerelle transfère les jetons (à elle-même) dans le cadre du processus de dépôt.
La première étape peut se produire dans une transaction séparée des deux dernières. Cependant, front-running n'est pas un problème car les deux fonctions qui appellent _initiateERC20Deposit
(depositERC20
et depositERC20To
) n'appellent cette fonction qu'avec msg.sender
en tant que paramètre _from
.
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;Afficher toutCopier
Ajoute le nombre de jetons déposés à la structure de données deposits
. Il pourrait y avoir plusieurs adresses sur L2 qui correspondent au même jeton L1 ERC-20, donc il n'est pas suffisant d'utiliser le solde sur la passerelle de jetons L1 ERC-20 pour garder une trace des dépôts.
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 _dataAfficher toutCopier
La passerelle de connexion L2 envoie un message au messager du domaine transversal L2 qui fait que le messager du domaine transversal L1 appelle cette fonction (lorsque la transaction qui finalise le message(opens in a new tab) est soumise sur L1, bien sûr).
1 ) external onlyFromCrossDomainAccount(l2TokenBridge) {Copier
Assurez-vous qu'il s'agit d'un message légitime provenant de la messagerie inter-domaine et originaire de la passerelle de jeton L2. Cette fonction est utilisée pour retirer l'ETH de la passerelle de connexion ainsi, nous devons nous assurer qu'elle n'est appelée que par l'appelant autorisé.
1 // slither-disable-next-line reentrancy-events2 (bool success, ) = _to.call{ value: _amount }(new bytes(0));Copier
Le moyen de transférer de l'ETH est d'appeler le destinataire avec le montant de wei dans le msg.value
.
1 require(success, "TransferHelper::safeTransferETH: ETH transfer failed");23 // slither-disable-next-line reentrancy-events4 emit ETHWithdrawalFinalized(_from, _to, _amount, _data);Copier
Émettre un événement à propos du retrait.
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) {Afficher toutCopier
Cette fonction est similaire à finalizeETHWithal
ci-dessus, avec les changements nécessaires pour les jetons ERC-20.
1 deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] - _amount;Copier
Mettre à jour la structure de données deposits
.
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}Afficher toutCopier
Il y a eu une implémentation antérieure de la passerelle. Lorsque nous sommes passés de l'implémentation à celle-ci, nous avons dû déplacer tous les actifs. Les jetons ERC-20 peuvent juste être déplacés. Cependant, pour transférer l'ETH à un contrat, vous avez besoin de l'approbation de ce contrat, ce que donateETH
nous fournit.
Jetons ERC-20 sur L2
Pour qu'un jeton ERC-20 s'intègre dans la passerelle standard, il doit permettre à la passerelle de connexion standard, et uniquement la passerelle standard, de frapper des jetons. Ceci est nécessaire, car les passerelles doivent s'assurer que le nombre de jetons circulant sur Optimism est égal au nombre de jetons verrouillés à l'intérieur du contrat passerelle L1. S'il existe trop de jetons sur L2, certains utilisateurs seraient incapables de récupérer leurs actifs sur L1. Au lieu d'une passerelle de confiance, nous recréerions en fait une banque de réserve fractionnaire(opens in a new tab). S'il y a trop de jetons sur L1, certains de ces jetons resteraient bloqués à l'intérieur du contrat passerelle pour toujours puisqu'il n'y a aucun moyen de les libérer sans brûler les jetons L2.
IL2StandardERC20
Chaque jeton ERC-20 sur L2 qui utilise la passerelle standard doit fournir cette interface(opens in a new tab), qui possède les fonctions et les événements dont la passerelle standard a besoin.
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;34import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";Copier
L'interface standard ERC-20(opens in a new tab) ne dispose ni des fonctions mint
ni burn
. Ces méthodes ne sont pas requises par le standard ERC-20(opens in a new tab), qui laisse les mécanismes non spécifiés pour créer et détruire des jetons.
1import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";Copier
L'interface ERC-165(opens in a new tab) est utilisée pour spécifier quelles fonctions sont proposées par un contrat. Vous pouvez lire le standard ici(opens in a new tab).
1interface IL2StandardERC20 is IERC20, IERC165 {2 function l1Token() external returns (address);Copier
Cette fonction fournit l'adresse du jeton L1 qui est relié à ce contrat. Notez que nous n'avons pas de fonction similaire dans la direction opposée. Nous devons être en mesure de relier tout jeton L1, que la prise en charge L2 ait été planifiée ou non lorsqu'il a été implémenté.
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}Copier
Les fonctions et événements à frapper (créer) et brûler (détruire) des jetons. La passerelle de connexion devrait être la seule entité qui puisse exécuter ces fonctions pour s'assurer que le nombre de jetons est correct (égal au nombre de jetons verrouillés sur L1).
L2StandardERC20
Ceci est notre implémentation de l'interface IL2StandardERC20
(opens in a new tab). À moins que vous n'ayez besoin d'une sorte de logique personnalisée, vous devriez utiliser celle-ci.
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;34import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";Copier
Le contrat OpenZeppelin ERC-20(opens in a new tab). Optimism ne souhaite pas réinventer de la roue, surtout lorsque la roue est bien contrôlée et a besoin d'être suffisamment digne de confiance pour détenir des actifs.
1import "./IL2StandardERC20.sol";23contract L2StandardERC20 is IL2StandardERC20, ERC20 {4 address public l1Token;5 address public l2Bridge;Copier
Ce sont les deux paramètres de configuration supplémentaires dont nous avons besoin et que ERC-20 ne réalise normalement pas.
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 }Afficher toutCopier
En premier lieu, appelez le constructeur pour le contrat dont nous héritons de (ERC20(_name, _symbol)
) et définissez ensuite vos propres variables.
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 }Afficher toutCopier
C'est ainsi que fonctionne ERC-165(opens in a new tab). Chaque interface est un certain nombre de fonctions supportées, et est identifiée comme ABIexclusive ou(opens in a new tab) des sélecteurs de fonctions ABI(opens in a new tab) pour ces fonctions.
La passerelle de connexion L2 utilise ERC-165 comme vérification garantissant que le contrat ERC-20 auquel des actifs sont envoyés est un IL2StandardERC20
.
Remarque : Il n'existe rien pour empêcher le contrat dévoyé de fournir de fausses réponses à supportsInterface
ainsi, il s'agit donc d'un mécanisme de vérification et non pas un mécanisme de sécurité.
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}Afficher toutCopier
Seul la passerelle L2 est autorisée à frapper et à brûler des actifs.
_mint
et _burn
sont définis dans le contrat OpenZeppelin ERC-20. Ce contrat ne les expose pas en externe, parce que les conditions de frappe et de brûlage des jetons sont aussi variées que le nombre de façons d'utiliser ERC-20.
Code de passerelle L2
Il s'agit du code qui exécute la passerelle de connexion sur Optimism. La source de ce contrat se trouve ici(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";Copier
L'interface IL2ERC20Bridge(opens in a new tab) est très similaire à l'équivalent L1 que nous avons vu ci-dessus. Il existe deux différences significatives :
- Sur L1, vous initiez des dépôts et finalisez des retraits. Ici, vous initiez des retraits et finalisez les dépôts.
- Sur L1, il est nécessaire de faire une distinction entre les jetons ETH et ERC-20. Sur L2, nous pouvons utiliser les mêmes fonctions pour les deux, parce que les soldes ETH en interne sur Optimism sont traités comme un jeton ERC-20 avec l'adresse 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000(opens in a new tab).
1/* Library Imports */2import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";3import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";4import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";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;Afficher toutCopier
Conserver la trace de l'adresse de la passerelle de connexion L1. Notez que contrairement à l'équivalent pour L1, ici nous _ avons besoin _de cette variable. L'adresse de la passerelle L1 n'est pas connue à l'avance.
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 }Afficher toutCopier
Ces deux fonctions initient les retraits. Notez qu'il n'y a pas besoin de spécifier l'adresse de jeton L1. Les jetons L2 doivent nous indiquer l'adresse de l'équivalent L1.
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);Afficher toutCopier
Notez que nous ne sommes pas reliés au paramètre _from
mais à msg.sender
qui est beaucoup plus difficile à contrefaire (même impossible, à ma connaissance).
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) {Copier
Sur L1, il est nécessaire de faire une distinction entre les jetons ETH et ERC-20.
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 _dataAfficher toutCopier
Cette fonction est appelée via L1StandardBridge
.
1 ) external virtual onlyFromCrossDomainAccount(l1TokenBridge) {Copier
Assurez-vous que la source du message est fiable. C'est important car cette fonction appelle _mint
et peut être utilisée pour donner des jetons qui ne sont pas couverts par des jetons que la passerelle possède sur L1.
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()Copier
Contrôle de cohérence :
- L'interface correcte est prise en charge
- L'adresse du contrat L2 ERC-20 du L1 correspond à la source L1 des jetons
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);Copier
Si les contrôles de cohérence sont effectués, finalisez le dépôt :
- Frapper les jetons
- Émettre l'événement approprié
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.Afficher toutCopier
Si un utilisateur a fait une erreur détectable en utilisant la mauvaise adresse de jeton L2, nous souhaitons annuler le dépôt et retourner les jetons sur L1. La seule façon de le faire à partir de L2 est d'envoyer un message qui devra attendre la période problématique de défaut mais c'est beaucoup mieux pour l'utilisateur que de perdre les jetons définitivement.
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}Afficher toutCopier
Conclusion
La passerelle standard est le mécanisme le plus souple pour les transferts d'actifs. Cependant, parce qu'il est si générique, ce n'est pas toujours le mécanisme le plus facile à utiliser. Spécialement pour les retraits, la plupart des utilisateurs préfèrent utiliser des passerelles tierces(opens in a new tab) qui n'attendent pas la période problématique et ne nécessitent pas de preuve de Merkle pour finaliser le retrait.
Ces passerelles fonctionnent généralement en ayant des actifs sur L1 qu'ils fournissent immédiatement moyennant un petit supplément (souvent inférieur au coût du gaz pour un retrait standard d'une passerelle). Quand la passerelle (ou la personne qui la gère) prévoit d'être en deçà des actifs L1, elle transfère suffisamment d'actifs de L2. Comme il s'agit de retraits très importants, le coût de retrait est amorti sur une somme importante et représente un pourcentage beaucoup plus faible.
Espérons que cet article vous aide à mieux comprendre comment la couche 2 fonctionne, et comment écrire du code Solidity clair et sécurisé.
Dernière modification: @lukassim(opens in a new tab), 26 avril 2024