1use crate::{
2 call_handler_ext::{
3 TransactionConfig,
4 maybe_return_coins,
5 },
6 utxo_manager::SharedUtxoManager,
7 wallet_ext::{
8 BuilderData,
9 WalletExt,
10 },
11};
12use fuel_core_client::client::FuelClient;
13use fuel_core_types::{
14 fuel_tx::{
15 Create,
16 Output,
17 TxPointer,
18 UniqueIdentifier,
19 Witness,
20 },
21 fuel_types::canonical::Serialize,
22};
23use fuels::{
24 accounts::ViewOnlyAccount,
25 prelude::{
26 Contract,
27 Error as FuelsError,
28 Result as FuelsResult,
29 Wallet,
30 },
31 programs::contract::{
32 DeployResponse,
33 Regular,
34 },
35};
36use std::future::Future;
37
38pub(crate) trait ContractExt {
39 fn almost_sync_deploy(
40 self,
41 account: &Wallet,
42 builder_data: &BuilderData,
43 utxo_manager: &SharedUtxoManager,
44 tx_config: &Option<TransactionConfig>,
45 submit_clients: &[FuelClient],
46 ) -> impl Future<Output = FuelsResult<DeployResponse>>;
47}
48
49impl ContractExt for Contract<Regular> {
50 async fn almost_sync_deploy(
54 self,
55 account: &Wallet,
56 builder_data: &BuilderData,
57 utxo_manager: &SharedUtxoManager,
58 tx_config: &Option<TransactionConfig>,
59 submit_clients: &[FuelClient],
60 ) -> FuelsResult<DeployResponse> {
61 let consensus_parameters = &builder_data.consensus_parameters;
62 let max_fee = builder_data.max_fee();
63 let base_asset_id = *consensus_parameters.base_asset_id();
64 let chain_id = consensus_parameters.chain_id();
65 let secret_key = account.signer().secret_key();
66 let owner = account.address();
67
68 let contract_id = self.contract_id();
69 let state_root = self.state_root();
70 let salt = self.salt();
71 let storage_slots = self.storage_slots();
72 let code = self.code();
73 let bytecode = Witness::from(code);
74
75 let witness_limit = bytecode.size() + crate::wallet_ext::SIGNATURE_MARGIN;
76
77 let mut builder = fuel_core_types::fuel_tx::TransactionBuilder::<Create>::create(
78 bytecode,
79 salt,
80 storage_slots.to_vec(),
81 );
82
83 builder
84 .with_chain_id(consensus_parameters.chain_id())
85 .max_fee_limit(max_fee)
86 .witness_limit(witness_limit as u64);
87
88 let input_coins = {
89 utxo_manager
90 .lock()
91 .await
92 .guaranteed_extract_coins(owner, base_asset_id, max_fee as u128)
93 .map_err(|e| FuelsError::Other(e.to_string()))
94 }?;
95
96 for coin in input_coins.iter() {
97 builder.add_unsigned_coin_input(
98 secret_key,
99 coin.utxo_id,
100 coin.amount,
101 coin.asset_id,
102 TxPointer::default(),
103 );
104 }
105
106 builder.add_output(Output::Change {
107 to: owner,
108 amount: 0,
109 asset_id: base_asset_id,
110 });
111
112 builder.add_output(Output::ContractCreated {
113 contract_id,
114 state_root,
115 });
116
117 let tx = builder.finalize_as_transaction();
118 let tx_id = tx.id(&chain_id);
119
120 let result = match account
121 .send_transaction(chain_id, &tx, submit_clients)
122 .await
123 {
124 Ok(result) => result,
125 Err(e) => {
126 let err_msg = e.to_string();
127 if crate::wallet_ext::is_coin_invalid_error(&err_msg) {
128 tracing::warn!(
129 %tx_id,
130 "Deploy rejected due to invalid coins, \
131 not returning coins to UtxoManager: {err_msg}",
132 );
133 } else {
134 tracing::warn!(
135 %tx_id,
136 "Deploy failed, returning coins to UtxoManager: {err_msg}",
137 );
138 let mut utxo_manager = utxo_manager.lock().await;
139 utxo_manager.load_from_coins_vec(input_coins);
140 }
141 return Err(FuelsError::Other(e.to_string()));
142 }
143 };
144
145 maybe_return_coins(
147 account,
148 &tx,
149 tx_id,
150 tx_config.and_then(|c| c.expiration_height),
151 utxo_manager,
152 );
153
154 let tx_id = Some(result.tx_id);
155
156 {
157 let mut utxo_manager = utxo_manager.lock().await;
158 utxo_manager.load_from_coins_vec(result.known_coins);
159 utxo_manager.load_from_coins_vec(result.dynamic_coins);
160 }
161
162 Ok(DeployResponse {
163 tx_status: Some(result.tx_status.take_success_checked(None)?),
164 tx_id,
165 contract_id,
166 })
167 }
168}