11// SPDX-License-Identifier: GPL-3.0
22
3+ use super :: UpContractOutput ;
34use crate :: {
45 cli:: {
5- Cli ,
6+ Cli , JsonCli ,
67 traits:: { Cli as _, Confirm } ,
78 } ,
89 commands:: call:: contract:: CallContractCommand ,
@@ -16,6 +17,7 @@ use crate::{
1617 urls,
1718 wallet:: request_signature,
1819 } ,
20+ output:: deploy_error,
1921 style:: style,
2022} ;
2123use clap:: Args ;
@@ -497,6 +499,112 @@ impl UpContractCommand {
497499 Ok ( ( ) )
498500 }
499501
502+ /// Executes the contract deployment in JSON mode and returns structured output.
503+ pub ( crate ) async fn execute_json ( & mut self ) -> anyhow:: Result < UpContractOutput > {
504+ self . skip_confirm = true ;
505+ let mut json_cli = JsonCli ;
506+ if self . use_wallet {
507+ return Err ( deploy_error (
508+ "`pop --json up` does not support `--use-wallet`; provide `--suri`" ,
509+ ) ) ;
510+ }
511+ if self . upload_only {
512+ return Err ( deploy_error (
513+ "`pop --json up` does not support `--upload-only`; deployment must instantiate a contract" ,
514+ ) ) ;
515+ }
516+ if !self . execute {
517+ return Err ( deploy_error (
518+ "`pop --json up` requires `--execute` to return contract deployment output" ,
519+ ) ) ;
520+ }
521+
522+ let contract_already_built = has_contract_been_built ( & self . path ) ;
523+ if ( !self . skip_build || !contract_already_built) &&
524+ let Err ( e) =
525+ build_contract_artifacts ( & mut json_cli, & self . path , true , Verbosity :: Quiet , None )
526+ {
527+ return Err ( deploy_error ( e. to_string ( ) ) ) ;
528+ }
529+
530+ resolve_signer ( self . skip_confirm , & mut self . use_wallet , & mut self . suri , & mut json_cli)
531+ . map_err ( |e| deploy_error ( e. to_string ( ) ) ) ?;
532+
533+ let mut url = if let Some ( url) = self . url . clone ( ) {
534+ url
535+ } else {
536+ Url :: parse ( urls:: LOCAL ) . expect ( "default url is valid" )
537+ } ;
538+ let mut processes =
539+ if !is_chain_alive ( url. clone ( ) ) . await . map_err ( |e| deploy_error ( e. to_string ( ) ) ) ? {
540+ let local_url = Url :: parse ( urls:: LOCAL ) . expect ( "default url is valid" ) ;
541+ if url == local_url {
542+ let ports = resolve_ink_node_ports ( DEFAULT_PORT , DEFAULT_ETH_RPC_PORT )
543+ . map_err ( |e| deploy_error ( e. to_string ( ) ) ) ?;
544+ url = Url :: parse ( & format ! ( "ws://localhost:{}" , ports. ink_node_port) )
545+ . map_err ( |e| deploy_error ( e. to_string ( ) ) ) ?;
546+ Some (
547+ start_ink_node ( & url, true , ports. ink_node_port , ports. eth_rpc_port )
548+ . await
549+ . map_err ( |e| deploy_error ( e. to_string ( ) ) ) ?,
550+ )
551+ } else {
552+ return Err ( deploy_error (
553+ "You need to specify an accessible endpoint to deploy the contract." ,
554+ ) ) ;
555+ }
556+ } else {
557+ None
558+ } ;
559+ self . url = Some ( url. clone ( ) ) ;
560+
561+ let deploy_result = async {
562+ let function =
563+ extract_function ( self . path . clone ( ) , & self . constructor , FunctionType :: Constructor )
564+ . map_err ( |e| deploy_error ( e. to_string ( ) ) ) ?;
565+ if !function. args . is_empty ( ) {
566+ resolve_function_args ( & function, & mut json_cli, & mut self . args , self . skip_confirm )
567+ . map_err ( |e| deploy_error ( e. to_string ( ) ) ) ?;
568+ }
569+ normalize_call_args ( & mut self . args , & function) ;
570+
571+ let instantiate_exec = set_up_deployment ( self . clone ( ) . into ( ) )
572+ . await
573+ . map_err ( |e| deploy_error ( e. to_string ( ) ) ) ?;
574+
575+ let calculated_weight = dry_run_gas_estimate_instantiate ( & instantiate_exec)
576+ . await
577+ . map_err ( |e| deploy_error ( e. to_string ( ) ) ) ?;
578+ let weight_limit =
579+ if let ( Some ( gas_limit) , Some ( proof_size) ) = ( self . gas_limit , self . proof_size ) {
580+ Weight :: from_parts ( gas_limit, proof_size)
581+ } else {
582+ calculated_weight
583+ } ;
584+
585+ let contract_info = instantiate_smart_contract ( instantiate_exec, weight_limit)
586+ . await
587+ . map_err ( |e| deploy_error ( e. to_string ( ) ) ) ?;
588+
589+ Ok ( UpContractOutput {
590+ contract_address : contract_info. address . to_string ( ) ,
591+ url : url. to_string ( ) ,
592+ code_hash : contract_info. code_hash ,
593+ } )
594+ }
595+ . await ;
596+
597+ let cleanup_result = if processes. is_some ( ) {
598+ terminate_nodes ( & mut json_cli, processes. take ( ) , true )
599+ . await
600+ . map_err ( |e| deploy_error ( e. to_string ( ) ) )
601+ } else {
602+ Ok ( ( ) )
603+ } ;
604+
605+ finalize_json_deploy_result ( deploy_result, cleanup_result)
606+ }
607+
500608 async fn keep_interacting_with_node (
501609 & self ,
502610 cli : & mut Cli ,
@@ -629,6 +737,19 @@ impl UpContractCommand {
629737 }
630738}
631739
740+ fn finalize_json_deploy_result < T > (
741+ deploy_result : anyhow:: Result < T > ,
742+ cleanup_result : anyhow:: Result < ( ) > ,
743+ ) -> anyhow:: Result < T > {
744+ match ( deploy_result, cleanup_result) {
745+ ( Ok ( output) , Ok ( ( ) ) ) => Ok ( output) ,
746+ ( Ok ( _) , Err ( cleanup_error) ) => Err ( cleanup_error) ,
747+ ( Err ( deploy_error) , Ok ( ( ) ) ) => Err ( deploy_error) ,
748+ // Preserve the original deploy error when both deployment and cleanup fail.
749+ ( Err ( deploy_error) , Err ( _cleanup_error) ) => Err ( deploy_error) ,
750+ }
751+ }
752+
632753impl From < UpContractCommand > for UpOpts {
633754 fn from ( cmd : UpContractCommand ) -> Self {
634755 UpOpts {
@@ -905,4 +1026,49 @@ mod tests {
9051026 assert_eq ! ( ports. ink_node_port, DEFAULT_PORT ) ;
9061027 assert_eq ! ( ports. eth_rpc_port, DEFAULT_ETH_RPC_PORT ) ;
9071028 }
1029+
1030+ #[ test]
1031+ fn finalize_json_deploy_result_returns_output_when_both_succeed ( ) {
1032+ let output = finalize_json_deploy_result :: < u32 > ( Ok ( 7 ) , Ok ( ( ) ) ) . expect ( "expected success" ) ;
1033+ assert_eq ! ( output, 7 ) ;
1034+ }
1035+
1036+ #[ test]
1037+ fn finalize_json_deploy_result_returns_cleanup_error_when_deploy_succeeds ( ) {
1038+ let error = finalize_json_deploy_result :: < u32 > ( Ok ( 7 ) , Err ( deploy_error ( "cleanup failed" ) ) )
1039+ . expect_err ( "expected cleanup failure" ) ;
1040+ assert_eq ! ( error. to_string( ) , "cleanup failed" ) ;
1041+ }
1042+
1043+ #[ test]
1044+ fn finalize_json_deploy_result_returns_deploy_error_when_deploy_fails ( ) {
1045+ let error = finalize_json_deploy_result :: < u32 > ( Err ( deploy_error ( "deploy failed" ) ) , Ok ( ( ) ) )
1046+ . expect_err ( "expected deploy failure" ) ;
1047+ assert_eq ! ( error. to_string( ) , "deploy failed" ) ;
1048+ }
1049+
1050+ #[ test]
1051+ fn finalize_json_deploy_result_preserves_deploy_error_when_both_fail ( ) {
1052+ let error = finalize_json_deploy_result :: < u32 > (
1053+ Err ( deploy_error ( "deploy failed" ) ) ,
1054+ Err ( deploy_error ( "cleanup failed" ) ) ,
1055+ )
1056+ . expect_err ( "expected deploy failure" ) ;
1057+ assert_eq ! ( error. to_string( ) , "deploy failed" ) ;
1058+ }
1059+
1060+ #[ tokio:: test]
1061+ async fn execute_json_requires_execute_before_setup_work ( ) {
1062+ let mut cmd = UpContractCommand {
1063+ path : PathBuf :: from ( "__non_existent_contract_path_for_execute_json_test__" ) ,
1064+ execute : false ,
1065+ ..Default :: default ( )
1066+ } ;
1067+
1068+ let error = cmd. execute_json ( ) . await . expect_err ( "expected missing execute error" ) ;
1069+ assert_eq ! (
1070+ error. to_string( ) ,
1071+ "`pop --json up` requires `--execute` to return contract deployment output" ,
1072+ ) ;
1073+ }
9081074}
0 commit comments