11use std:: net:: SocketAddr ;
2+ use std:: path:: Path ;
3+ use std:: process:: Command ;
24use std:: sync:: Arc ;
35
46use katana_chain_spec:: { dev, ChainSpec } ;
@@ -23,6 +25,23 @@ use starknet::providers::{JsonRpcClient, Url};
2325pub use starknet:: providers:: { Provider , ProviderError } ;
2426use starknet:: signers:: { LocalWallet , SigningKey } ;
2527
28+ /// Errors that can occur when migrating contracts to a test node.
29+ #[ derive( Debug , thiserror:: Error ) ]
30+ pub enum MigrateError {
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+
2645pub type ForkTestNode = TestNode < ForkProviderFactory > ;
2746
2847#[ derive( Debug ) ]
@@ -124,6 +143,138 @@ where
124143 let client = self . rpc_http_client ( ) ;
125144 katana_rpc_client:: starknet:: Client :: new_with_client ( client)
126145 }
146+
147+ /// Migrates the `spawn-and-move` example contracts from the dojo repository.
148+ ///
149+ /// This method requires `git`, `asdf`, and `sozo` to be available in PATH.
150+ /// The scarb version is managed by asdf using the `.tool-versions` file
151+ /// in the dojo repository.
152+ pub async fn migrate_spawn_and_move ( & self ) -> Result < ( ) , MigrateError > {
153+ self . migrate_example ( "spawn-and-move" ) . await
154+ }
155+
156+ /// Migrates the `simple` example contracts from the dojo repository.
157+ ///
158+ /// This method requires `git`, `asdf`, and `sozo` to be available in PATH.
159+ /// The scarb version is managed by asdf using the `.tool-versions` file
160+ /// in the dojo repository.
161+ pub async fn migrate_simple ( & self ) -> Result < ( ) , MigrateError > {
162+ self . migrate_example ( "simple" ) . await
163+ }
164+
165+ /// Migrates contracts from a dojo example project.
166+ ///
167+ /// Clones the dojo repository, builds contracts with `scarb`, and deploys
168+ /// them with `sozo migrate`.
169+ ///
170+ /// This method requires `git`, `asdf`, and `sozo` to be available in PATH.
171+ /// The scarb version is managed by asdf using the `.tool-versions` file
172+ /// in the dojo repository.
173+ async fn migrate_example ( & self , example : & str ) -> Result < ( ) , MigrateError > {
174+ let rpc_url = format ! ( "http://{}" , self . rpc_addr( ) ) ;
175+
176+ let ( address, account) = self
177+ . backend ( )
178+ . chain_spec
179+ . genesis ( )
180+ . accounts ( )
181+ . next ( )
182+ . expect ( "must have at least one genesis account" ) ;
183+ let private_key = account. private_key ( ) . ok_or ( MigrateError :: MissingPrivateKey ) ?;
184+
185+ let address_hex = address. to_string ( ) ;
186+ let private_key_hex = format ! ( "{private_key:#x}" ) ;
187+ let example_path = format ! ( "dojo/examples/{example}" ) ;
188+
189+ tokio:: task:: spawn_blocking ( move || {
190+ let temp_dir = tempfile:: tempdir ( ) ?;
191+
192+ // Clone dojo repository at v1.7.0
193+ run_git_clone ( temp_dir. path ( ) ) ?;
194+
195+ let project_dir = temp_dir. path ( ) . join ( & example_path) ;
196+
197+ // Build contracts using asdf to ensure correct scarb version
198+ run_scarb_build ( & project_dir) ?;
199+
200+ // Deploy contracts to the katana node
201+ run_sozo_migrate ( & project_dir, & rpc_url, & address_hex, & private_key_hex) ?;
202+
203+ Ok ( ( ) )
204+ } )
205+ . await ?
206+ }
207+ }
208+
209+ fn run_git_clone ( temp_dir : & Path ) -> Result < ( ) , MigrateError > {
210+ let output = Command :: new ( "git" )
211+ . args ( [ "clone" , "--depth" , "1" , "--branch" , "v1.7.0" , "https://github.com/dojoengine/dojo" ] )
212+ . current_dir ( temp_dir)
213+ . output ( )
214+ . map_err ( |e| MigrateError :: GitClone ( e. to_string ( ) ) ) ?;
215+
216+ if !output. status . success ( ) {
217+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
218+ return Err ( MigrateError :: GitClone ( stderr. to_string ( ) ) ) ;
219+ }
220+ Ok ( ( ) )
221+ }
222+
223+ fn run_scarb_build ( project_dir : & Path ) -> Result < ( ) , MigrateError > {
224+ let output = Command :: new ( "asdf" )
225+ . args ( [ "exec" , "scarb" , "build" ] )
226+ . current_dir ( project_dir)
227+ . output ( )
228+ . map_err ( |e| MigrateError :: ScarbBuild ( e. to_string ( ) ) ) ?;
229+
230+ if !output. status . success ( ) {
231+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
232+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
233+ let combined = format ! ( "{stdout}\n {stderr}" ) ;
234+
235+ let lines: Vec < & str > = combined. lines ( ) . collect ( ) ;
236+ let last_50: String =
237+ lines. iter ( ) . rev ( ) . take ( 50 ) . rev ( ) . cloned ( ) . collect :: < Vec < _ > > ( ) . join ( "\n " ) ;
238+
239+ return Err ( MigrateError :: ScarbBuild ( last_50) ) ;
240+ }
241+ Ok ( ( ) )
242+ }
243+
244+ fn run_sozo_migrate (
245+ project_dir : & Path ,
246+ rpc_url : & str ,
247+ address : & str ,
248+ private_key : & str ,
249+ ) -> Result < ( ) , MigrateError > {
250+ let output = Command :: new ( "sozo" )
251+ . args ( [
252+ "migrate" ,
253+ "--rpc-url" ,
254+ rpc_url,
255+ "--account-address" ,
256+ address,
257+ "--private-key" ,
258+ private_key,
259+ ] )
260+ . current_dir ( project_dir)
261+ . output ( )
262+ . map_err ( |e| MigrateError :: SozoMigrate ( e. to_string ( ) ) ) ?;
263+
264+ if !output. status . success ( ) {
265+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
266+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
267+ let combined = format ! ( "{stdout}\n {stderr}" ) ;
268+
269+ let lines: Vec < & str > = combined. lines ( ) . collect ( ) ;
270+ let last_50: String =
271+ lines. iter ( ) . rev ( ) . take ( 50 ) . rev ( ) . cloned ( ) . collect :: < Vec < _ > > ( ) . join ( "\n " ) ;
272+
273+ eprintln ! ( "sozo migrate failed. Last 50 lines of output:\n {last_50}" ) ;
274+
275+ return Err ( MigrateError :: SozoMigrate ( last_50) ) ;
276+ }
277+ Ok ( ( ) )
127278}
128279
129280pub fn test_config ( ) -> Config {
0 commit comments