Skip to content

Commit ad534f3

Browse files
authored
Merge pull request #462 from EspressoSystems/ax/step1
Chain init with key manager contract deployed
2 parents 2115479 + 56bd2be commit ad534f3

File tree

10 files changed

+307
-13
lines changed

10 files changed

+307
-13
lines changed

Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ rust-version = "1.85.0"
2929

3030
[workspace.dependencies]
3131
aes-gcm = { version = "0.10.3" }
32-
alloy = { version = "1.0", features = ["default", "arbitrary", "k256", "serde", "rlp", "node-bindings", "getrandom"] }
32+
alloy = { version = "1.0", features = ["default", "arbitrary", "k256", "serde", "rlp", "node-bindings", "getrandom", "signer-mnemonic"] }
3333
alloy-chains = "0.2"
3434
# derive feature is not exposed via `alloy`, thus has to explicitly declare here
3535
alloy-rlp = { version = "0.3.12", features = ["derive"] }
@@ -92,6 +92,8 @@ tokio = { version = "1", default-features = false, features = ["full"] }
9292
tokio-stream = "0.1.17"
9393
tokio-tungstenite = { version = "0.27.0", features = ["rustls-tls-webpki-roots", "url"] }
9494
tokio-util = "0.7.15"
95+
toml = "0.8"
96+
toml_edit = "0.22"
9597
tonic = "0.14.1"
9698
tonic-prost = "0.14.1"
9799
tonic-prost-build = "0.14.1"
@@ -100,7 +102,7 @@ tower-http = { version = "0.6.6", features = ["trace", "request-id", "util"] }
100102
tracing = "0.1"
101103
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] }
102104
turmoil = "0.6.4"
103-
url = "2.5.4"
105+
url = { version = "2.5.4", features = ["serde"] }
104106
zeroize = { version = "1.8", features = ["zeroize_derive"] }
105107

106108
espresso-types = { git = "https://github.com/EspressoSystems/espresso-network.git" }

justfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,6 @@ test-individually:
134134
echo "Testing $pkg"; \
135135
cargo nextest run --no-tests=pass -p $pkg || exit 1; \
136136
done
137+
138+
test-contract-deploy:
139+
./scripts/test-contract-deploy

scripts/test-contract-deploy

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env bash
2+
3+
set -emuo pipefail
4+
5+
cleanup() {
6+
trap - EXIT TERM
7+
if [ -n "$ANVIL_PID" ]; then
8+
kill "$ANVIL_PID" 2>/dev/null || true
9+
fi
10+
rm -f .anvil.pid anvil.log
11+
}
12+
13+
trap cleanup EXIT TERM INT
14+
15+
# Kill any existing anvil processes to avoid port conflicts
16+
pkill anvil || true
17+
sleep 1
18+
19+
# Start anvil in background
20+
anvil --port 8545 >anvil.log 2>&1 &
21+
ANVIL_PID=$!
22+
echo $ANVIL_PID >.anvil.pid
23+
24+
# Wait for anvil to start
25+
sleep 1
26+
27+
# Run the deploy command
28+
MANAGER_MNEMONIC="test test test test test test test test test test test junk"
29+
MANAGER_ACCOUNT_INDEX=0
30+
URL="http://localhost:8545"
31+
RUST_LOG=info cargo run --bin deploy -- -m "$MANAGER_MNEMONIC" -i "$MANAGER_ACCOUNT_INDEX" -u "$URL"

timeboost-contract/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,19 @@ rust-version.workspace = true
77

88
[dependencies]
99
alloy = { workspace = true }
10+
anyhow = { workspace = true }
11+
clap = { workspace = true }
12+
rand = { workspace = true }
1013
tracing = { workspace = true }
14+
serde = { workspace = true }
15+
timeboost-utils = { path = "../timeboost-utils" }
1116
tokio = { workspace = true }
17+
toml = { workspace = true }
18+
url = { workspace = true }
1219

1320
[build-dependencies]
1421
alloy = { workspace = true }
22+
23+
[[bin]]
24+
name = "deploy"
25+
path = "src/binaries/deploy.rs"
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//! CLI for contract deployment
2+
//!
3+
//! # Usage
4+
//!
5+
//! ```
6+
//! # Write config to stdout
7+
//! cargo run --bin deploy -- -m "your mnemonic here" -i 0 -u http://localhost:8545
8+
//!
9+
//! # Write config to a file
10+
//! cargo run --bin deploy -- -m "your mnemonic here" -i 0 -u http://localhost:8545 -o output.toml
11+
//! ```
12+
//!
13+
//! # Local test
14+
//! Run `just test-contract-deploy`
15+
use alloy::{primitives::Address, providers::WalletProvider};
16+
use anyhow::{Context, Result};
17+
use clap::Parser;
18+
use serde::Serialize;
19+
use std::path::PathBuf;
20+
use timeboost_contract::provider::build_provider;
21+
use timeboost_utils::types::logging;
22+
use tokio::fs;
23+
use tracing::info;
24+
use url::Url;
25+
26+
#[derive(Clone, Debug, Parser)]
27+
struct Args {
28+
#[clap(short, long)]
29+
mnemonic: String,
30+
31+
#[clap(short, long)]
32+
index: u32,
33+
34+
#[clap(short, long)]
35+
url: Url,
36+
37+
#[clap(short, long)]
38+
output: Option<PathBuf>,
39+
}
40+
41+
/// Config type for the key manager who has the permission to update the KeyManager contract
42+
/// See `test-configs/keymanager.toml` for an example
43+
#[derive(Debug, Serialize)]
44+
struct KeyManagerConfig {
45+
wallet: LocalWalletConfig,
46+
deployments: Deployments,
47+
}
48+
49+
#[derive(Debug, Serialize)]
50+
struct LocalWalletConfig {
51+
mnemonic: String,
52+
account_index: u32,
53+
}
54+
55+
#[derive(Debug, Serialize)]
56+
struct Deployments {
57+
/// RPC endpoint of the target chain
58+
chain_url: Url,
59+
/// The contract address of KeyManager.sol proxy
60+
key_manager: Option<Address>,
61+
}
62+
63+
#[tokio::main]
64+
async fn main() -> Result<()> {
65+
logging::init_logging();
66+
67+
let args = Args::parse();
68+
69+
info!("Starting contract deployment");
70+
71+
// Construct the config from command-line arguments
72+
let mut cfg = KeyManagerConfig {
73+
wallet: LocalWalletConfig {
74+
mnemonic: args.mnemonic,
75+
account_index: args.index,
76+
},
77+
deployments: Deployments {
78+
chain_url: args.url,
79+
key_manager: None,
80+
},
81+
};
82+
83+
// Build provider
84+
let provider = build_provider(
85+
cfg.wallet.mnemonic.clone(),
86+
cfg.wallet.account_index,
87+
cfg.deployments.chain_url.clone(),
88+
)?;
89+
90+
let manager = provider.default_signer_address();
91+
info!("Deploying with manager address: {manager:#x}");
92+
93+
// Deploy the KeyManager contract
94+
let km_addr = timeboost_contract::deployer::deploy_key_manager_contract(&provider, manager)
95+
.await
96+
.context("Failed to deploy KeyManager contract")?;
97+
info!("KeyManager deployed successfully at: {km_addr:#x}");
98+
99+
// Update the address and deliver the final config
100+
cfg.deployments.key_manager = Some(km_addr);
101+
let toml = toml::to_string_pretty(&cfg)?;
102+
103+
if let Some(out) = &args.output {
104+
fs::write(out, &toml).await?;
105+
info!(file=?out, "Config written to file");
106+
} else {
107+
println!("{toml}");
108+
}
109+
110+
Ok(())
111+
}

timeboost-contract/src/deployer.rs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,50 @@ pub async fn deploy_key_manager_contract(
4545

4646
#[cfg(test)]
4747
mod tests {
48-
use crate::{KeyManager, deployer::deploy_key_manager_contract};
49-
use alloy::{primitives::Address, providers::ProviderBuilder};
48+
use alloy::{providers::WalletProvider, sol_types::SolValue};
49+
use rand::prelude::*;
50+
51+
use crate::{CommitteeMemberSol, CommitteeSol, KeyManager};
5052

5153
#[tokio::test]
5254
async fn test_key_manager_deployment() {
53-
let provider = ProviderBuilder::new().connect_anvil_with_wallet();
54-
let manager = Address::random();
55-
let addr = deploy_key_manager_contract(&provider, manager)
56-
.await
57-
.unwrap();
55+
let (provider, addr) = crate::init_test_chain().await.unwrap();
56+
let manager = provider.default_signer_address();
5857
let contract = KeyManager::new(addr, provider);
5958

6059
// try read from the contract storage
6160
assert_eq!(contract.manager().call().await.unwrap(), manager);
61+
62+
// try write to the contract storage
63+
let rng = &mut rand::rng();
64+
let members = (0..5)
65+
.map(|_| CommitteeMemberSol::random())
66+
.collect::<Vec<_>>();
67+
let timestamp = rng.random::<u64>();
68+
69+
let _tx_receipt = contract
70+
.setNextCommittee(timestamp, members.clone())
71+
.send()
72+
.await
73+
.unwrap()
74+
.get_receipt()
75+
.await
76+
.unwrap();
77+
78+
// make sure next committee is correctly registered
79+
assert_eq!(
80+
contract
81+
.getCommitteeById(0)
82+
.call()
83+
.await
84+
.unwrap()
85+
.abi_encode_sequence(),
86+
CommitteeSol {
87+
id: 0,
88+
effectiveTimestamp: timestamp,
89+
members,
90+
}
91+
.abi_encode_sequence()
92+
);
6293
}
6394
}

timeboost-contract/src/lib.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,29 @@
22
//!
33
//! This crate provides Rust bindings and API to interact with smart contracts,
44
5+
use alloy::{
6+
primitives::Address,
7+
providers::{ProviderBuilder, WalletProvider},
8+
};
9+
use anyhow::Result;
10+
511
// Include the generated contract bindings
612
// The build script auto-detects contracts and generates bindings in src/bindings/
7-
pub mod bindings;
13+
mod bindings;
814
pub mod deployer;
15+
pub mod provider;
16+
mod sol_types;
17+
18+
use provider::TestProviderWithWallet;
19+
pub use sol_types::*;
20+
21+
/// Spawn a local test blockchain and deploy KeyManager contract.
22+
/// Returns a WalletProvider to the chain and the deployed contract address.
23+
pub async fn init_test_chain() -> Result<(TestProviderWithWallet, Address)> {
24+
// this provider wraps both the test chain instance (exit on drop), and the wallet provider
25+
let provider = ProviderBuilder::new().connect_anvil_with_wallet();
26+
let km_addr =
27+
deployer::deploy_key_manager_contract(&provider, provider.default_signer_address()).await?;
928

10-
// We manually re-export the type here carefully due to alloy's lack of shared type:
11-
// tracking issue: https://github.com/foundry-rs/foundry/issues/10153
12-
pub use bindings::{erc1967proxy::ERC1967Proxy, keymanager::KeyManager};
29+
Ok((provider, km_addr))
30+
}

timeboost-contract/src/provider.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//! Helper functions to build Ethereum [providers](https://docs.rs/alloy/latest/alloy/providers/trait.Provider.html)
2+
//! Partial Credit: <https://github.com/EspressoSystems/espresso-network/tree/main/contracts/rust/deployer>
3+
4+
use alloy::{
5+
network::{Ethereum, EthereumWallet},
6+
providers::{
7+
ProviderBuilder, RootProvider,
8+
fillers::{FillProvider, JoinFill, WalletFiller},
9+
layers::AnvilProvider,
10+
utils::JoinedRecommendedFillers,
11+
},
12+
signers::local::{LocalSignerError, MnemonicBuilder, PrivateKeySigner, coins_bip39::English},
13+
transports::http::reqwest::Url,
14+
};
15+
16+
/// Type alias that connects to providers with recommended fillers and wallet
17+
/// use `<HttpProviderWithWallet as WalletProvider>::wallet()` to access internal wallet
18+
/// use `<HttpProviderWithWallet as WalletProvider>::default_signer_address(&provider)` to get
19+
/// wallet address
20+
pub type HttpProviderWithWallet = FillProvider<
21+
JoinFill<JoinedRecommendedFillers, WalletFiller<EthereumWallet>>,
22+
RootProvider,
23+
Ethereum,
24+
>;
25+
26+
pub type TestProviderWithWallet = FillProvider<
27+
JoinFill<JoinedRecommendedFillers, WalletFiller<EthereumWallet>>,
28+
AnvilProvider<RootProvider>,
29+
Ethereum,
30+
>;
31+
32+
/// Build a local signer from wallet mnemonic and account index
33+
pub fn build_signer(
34+
mnemonic: String,
35+
account_index: u32,
36+
) -> Result<PrivateKeySigner, LocalSignerError> {
37+
MnemonicBuilder::<English>::default()
38+
.phrase(mnemonic)
39+
.index(account_index)?
40+
.build()
41+
}
42+
43+
/// a handy thin wrapper around wallet builder and provider builder that directly
44+
/// returns an instantiated `Provider` with default fillers with wallet, ready to send tx
45+
pub fn build_provider(
46+
mnemonic: String,
47+
account_index: u32,
48+
url: Url,
49+
) -> Result<HttpProviderWithWallet, LocalSignerError> {
50+
let signer = build_signer(mnemonic, account_index)?;
51+
let wallet = EthereumWallet::from(signer);
52+
Ok(ProviderBuilder::new().wallet(wallet).connect_http(url))
53+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//! Solidity types for contract interaction
2+
3+
// We manually re-export the type here carefully due to alloy's lack of shared type:
4+
// tracking issue: https://github.com/foundry-rs/foundry/issues/10153
5+
pub use crate::bindings::{
6+
erc1967proxy::ERC1967Proxy,
7+
keymanager::KeyManager,
8+
keymanager::KeyManager::{Committee as CommitteeSol, CommitteeMember as CommitteeMemberSol},
9+
};
10+
11+
impl CommitteeMemberSol {
12+
#[cfg(test)]
13+
pub fn random() -> Self {
14+
use alloy::primitives::Bytes;
15+
use rand::prelude::*;
16+
17+
let mut rng = rand::rng();
18+
CommitteeMemberSol {
19+
sigKey: Bytes::from(rng.random::<[u8; 32]>()),
20+
dhKey: Bytes::from(rng.random::<[u8; 32]>()),
21+
dkgKey: Bytes::from(rng.random::<[u8; 32]>()),
22+
networkAddress: format!("127.0.0.1:{}", rng.random::<u16>()),
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)