Skip to content

Commit

Permalink
Make contract UUPS proxy upgradeable (#47)
Browse files Browse the repository at this point in the history
* forge install: openzeppelin-contracts-upgradeable

v5.0.2

* wip UUPS

* forge install: openzeppelin-contracts

v5.0.2

* update test and deployment for new proxy pattern

* forge fmt

* bump blobstream contracts

* update manual command address

* Add upgradability utils

* update docs for ownership commands

* remove forge script for maintainability

* add println for CLI upgrade
  • Loading branch information
austinabell authored Sep 27, 2024
1 parent 37fc526 commit fd94732
Show file tree
Hide file tree
Showing 13 changed files with 576 additions and 103 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
[submodule "contracts/lib/forge-std"]
path = contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "contracts/lib/openzeppelin-contracts-upgradeable"]
path = contracts/lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
[submodule "contracts/lib/blobstream-contracts"]
path = contracts/lib/blobstream-contracts
url = https://github.com/celestiaorg/blobstream-contracts
71 changes: 63 additions & 8 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use alloy::{
providers::ProviderBuilder,
signers::local::PrivateKeySigner,
};
use alloy_sol_types::sol;
use alloy_sol_types::{sol, SolCall};
use blobstream0_core::prove_block_range;
use blobstream0_primitives::IBlobstream;
use clap::Parser;
Expand All @@ -42,6 +42,11 @@ sol!(
RiscZeroGroth16Verifier,
"../contracts/out/RiscZeroGroth16Verifier.sol/RiscZeroGroth16Verifier.json"
);
sol!(
#[sol(rpc)]
ERC1967Proxy,
"../contracts/out/ERC1967Proxy.sol/ERC1967Proxy.json"
);

// Pulled from https://github.com/risc0/risc0-ethereum/blob/ebec385cc526adb9279c1af55d699c645ca6d694/contracts/src/groth16/ControlID.sol
const CONTROL_ID: [u8; 32] =
Expand All @@ -56,6 +61,7 @@ enum BlobstreamCli {
Service(service::ServiceArgs),
ProveRange(ProveRangeArgs),
Deploy(DeployArgs),
Upgrade(UpgradeArgs),
}

#[derive(Parser, Debug)]
Expand Down Expand Up @@ -85,7 +91,7 @@ struct DeployArgs {
#[clap(long, env)]
eth_rpc: String,

/// Hex encoded private key to use for submitting proofs to Ethereum
/// Hex encoded private key to use for deploying.
#[clap(long, env)]
private_key_hex: String,

Expand All @@ -110,6 +116,22 @@ struct DeployArgs {
dev: bool,
}

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct UpgradeArgs {
/// The Ethereum RPC URL
#[clap(long, env)]
eth_rpc: String,

/// Hex encoded private key to use for upgrading the contract. Must be the owner.
#[clap(long, env)]
private_key_hex: String,

/// Hex encoded address of admin for upgrades. Will default to the private key address.
#[clap(long, env)]
proxy_address: String,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
dotenv().ok();
Expand Down Expand Up @@ -174,16 +196,49 @@ async fn main() -> anyhow::Result<()> {
};

// Deploy the contract.
let contract = IBlobstream::deploy(
let implementation = IBlobstream::deploy(&provider).await?;
tracing::debug!(target: "blobstream0::cli", "Deployed implementation contract to {}", implementation.address());

let proxy = ERC1967Proxy::deploy(
&provider,
admin_address,
verifier_address,
FixedBytes::<32>::from_hex(deploy.tm_block_hash)?,
deploy.tm_height,
implementation.address().clone(),
IBlobstream::initializeCall {
_admin: admin_address,
_verifier: verifier_address,
_trustedHash: FixedBytes::<32>::from_hex(deploy.tm_block_hash)?,
_trustedHeight: deploy.tm_height,
}
.abi_encode()
.into(),
)
.await?;
tracing::debug!(target: "blobstream0::cli", "Deployed proxy contract");

println!("deployed contract to address: {}", contract.address());
println!("deployed contract to address: {}", proxy.address());
}
BlobstreamCli::Upgrade(upgrade) => {
let signer: PrivateKeySigner = upgrade.private_key_hex.parse()?;

let wallet = EthereumWallet::from(signer);
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(wallet)
.on_http(upgrade.eth_rpc.parse()?);

let proxy_address: Address = upgrade.proxy_address.parse()?;
println!("proxy address: {}", proxy_address);

let implementation = IBlobstream::deploy(&provider).await?;
tracing::debug!(target: "blobstream0::cli", "Deployed new implementation contract to {}", implementation.address());

IBlobstream::new(proxy_address, provider.clone())
.upgradeToAndCall(implementation.address().clone(), Default::default())
.send()
.await?
.watch()
.await?;
tracing::debug!(target: "blobstream0::cli", "Upgraded proxy contract to new implementation");
println!("Upgraded proxy contract to {}", implementation.address());
}
BlobstreamCli::Service(service) => service.start().await?,
}
Expand Down
28 changes: 21 additions & 7 deletions cli/tests/e2e_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use alloy::{
network::EthereumWallet, node_bindings::Anvil, primitives::U256, providers::ProviderBuilder,
signers::local::PrivateKeySigner,
};
use alloy_sol_types::sol;
use alloy_sol_types::{sol, SolCall};
use blobstream0_core::{post_batch, prove_block_range};
use blobstream0_primitives::IBlobstream::{self, BinaryMerkleProof, DataRootTuple};
use reqwest::header;
Expand All @@ -30,6 +30,11 @@ sol!(
MockVerifier,
"../contracts/out/RiscZeroMockVerifier.sol/RiscZeroMockVerifier.json"
);
sol!(
#[sol(rpc)]
ERC1967Proxy,
"../contracts/out/ERC1967Proxy.sol/ERC1967Proxy.json"
);

const CELESTIA_RPC_URL: &str = "https://celestia-testnet.brightlystake.com";

Expand Down Expand Up @@ -85,15 +90,24 @@ async fn e2e_basic_range() -> anyhow::Result<()> {
.unwrap();

// Deploy the contract.
let contract = IBlobstream::deploy(
let implementation = IBlobstream::deploy(&provider).await?;
tracing::debug!(target: "blobstream0::cli", "Deployed implementation contract");

let contract = ERC1967Proxy::deploy(
&provider,
anvil.addresses()[0],
verifier.address().clone(),
// Uses Celestia block hash at height below proving range on Mocha
trusted_block_hash,
BATCH_START as u64 - 1,
implementation.address().clone(),
IBlobstream::initializeCall {
_admin: anvil.addresses()[0],
_verifier: verifier.address().clone(),
_trustedHash: trusted_block_hash,
_trustedHeight: BATCH_START as u64 - 1,
}
.abi_encode()
.into(),
)
.await?;
// Pretend as if the proxy is the contract itself, requests forwarded to implementation.
let contract = IBlobstream::new(contract.address().clone(), provider.clone());

let receipt =
prove_block_range(tm_client.clone(), BATCH_START as u64..BATCH_END as u64).await?;
Expand Down
Loading

0 comments on commit fd94732

Please sign in to comment.