Skip to content

Commit e92418e

Browse files
authored
feat(l2): make L1 contracts upgradeable (#2660)
**Motivation** <!-- Why does this pull request exist? What are its goals? --> We want the L1 contracts to be upgradeable so it's possible to fix bugs and introduce new features. **Description** <!-- A clear and concise general description of the changes this PR introduces --> Changed the contracts to follow the UUPS proxy pattern (from OpenZeppelin's library). The deployer binary now deploys both the implementation and the proxy. <!-- Link to issues: Resolves #111, Resolves #222 -->
1 parent a169985 commit e92418e

File tree

12 files changed

+260
-80
lines changed

12 files changed

+260
-80
lines changed

crates/l2/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ deploy-l1: ## 📜 Deploys the L1 contracts
120120
--sp1.verifier-address 0x00000000000000000000000000000000000000aa \
121121
--pico.verifier-address 0x00000000000000000000000000000000000000aa \
122122
--risc0.verifier-address 0x00000000000000000000000000000000000000aa \
123+
--on-chain-proposer-owner 0x03d0a0aee676cc45bf7032649e0871927c947c8e \
124+
--bridge-owner 0x03d0a0aee676cc45bf7032649e0871927c947c8e \
123125
--deposit-rich
124126

125127
## Same as deploy-l1 but does not do deposits for rich accounts since that doesn't make sense for deployments to devnets/testnets i.e Sepolia

crates/l2/contracts/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Ethrex L2 contracts
2+
3+
There are two L1 contracts: OnChainProposer and CommonBridge. Both contracts are deployed using UUPS proxies, so they are upgradeables.
4+
5+
### Upgrade the contracts
6+
7+
To upgrade a contract, you have to create the new contract and, as the original one, inherit from OpenZeppelin's `UUPSUpgradeable`. Make sure to implement the `_authorizeUpgrade` function and follow the [proxy pattern restrictions](https://docs.openzeppelin.com/upgrades-plugins/writing-upgradeable).
8+
9+
Once you have the new contract, you need to do the following two steps:
10+
11+
1. Deploy the new contract
12+
```sh
13+
rex deploy <NEW_IMPLEMENTATION_BYTECODE> 0 <DEPLOYER_PRIVATE_KEY>
14+
```
15+
2. Upgrade the proxy by calling the method `upgradeToAndCall(address newImplementation, bytes memory data)`. The `data` parameter is the calldata to call on the new implementation as an initialization, you can pass an empty stream.
16+
```sh
17+
rex send <PROXY_ADDRESS> 0 <PRIVATE_KEY> -- 'upgradeToAndCall(address,bytes)' 0 <NEW_IMPLEMENTATION_ADDRESS> <INITIALIZATION_CALLDATA>
18+
```
19+
3. Check the proxy updated the pointed address to the new implementation. It should return the address of the new implementation:
20+
```sh
21+
curl http://localhost:8545 -d '{"jsonrpc": "2.0", "id": "1", "method": "eth_getStorageAt", "params": [<PROXY_ADDRESS>, "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", "latest"]}'
22+
```

crates/l2/contracts/bin/deployer/cli.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,22 @@ pub struct DeployerOptions {
205205
help = "If set to true, initializes the committer in validium mode."
206206
)]
207207
pub validium: bool,
208+
#[arg(
209+
long,
210+
value_name = "ADDRESS",
211+
env = "ETHREX_ON_CHAIN_PROPOSER_OWNER",
212+
help_heading = "Deployer options",
213+
help = "Address of the owner of the OnChainProposer contract, who can upgrade the contract."
214+
)]
215+
pub on_chain_proposer_owner: Address,
216+
#[arg(
217+
long,
218+
value_name = "ADDRESS",
219+
env = "ETHREX_BRIDGE_OWNER",
220+
help_heading = "Deployer options",
221+
help = "Address of the owner of the CommonBridge contract, who can upgrade the contract."
222+
)]
223+
pub bridge_owner: Address,
208224
}
209225

210226
impl Default for DeployerOptions {
@@ -258,6 +274,16 @@ impl Default for DeployerOptions {
258274
sp1_deploy_verifier: false,
259275
randomize_contract_deployment: false,
260276
validium: false,
277+
// 0x03d0a0aee676cc45bf7032649e0871927c947c8e
278+
on_chain_proposer_owner: H160([
279+
0x03, 0xd0, 0xa0, 0xae, 0xe6, 0x76, 0xcc, 0x45, 0xbf, 0x70, 0x32, 0x64, 0x9e, 0x08,
280+
0x71, 0x92, 0x7c, 0x94, 0x7c, 0x8e,
281+
]),
282+
// 0x03d0a0aee676cc45bf7032649e0871927c947c8e
283+
bridge_owner: H160([
284+
0x03, 0xd0, 0xa0, 0xae, 0xe6, 0x76, 0xcc, 0x45, 0xbf, 0x70, 0x32, 0x64, 0x9e, 0x08,
285+
0x71, 0x92, 0x7c, 0x94, 0x7c, 0x8e,
286+
]),
261287
}
262288
}
263289
}

crates/l2/contracts/bin/deployer/main.rs

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ use ethrex_common::{Address, U256};
1515
use ethrex_l2::utils::test_data_io::read_genesis_file;
1616
use ethrex_l2_sdk::{
1717
calldata::{encode_calldata, Value},
18-
compile_contract, deploy_contract, get_address_from_secret_key, initialize_contract,
18+
compile_contract, deploy_contract, deploy_with_proxy, get_address_from_secret_key,
19+
initialize_contract,
1920
};
2021
use ethrex_rpc::{
2122
clients::{eth::BlockByNumber, EthClientError, Overrides},
@@ -28,8 +29,8 @@ mod cli;
2829
mod error;
2930

3031
const INITIALIZE_ON_CHAIN_PROPOSER_SIGNATURE: &str =
31-
"initialize(address,address,address,address,address[])";
32-
const BRIDGE_INITIALIZER_SIGNATURE: &str = "initialize(address)";
32+
"initialize(bool,address,address,address,address,address,address[])";
33+
const BRIDGE_INITIALIZER_SIGNATURE: &str = "initialize(address,address)";
3334

3435
#[tokio::main]
3536
async fn main() -> Result<(), DeployerError> {
@@ -88,12 +89,13 @@ fn download_contract_deps(opts: &DeployerOptions) -> Result<(), DeployerError> {
8889
})?;
8990

9091
git_clone(
91-
"https://github.com/OpenZeppelin/openzeppelin-contracts.git",
92+
"https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git",
9293
opts.contracts_path
93-
.join("lib/openzeppelin-contracts")
94+
.join("lib/openzeppelin-contracts-upgradeable")
9495
.to_str()
9596
.ok_or(DeployerError::FailedToGetStringFromPath)?,
9697
None,
98+
true,
9799
)?;
98100

99101
git_clone(
@@ -103,6 +105,7 @@ fn download_contract_deps(opts: &DeployerOptions) -> Result<(), DeployerError> {
103105
.to_str()
104106
.ok_or(DeployerError::FailedToGetStringFromPath)?,
105107
None,
108+
false,
106109
)?;
107110

108111
git_clone(
@@ -112,6 +115,7 @@ fn download_contract_deps(opts: &DeployerOptions) -> Result<(), DeployerError> {
112115
.to_str()
113116
.ok_or(DeployerError::FailedToGetStringFromPath)?,
114117
Some("evm"),
118+
false,
115119
)?;
116120

117121
Ok(())
@@ -121,6 +125,7 @@ pub fn git_clone(
121125
repository_url: &str,
122126
outdir: &str,
123127
branch: Option<&str>,
128+
submodules: bool,
124129
) -> Result<ExitStatus, DeployerError> {
125130
let mut git_cmd = Command::new("git");
126131

@@ -130,6 +135,10 @@ pub fn git_clone(
130135
git_clone_cmd.arg("--branch").arg(branch);
131136
}
132137

138+
if submodules {
139+
git_clone_cmd.arg("--recurse-submodules");
140+
}
141+
133142
git_clone_cmd
134143
.arg(outdir)
135144
.spawn()
@@ -139,6 +148,7 @@ pub fn git_clone(
139148
}
140149

141150
fn compile_contracts(opts: &DeployerOptions) -> Result<(), DeployerError> {
151+
compile_contract(&opts.contracts_path, "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol", false)?;
142152
compile_contract(&opts.contracts_path, "src/l1/OnChainProposer.sol", false)?;
143153
compile_contract(&opts.contracts_path, "src/l1/CommonBridge.sol", false)?;
144154
compile_contract(
@@ -179,41 +189,48 @@ async fn deploy_contracts(
179189
.to_vec()
180190
};
181191

182-
let (on_chain_proposer_deployment_tx_hash, on_chain_proposer_address) = deploy_contract(
183-
&[&[0u8; 31][..], &[u8::from(opts.validium)]].concat(),
184-
&opts.contracts_path.join("solc_out/OnChainProposer.bin"),
185-
&opts.private_key,
186-
&salt,
192+
let on_chain_proposer_deployment = deploy_with_proxy(
193+
opts.private_key,
187194
eth_client,
195+
&opts.contracts_path.join("solc_out"),
196+
"OnChainProposer.bin",
197+
&salt,
188198
)
189199
.await?;
190200

191201
spinner.success(&format!(
192-
"OnChainProposer:\n\tDeployed at address {}\n\tWith tx hash {}",
193-
format!("{on_chain_proposer_address:#x}").bright_green(),
194-
format!("{on_chain_proposer_deployment_tx_hash:#x}").bright_cyan()
202+
r#"OnChainProposer:
203+
Deployed implementation at address {}
204+
With tx hash {},
205+
Deployed proxy at address {}
206+
With tx hash {}"#,
207+
format!("{:#x}", on_chain_proposer_deployment.implementation_address).bright_green(),
208+
format!("{:#x}", on_chain_proposer_deployment.implementation_tx_hash).bright_cyan(),
209+
format!("{:#x}", on_chain_proposer_deployment.proxy_address).bright_green(),
210+
format!("{:#x}", on_chain_proposer_deployment.proxy_tx_hash).bright_cyan()
195211
));
196212

197213
let mut spinner = Spinner::new(deploy_frames.clone(), "Deploying CommonBridge", Color::Cyan);
198-
let (bridge_deployment_tx_hash, bridge_address) = deploy_contract(
199-
&{
200-
let deployer_address = get_address_from_secret_key(&opts.private_key)?;
201-
let offset = 32 - deployer_address.as_bytes().len() % 32;
202-
let mut encoded_owner = vec![0; offset];
203-
encoded_owner.extend_from_slice(deployer_address.as_bytes());
204-
encoded_owner
205-
},
206-
&opts.contracts_path.join("solc_out/CommonBridge.bin"),
207-
&opts.private_key,
208-
&salt,
214+
215+
let bridge_deployment = deploy_with_proxy(
216+
opts.private_key,
209217
eth_client,
218+
&opts.contracts_path.join("solc_out"),
219+
"CommonBridge.bin",
220+
&salt,
210221
)
211222
.await?;
212223

213224
spinner.success(&format!(
214-
"CommonBridge:\n\tDeployed at address {}\n\tWith tx hash {}",
215-
format!("{bridge_address:#x}").bright_green(),
216-
format!("{bridge_deployment_tx_hash:#x}").bright_cyan(),
225+
r#"CommonBridge:
226+
Deployed implementation at address {}
227+
With tx hash {},
228+
Deployed proxy at address {}
229+
With tx hash {}"#,
230+
format!("{:#x}", bridge_deployment.implementation_address).bright_green(),
231+
format!("{:#x}", bridge_deployment.implementation_tx_hash).bright_cyan(),
232+
format!("{:#x}", bridge_deployment.proxy_address).bright_green(),
233+
format!("{:#x}", bridge_deployment.proxy_tx_hash).bright_cyan()
217234
));
218235

219236
let sp1_verifier_address = if opts.sp1_deploy_verifier {
@@ -273,8 +290,8 @@ async fn deploy_contracts(
273290
))?;
274291

275292
Ok((
276-
on_chain_proposer_address,
277-
bridge_address,
293+
on_chain_proposer_deployment.proxy_address,
294+
bridge_deployment.proxy_address,
278295
sp1_verifier_address,
279296
pico_verifier_address,
280297
risc0_verifier_address,
@@ -301,6 +318,8 @@ async fn initialize_contracts(
301318

302319
let initialize_tx_hash = {
303320
let calldata_values = vec![
321+
Value::Bool(opts.validium),
322+
Value::Address(opts.on_chain_proposer_owner),
304323
Value::Address(bridge_address),
305324
Value::Address(risc0_verifier_address),
306325
Value::Address(sp1_verifier_address),
@@ -333,7 +352,10 @@ async fn initialize_contracts(
333352
Color::Cyan,
334353
);
335354
let initialize_tx_hash = {
336-
let calldata_values = vec![Value::Address(on_chain_proposer_address)];
355+
let calldata_values = vec![
356+
Value::Address(opts.bridge_owner),
357+
Value::Address(on_chain_proposer_address),
358+
];
337359
let bridge_initialization_calldata =
338360
encode_calldata(BRIDGE_INITIALIZER_SIGNATURE, &calldata_values)?;
339361

@@ -403,7 +425,6 @@ async fn make_deposits(
403425
let overrides = Overrides {
404426
value: Some(value_to_deposit),
405427
from: Some(address),
406-
gas_limit: Some(21000 * 5),
407428
max_fee_per_gas: Some(gas_price),
408429
max_priority_fee_per_gas: Some(gas_price),
409430
..Overrides::default()

crates/l2/contracts/src/l1/CommonBridge.sol

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity =0.8.29;
33

4-
import "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol";
5-
import "../../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
4+
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
5+
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
6+
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
7+
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
68
import "./interfaces/ICommonBridge.sol";
79
import "./interfaces/IOnChainProposer.sol";
810

911
/// @title CommonBridge contract.
1012
/// @author LambdaClass
11-
contract CommonBridge is ICommonBridge, Ownable, ReentrancyGuard {
13+
contract CommonBridge is
14+
ICommonBridge,
15+
ReentrancyGuard,
16+
Initializable,
17+
UUPSUpgradeable,
18+
OwnableUpgradeable
19+
{
1220
/// @notice Mapping of unclaimed withdrawals. A withdrawal is claimed if
1321
/// there is a non-zero value in the mapping (a merkle root) for the hash
1422
/// of the L2 transaction that requested the withdrawal.
@@ -45,10 +53,15 @@ contract CommonBridge is ICommonBridge, Ownable, ReentrancyGuard {
4553
_;
4654
}
4755

48-
constructor(address owner) Ownable(owner) {}
49-
50-
/// @inheritdoc ICommonBridge
51-
function initialize(address onChainProposer) public nonReentrant {
56+
/// @notice Initializes the contract.
57+
/// @dev This method is called only once after the contract is deployed.
58+
/// @dev It sets the OnChainProposer address.
59+
/// @param owner the address of the owner who can perform upgrades.
60+
/// @param onChainProposer the address of the OnChainProposer contract.
61+
function initialize(
62+
address owner,
63+
address onChainProposer
64+
) public initializer {
5265
require(
5366
ON_CHAIN_PROPOSER == address(0),
5467
"CommonBridge: contract already initialized"
@@ -65,6 +78,8 @@ contract CommonBridge is ICommonBridge, Ownable, ReentrancyGuard {
6578

6679
lastFetchedL1Block = block.number;
6780
depositId = 0;
81+
82+
OwnableUpgradeable.__Ownable_init(owner);
6883
}
6984

7085
/// @inheritdoc ICommonBridge
@@ -248,4 +263,10 @@ contract CommonBridge is ICommonBridge, Ownable, ReentrancyGuard {
248263
withdrawalLeaf ==
249264
batchWithdrawalLogsMerkleRoots[withdrawalBatchNumber];
250265
}
266+
267+
/// @notice Allow owner to upgrade the contract.
268+
/// @param newImplementation the address of the new implementation
269+
function _authorizeUpgrade(
270+
address newImplementation
271+
) internal virtual override onlyOwner {}
251272
}

0 commit comments

Comments
 (0)