Guida del ponte standard di Optimism per contratti
Optimism(opens in a new tab) è un Rollup ottimistico. I rollup ottimistici possono elaborare le transazioni a un prezzo molto più basso di quello della rete principale di Ethereum (nota anche come livello 1 o L1), poiché le transazioni sono elaborate solo da alcuni nodi, invece che da ogni nodo sulla rete. Allo stesso tempo, i dati vengono tutti scritti nel L1, così che tutto possa essere provato e ricostruito con le garanzie d'integrità e disponibilità della rete principale.
Per usare le risorse del L1 su Optimism (o su qualsiasi altro L2), le risorse devono essere collegate con un ponte. Un modo per farlo è che gli utenti blocchino le risorse (ETH e token ERC-20 sono le più comuni) nel L1 e ricevano le risorse equivalenti da usare nel L2. In definitiva, chiunque le riceva potrebbe volerle ricollegare al L1. Così facendo, le risorse sono bruciate nel L2 e poi rilasciate nuovamente all'utente nel L1.
Questo è il modo in cui funziona il ponte standard di Optimism(opens in a new tab). In questo articolo esaminiamo il codice sorgente di quel ponte, per vedere come funziona e per studiarlo come un esempio di codice di Solidity ben scritto.
Flussi di controllo
Il ponte ha due flussi principali:
- Deposito (da L1 a L2)
- Prelievo (da L2 a L1)
Flusso di deposito
Livello 1
- In caso di deposito di un ERC-20, il depositante concede al ponte un'indennità per spendere l'importo depositato
- Il depositante chiama il ponte L1 (
depositERC20
,depositERC20To
,depositETH
, odepositETHTo
) - Il ponte L1 prende possesso della risorsa collegata
- ETH: la risorsa è trasferita dal depositante all'interno della chiamata
- ERC-20: la risorsa è trasferita dal ponte a sé stessa, usando l'indennità fornita dal depositante
- Il ponte L1 usa il meccanismo di messaggio interdominio per chiamare
finalizeDeposit
sul ponte L2
Livello 2
- Il ponte L2 verifica che la chiamata a
finalizeDeposit
sia legittima:- Proviene dal contratto di messaggistica interdominio
- Originariamente proveniva dal ponte su L1
- Il ponte L2 verifica se il contratto del token ERC-20 su L2 è quello corretto:
- Il contratto L2 segnala che la sua controparte del L1 è uguale a quella da cui i token provenivano su L1
- Il contratto L2 segnala che supporta l'interfaccia corretta (che usa ERC-165(opens in a new tab)).
- Se il contratto L2 è quello corretto, chiamalo per coniare il numero di token appropriato all'indirizzo corretto. Altrimenti, avvia un processo di prelievo per consentire all'utente di rivendicare i token su L1.
Flusso di prelievo
Livello 2
- Il prelevante chiama il ponte L2 (
withdraw
owithdrawTo
) - Il ponte L2 brucia il giusto numero di token appartenente a
msg.sender
- Il ponte L2 usa il meccanismo di messaggio interdominio per chiamare
finalizeETHWithdrawal
ofinalizeERC20Withdrawal
sul ponte L1
Livello 1
- Il ponte L1 verifica che la chiamata a
finalizeETHWithdrawal
ofinalizeERC20Withdrawal
sia legittima:- Proviene dal meccanismo di messaggistica interdominio
- Originariamente proveniva dal ponte su L2
- Il ponte L1 trasferisce la risorsa appropriata (ETH o ERC-20) all'indirizzo appropriato
Codice del Livello 1
Questo è il codice eseguito su L1, la rete principale di Ethereum.
IL1ERC20Bridge
Quest'interfaccia è definita qui(opens in a new tab). Include le funzioni e definizioni richieste per collegare i token ERC-20.
1// SPDX-License-Identifier: MIT2Copia
Gran parte del codice di Optimism è rilasciato sotto la licenza MIT(opens in a new tab).
1pragma solidity >0.5.0 <0.9.0;2Copia
Al momento della scrittura, l'ultima versione di Solidity è la 0.8.12. Fino al rilascio della versione 0.9.0, non sappiamo se questo codice è compatibile con esso o meno.
1/**2 * @title IL1ERC20Bridge3 */4interface IL1ERC20Bridge {5 /**********6 * Events *7 **********/89 event ERC20DepositInitiated(10Mostra tuttoCopia
Nella terminologia del ponte di Optimism, deposito indica un trasferimento da L1 a L2, e prelievo indica un trasferimento da L2 a L1.
1 address indexed _l1Token,2 address indexed _l2Token,3Copia
Nella maggior parte dei casi, l'indirizzo di un ERC-20 su L1 non equivale all'indirizzo dell'ERC-20 equivalente su L2. Puoi visualizzare l'elenco di indirizzi di token qui(opens in a new tab). L'indirizzo con chainId
1 è sul L1 (Mainnet) e l'indirizzo con chainId
10 è sul L2 (Optimism). Gli altri due valori di chainId
sono per la rete di prova Kovan (42) e la rete di prova Kovan di Optimistic (69).
1 address indexed _from,2 address _to,3 uint256 _amount,4 bytes _data5 );6Copia
È possibile aggiungere note ai trasferimenti, nel qual caso sono aggiunti agli eventi che li segnalano.
1 event ERC20WithdrawalFinalized(2 address indexed _l1Token,3 address indexed _l2Token,4 address indexed _from,5 address _to,6 uint256 _amount,7 bytes _data8 );9Copia
Lo stesso contratto del ponte gestisce i trasferimenti in entrambe le direzioni. Nel caso del ponte L1, ciò indica l'inizializzazione dei depositi e la finalizzazione dei prelievi.
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);11Mostra tuttoCopia
Questa funzione non è davvero necessaria, perché sul L2 è un contratto pre-distribuito, quindi è sempre all'indirizzo 0x4200000000000000000000000000000000000010
. Serve per simmetria con il ponte L2, perché non è banale sapere l'indirizzo del ponte L1.
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;18Mostra tuttoCopia
Il parametro _l2Gas
è l'importo di gas del L2 che la transazione può spendere. Fino a un certo limite (elevato), è gratuito(opens in a new tab), quindi, a meno che il contratto ERC-20 non faccia qualcosa di davvero strano durante il conio, non dovrebbe essere un problema. Questa funzione si occupa dello scenario comune, in cui un utente collega le risorse allo stesso indirizzo su una blockchain differente.
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;20Mostra tuttoCopia
Questa funzione è quasi identica a depositERC20
, ma ti consente di inviare l'ERC-20 a un altro indirizzo.
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}28Mostra tuttoCopia
I prelievi (e altri messaggi da L2 a L1) su Optimism sono processi in due fasi:
- Una transazione di avvio su L2.
- Una transazione di finalizzazione o rivendicazione su L1. Questa transazione deve verificarsi dopo il periodo di contestazione dell'errore(opens in a new tab) perché la transazione di L2 termini.
IL1StandardBridge
Quest'interfaccia è definita qui(opens in a new tab). Questo file contiene le definizioni dell'evento e la funzione per ETH. Queste definizioni sono molto simili a quelle definite nel precedente IL1ERC20Bridge
per ERC-20.
L'interfaccia del ponte è divisa tra due file perché alcuni token ERC-20 richiedono un'elaborazione specifica e non possono esser gestiti dal ponte standard. Il ponte personalizzato che gestisce un token di questo tipo può quindi implementare IL1ERC20Bridge
e non dover collegare anche 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 );19Mostra tuttoCopia
Questo evento è quasi identico alla versione di ERC-20 (ERC20DepositInitiated
), salvo che mancano gli indirizzi del token di L1 e L2. Lo stesso vale per gli altri eventi e le altre funzioni.
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}50Mostra tuttoCopia
CrossDomainEnabled
Questo contratto(opens in a new tab) è ereditato da entrambi i ponti (L1 e L2) per inviare i messaggi all'altro livello.
1// SPDX-License-Identifier: MIT2pragma solidity >0.5.0 <0.9.0;34/* Interface Imports */5import { ICrossDomainMessenger } from "./ICrossDomainMessenger.sol";6Copia
Quest'interfaccia(opens in a new tab) dice al contratto come inviare i messaggi all'altro livello, usando la messaggistica interdominio. Questa messaggistica interdominio è un sistema totalmente a sé, che merita il proprio articolo, che spero scriverò in futuro.
1/**2 * @title CrossDomainEnabled3 * @dev Contratto di supporto per contratti che eseguono comunicazioni tra domini4 *5 * Compiler usato: definito dal contratto ereditario6 */7contract CrossDomainEnabled {8 /*************9 * Variabili *10 *************/1112 // Il contratto di Messaggistica usato per inviare e ricevere messaggi dall'altro dominio.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 }25Mostra tuttoCopia
Il parametro che il contratto deve conoscere, l'indirizzo della messaggistica interdominio su questo livello. Questo parametro è impostato una volta, nel costruttore, e non cambia mai.
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) {12Mostra tuttoCopia
La messaggistica interdominio è accessibile da qualsiasi contratto sulla blockchain mentre è in esecuzione (sulla mainnet di Ethereum o su Optimism). Ma per fidarsi solo di certi messaggi, se provengono dal ponte dall'altra parte, serve il ponte su entrambi i lati.
1 require(2 msg.sender == address(getCrossDomainMessenger()),3 "OVM_XCHAIN: messenger contract unauthenticated"4 );5Copia
Solo i messaggi dalla messaggistica interdominio appropriata (messenger
, come vedi di seguito) sono affidabili.
12 require(3 getCrossDomainMessenger().xDomainMessageSender() == _sourceDomainAccount,4 "OVM_XCHAIN: wrong sender of cross-domain message"5 );6