Skip to content

Commit 419e841

Browse files
kariyclaude
andcommitted
feat(utils): add populate() method to TestNode
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5389c32 commit 419e841

File tree

3 files changed

+132
-0
lines changed

3 files changed

+132
-0
lines changed

Cargo.lock

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

crates/utils/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ async-trait.workspace = true
2525
futures.workspace = true
2626
rand.workspace = true
2727
starknet.workspace = true
28+
tempfile.workspace = true
2829
thiserror.workspace = true
2930
tokio = { workspace = true, features = [ "macros", "signal", "time" ], default-features = false }
3031

crates/utils/src/node.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use std::net::SocketAddr;
2+
use std::path::Path;
3+
use std::process::Command;
24
use std::sync::Arc;
35

46
use katana_chain_spec::{dev, ChainSpec};
@@ -23,6 +25,23 @@ use starknet::providers::{JsonRpcClient, Url};
2325
pub use starknet::providers::{Provider, ProviderError};
2426
use starknet::signers::{LocalWallet, SigningKey};
2527

28+
/// Errors that can occur when populating a test node with contracts.
29+
#[derive(Debug, thiserror::Error)]
30+
pub enum PopulateError {
31+
#[error("Failed to create temp directory: {0}")]
32+
TempDir(#[from] std::io::Error),
33+
#[error("Git clone failed: {0}")]
34+
GitClone(String),
35+
#[error("Scarb build failed: {0}")]
36+
ScarbBuild(String),
37+
#[error("Sozo migrate failed: {0}")]
38+
SozoMigrate(String),
39+
#[error("Missing genesis account private key")]
40+
MissingPrivateKey,
41+
#[error("Spawn blocking task failed: {0}")]
42+
SpawnBlocking(#[from] tokio::task::JoinError),
43+
}
44+
2645
pub type ForkTestNode = TestNode<ForkProviderFactory>;
2746

2847
#[derive(Debug)]
@@ -124,6 +143,117 @@ where
124143
let client = self.rpc_http_client();
125144
katana_rpc_client::starknet::Client::new_with_client(client)
126145
}
146+
147+
/// Populates the node with contracts by cloning the dojo repository,
148+
/// building contracts with `scarb`, and deploying them with `sozo migrate`.
149+
///
150+
/// This method requires `git`, `asdf`, and `sozo` to be available in PATH.
151+
/// The scarb version is managed by asdf using the `.tool-versions` file
152+
/// in the dojo repository.
153+
pub async fn populate(&self) -> Result<(), PopulateError> {
154+
let rpc_url = format!("http://{}", self.rpc_addr());
155+
156+
let (address, account) = self
157+
.backend()
158+
.chain_spec
159+
.genesis()
160+
.accounts()
161+
.next()
162+
.expect("must have at least one genesis account");
163+
let private_key = account.private_key().ok_or(PopulateError::MissingPrivateKey)?;
164+
165+
let address_hex = address.to_string();
166+
let private_key_hex = format!("{private_key:#x}");
167+
168+
tokio::task::spawn_blocking(move || {
169+
let temp_dir = tempfile::tempdir()?;
170+
171+
// Clone dojo repository at v1.7.0
172+
run_git_clone(temp_dir.path())?;
173+
174+
let project_dir = temp_dir.path().join("dojo/examples/spawn-and-move");
175+
176+
// Build contracts using asdf to ensure correct scarb version
177+
run_scarb_build(&project_dir)?;
178+
179+
// Deploy contracts to the katana node
180+
run_sozo_migrate(&project_dir, &rpc_url, &address_hex, &private_key_hex)?;
181+
182+
Ok(())
183+
})
184+
.await?
185+
}
186+
}
187+
188+
fn run_git_clone(temp_dir: &Path) -> Result<(), PopulateError> {
189+
let output = Command::new("git")
190+
.args(["clone", "--depth", "1", "--branch", "v1.7.0", "https://github.com/dojoengine/dojo"])
191+
.current_dir(temp_dir)
192+
.output()
193+
.map_err(|e| PopulateError::GitClone(e.to_string()))?;
194+
195+
if !output.status.success() {
196+
let stderr = String::from_utf8_lossy(&output.stderr);
197+
return Err(PopulateError::GitClone(stderr.to_string()));
198+
}
199+
Ok(())
200+
}
201+
202+
fn run_scarb_build(project_dir: &Path) -> Result<(), PopulateError> {
203+
let output = Command::new("asdf")
204+
.args(["exec", "scarb", "build"])
205+
.current_dir(project_dir)
206+
.output()
207+
.map_err(|e| PopulateError::ScarbBuild(e.to_string()))?;
208+
209+
if !output.status.success() {
210+
let stdout = String::from_utf8_lossy(&output.stdout);
211+
let stderr = String::from_utf8_lossy(&output.stderr);
212+
let combined = format!("{stdout}\n{stderr}");
213+
214+
let lines: Vec<&str> = combined.lines().collect();
215+
let last_50: String =
216+
lines.iter().rev().take(50).rev().cloned().collect::<Vec<_>>().join("\n");
217+
218+
return Err(PopulateError::ScarbBuild(last_50));
219+
}
220+
Ok(())
221+
}
222+
223+
fn run_sozo_migrate(
224+
project_dir: &Path,
225+
rpc_url: &str,
226+
address: &str,
227+
private_key: &str,
228+
) -> Result<(), PopulateError> {
229+
let output = Command::new("sozo")
230+
.args([
231+
"migrate",
232+
"--rpc-url",
233+
rpc_url,
234+
"--account-address",
235+
address,
236+
"--private-key",
237+
private_key,
238+
])
239+
.current_dir(project_dir)
240+
.output()
241+
.map_err(|e| PopulateError::SozoMigrate(e.to_string()))?;
242+
243+
if !output.status.success() {
244+
let stdout = String::from_utf8_lossy(&output.stdout);
245+
let stderr = String::from_utf8_lossy(&output.stderr);
246+
let combined = format!("{stdout}\n{stderr}");
247+
248+
let lines: Vec<&str> = combined.lines().collect();
249+
let last_50: String =
250+
lines.iter().rev().take(50).rev().cloned().collect::<Vec<_>>().join("\n");
251+
252+
eprintln!("sozo migrate failed. Last 50 lines of output:\n{last_50}");
253+
254+
return Err(PopulateError::SozoMigrate(last_50));
255+
}
256+
Ok(())
127257
}
128258

129259
pub fn test_config() -> Config {

0 commit comments

Comments
 (0)