/ bin / drk / src / deploy.rs
deploy.rs
  1  /* This file is part of DarkFi (https://dark.fi)
  2   *
  3   * Copyright (C) 2020-2025 Dyne.org foundation
  4   *
  5   * This program is free software: you can redistribute it and/or modify
  6   * it under the terms of the GNU Affero General Public License as
  7   * published by the Free Software Foundation, either version 3 of the
  8   * License, or (at your option) any later version.
  9   *
 10   * This program is distributed in the hope that it will be useful,
 11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13   * GNU Affero General Public License for more details.
 14   *
 15   * You should have received a copy of the GNU Affero General Public License
 16   * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17   */
 18  
 19  use std::collections::HashMap;
 20  
 21  use lazy_static::lazy_static;
 22  use rand::rngs::OsRng;
 23  
 24  use darkfi::{
 25      tx::{ContractCallLeaf, Transaction, TransactionBuilder},
 26      zk::{proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
 27      zkas::ZkBinary,
 28      Error, Result,
 29  };
 30  use darkfi_deployooor_contract::{
 31      client::{deploy_v1::DeployCallBuilder, lock_v1::LockCallBuilder},
 32      model::LockParamsV1,
 33      DeployFunction,
 34  };
 35  use darkfi_money_contract::MONEY_CONTRACT_ZKAS_FEE_NS_V1;
 36  use darkfi_sdk::{
 37      crypto::{
 38          ContractId, Keypair, PublicKey, SecretKey, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID,
 39      },
 40      deploy::DeployParamsV1,
 41      tx::TransactionHash,
 42      ContractCall,
 43  };
 44  use darkfi_serial::{deserialize_async, serialize, serialize_async, AsyncEncodable};
 45  use rusqlite::types::Value;
 46  
 47  use crate::{convert_named_params, error::WalletDbResult, rpc::ScanCache, Drk};
 48  
 49  // Wallet SQL table constant names. These have to represent the `wallet.sql`
 50  // SQL schema. Table names are prefixed with the contract ID to avoid collisions.
 51  lazy_static! {
 52      pub static ref DEPLOY_AUTH_TABLE: String =
 53          format!("{}_deploy_auth", DEPLOYOOOR_CONTRACT_ID.to_string());
 54      pub static ref DEPLOY_HISTORY_TABLE: String =
 55          format!("{}_deploy_history", DEPLOYOOOR_CONTRACT_ID.to_string());
 56  }
 57  
 58  // DEPLOY_AUTH_TABLE
 59  pub const DEPLOY_AUTH_COL_CONTRACT_ID: &str = "contract_id";
 60  pub const DEPLOY_AUTH_COL_SECRET_KEY: &str = "secret_key";
 61  pub const DEPLOY_AUTH_COL_IS_LOCKED: &str = "is_locked";
 62  pub const DEPLOY_AUTH_COL_LOCK_HEIGHT: &str = "lock_height";
 63  
 64  // DEPLOY_HISTORY_TABLE
 65  pub const DEPLOY_HISTORY_COL_TX_HASH: &str = "tx_hash";
 66  pub const DEPLOY_HISTORY_COL_CONTRACT: &str = "contract";
 67  pub const DEPLOY_HISTORY_COL_TYPE: &str = "type";
 68  pub const DEPLOY_HISTORY_COL_BLOCK_HEIGHT: &str = "block_height";
 69  pub const DEPLOY_HISTORY_COL_WASM_BINCODE: &str = "wasm_bincode";
 70  pub const DEPLOY_HISTORY_COL_DEPLOY_IX: &str = "deploy_ix";
 71  
 72  impl Drk {
 73      /// Initialize wallet with tables for the Deployooor contract.
 74      pub fn initialize_deployooor(&self) -> WalletDbResult<()> {
 75          // Initialize Deployooor wallet schema
 76          let wallet_schema = include_str!("../deploy.sql");
 77          self.wallet.exec_batch_sql(wallet_schema)?;
 78  
 79          Ok(())
 80      }
 81  
 82      /// Generate a new deploy authority keypair and place it into the wallet
 83      pub async fn deploy_auth_keygen(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
 84          output.push(String::from("Generating a new keypair"));
 85  
 86          let secret_key = SecretKey::random(&mut OsRng);
 87          let contract_id = ContractId::derive_public(PublicKey::from_secret(secret_key));
 88          let lock_height: Option<u32> = None;
 89  
 90          let query = format!(
 91              "INSERT INTO {} ({}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4);",
 92              *DEPLOY_AUTH_TABLE,
 93              DEPLOY_AUTH_COL_CONTRACT_ID,
 94              DEPLOY_AUTH_COL_SECRET_KEY,
 95              DEPLOY_AUTH_COL_IS_LOCKED,
 96              DEPLOY_AUTH_COL_LOCK_HEIGHT,
 97          );
 98          self.wallet.exec_sql(
 99              &query,
100              rusqlite::params![
101                  serialize_async(&contract_id).await,
102                  serialize_async(&secret_key).await,
103                  0,
104                  lock_height
105              ],
106          )?;
107  
108          output.push(String::from("Created new contract deploy authority"));
109          output.push(format!("Contract ID: {contract_id}"));
110  
111          Ok(())
112      }
113  
114      /// Insert a deploy authority history record into the wallet.
115      pub fn put_deploy_history_record(
116          &self,
117          tx_hash: &TransactionHash,
118          contract: &ContractId,
119          tx_type: &str,
120          block_height: &u32,
121          wasm_bincode: &Option<Vec<u8>>,
122          deploy_ix: &Option<Vec<u8>>,
123      ) -> WalletDbResult<()> {
124          let query = format!(
125              "INSERT INTO {} ({}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6);",
126              *DEPLOY_HISTORY_TABLE,
127              DEPLOY_HISTORY_COL_TX_HASH,
128              DEPLOY_HISTORY_COL_CONTRACT,
129              DEPLOY_HISTORY_COL_TYPE,
130              DEPLOY_HISTORY_COL_BLOCK_HEIGHT,
131              DEPLOY_HISTORY_COL_WASM_BINCODE,
132              DEPLOY_HISTORY_COL_DEPLOY_IX,
133          );
134          self.wallet.exec_sql(
135              &query,
136              rusqlite::params![
137                  tx_hash.to_string(),
138                  serialize(contract),
139                  tx_type,
140                  block_height,
141                  serialize(wasm_bincode),
142                  serialize(deploy_ix),
143              ],
144          )?;
145  
146          Ok(())
147      }
148  
149      /// Reset all contract deploy authorities locked status in the wallet.
150      pub fn reset_deploy_authorities(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
151          output.push(String::from("Resetting deploy authorities locked status"));
152          let query = format!(
153              "UPDATE {} SET {} = 0, {} = NULL;",
154              *DEPLOY_AUTH_TABLE, DEPLOY_AUTH_COL_IS_LOCKED, DEPLOY_AUTH_COL_LOCK_HEIGHT
155          );
156          self.wallet.exec_sql(&query, &[])?;
157          output.push(String::from("Successfully reset deploy authorities locked status"));
158  
159          Ok(())
160      }
161  
162      /// Remove deploy authorities locked status in the wallet that
163      /// where locked after provided height.
164      pub fn unlock_deploy_authorities_after(
165          &self,
166          height: &u32,
167          output: &mut Vec<String>,
168      ) -> WalletDbResult<()> {
169          output.push(format!("Resetting deploy authorities locked status after: {height}"));
170          let query = format!(
171              "UPDATE {} SET {} = 0, {} = NULL WHERE {} > ?1;",
172              *DEPLOY_AUTH_TABLE,
173              DEPLOY_AUTH_COL_IS_LOCKED,
174              DEPLOY_AUTH_COL_LOCK_HEIGHT,
175              DEPLOY_AUTH_COL_LOCK_HEIGHT
176          );
177          self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
178          output.push(String::from("Successfully reset deploy authorities locked status"));
179  
180          Ok(())
181      }
182  
183      /// Reset all contracts history records in the wallet.
184      pub fn reset_deploy_history(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
185          output.push(String::from("Resetting deployment history"));
186          let query = format!("DELETE FROM {};", *DEPLOY_HISTORY_TABLE);
187          self.wallet.exec_sql(&query, &[])?;
188          output.push(String::from("Successfully deployment history"));
189  
190          Ok(())
191      }
192  
193      /// Remove the contracts history records in the wallet that were
194      /// created after provided height.
195      pub fn remove_deploy_history_after(
196          &self,
197          height: &u32,
198          output: &mut Vec<String>,
199      ) -> WalletDbResult<()> {
200          output.push(format!("Removing deployment history records after: {height}"));
201          let query = format!(
202              "DELETE FROM {} WHERE {} > ?1;",
203              *DEPLOY_HISTORY_TABLE, DEPLOY_HISTORY_COL_BLOCK_HEIGHT
204          );
205          self.wallet.exec_sql(&query, rusqlite::params![height])?;
206          output.push(String::from("Successfully removed deployment history records"));
207  
208          Ok(())
209      }
210  
211      /// List contract deploy authorities from the wallet
212      pub async fn list_deploy_auth(
213          &self,
214      ) -> Result<Vec<(ContractId, SecretKey, bool, Option<u32>)>> {
215          let rows = match self.wallet.query_multiple(&DEPLOY_AUTH_TABLE, &[], &[]) {
216              Ok(r) => r,
217              Err(e) => {
218                  return Err(Error::DatabaseError(format!(
219                      "[list_deploy_auth] Deploy auth retrieval failed: {e}",
220                  )))
221              }
222          };
223  
224          let mut ret = Vec::with_capacity(rows.len());
225          for row in rows {
226              let Value::Blob(ref contract_id_bytes) = row[0] else {
227                  return Err(Error::ParseFailed(
228                      "[list_deploy_auth] Failed to parse contract id bytes",
229                  ))
230              };
231              let contract_id: ContractId = deserialize_async(contract_id_bytes).await?;
232  
233              let Value::Blob(ref secret_key_bytes) = row[1] else {
234                  return Err(Error::ParseFailed(
235                      "[list_deploy_auth] Failed to parse secret key bytes",
236                  ))
237              };
238              let secret_key: SecretKey = deserialize_async(secret_key_bytes).await?;
239  
240              let Value::Integer(locked) = row[2] else {
241                  return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse \"is_locked\""))
242              };
243  
244              let lock_height = match row[3] {
245                  Value::Integer(lock_height) => {
246                      let Ok(lock_height) = u32::try_from(lock_height) else {
247                          return Err(Error::ParseFailed(
248                              "[list_deploy_auth] Lock height parsing failed",
249                          ))
250                      };
251                      Some(lock_height)
252                  }
253                  Value::Null => None,
254                  _ => {
255                      return Err(Error::ParseFailed("[list_deploy_auth] Lock height parsing failed"))
256                  }
257              };
258  
259              ret.push((contract_id, secret_key, locked != 0, lock_height))
260          }
261  
262          Ok(ret)
263      }
264  
265      /// Retrieve a deploy authority keypair and status for provided
266      /// contract id.
267      async fn get_deploy_auth(&self, contract_id: &ContractId) -> Result<(Keypair, bool)> {
268          // Find the deploy authority keypair
269          let row = match self.wallet.query_single(
270              &DEPLOY_AUTH_TABLE,
271              &[DEPLOY_AUTH_COL_SECRET_KEY, DEPLOY_AUTH_COL_IS_LOCKED],
272              convert_named_params! {(DEPLOY_AUTH_COL_CONTRACT_ID, serialize_async(contract_id).await)},
273          ) {
274              Ok(v) => v,
275              Err(e) => {
276                  return Err(Error::DatabaseError(format!(
277                      "[get_deploy_auth] Failed to retrieve deploy authority keypair: {e}"
278                  )))
279              }
280          };
281  
282          let Value::Blob(ref secret_key_bytes) = row[0] else {
283              return Err(Error::ParseFailed("[get_deploy_auth] Failed to parse secret key bytes"))
284          };
285          let secret_key: SecretKey = deserialize_async(secret_key_bytes).await?;
286          let keypair = Keypair::new(secret_key);
287  
288          let Value::Integer(locked) = row[1] else {
289              return Err(Error::ParseFailed("[get_deploy_auth] Failed to parse \"is_locked\""))
290          };
291  
292          Ok((keypair, locked != 0))
293      }
294  
295      /// Retrieve contract deploy authorities keys map from the wallet.
296      pub async fn get_deploy_auths_keys_map(&self) -> Result<HashMap<[u8; 32], SecretKey>> {
297          let rows = match self.wallet.query_multiple(
298              &DEPLOY_AUTH_TABLE,
299              &[DEPLOY_AUTH_COL_SECRET_KEY],
300              &[],
301          ) {
302              Ok(r) => r,
303              Err(e) => {
304                  return Err(Error::DatabaseError(format!(
305                  "[get_deploy_auths_keys_map] Failed to retrieve deploy authorities secret keys: {e}",
306              )))
307              }
308          };
309  
310          let mut ret = HashMap::new();
311          for row in rows {
312              let Value::Blob(ref secret_key_bytes) = row[0] else {
313                  return Err(Error::ParseFailed(
314                      "[get_deploy_auths_keys_map] Failed to parse secret key bytes",
315                  ))
316              };
317              let secret_key: SecretKey = deserialize_async(secret_key_bytes).await?;
318              ret.insert(PublicKey::from_secret(secret_key).to_bytes(), secret_key);
319          }
320  
321          Ok(ret)
322      }
323  
324      /// Retrieve all deploy history records basic information, for
325      /// provided contract id.
326      pub async fn get_deploy_auth_history(
327          &self,
328          contract_id: &ContractId,
329      ) -> Result<Vec<(String, String, u32)>> {
330          let rows = match self.wallet.query_multiple(
331              &DEPLOY_HISTORY_TABLE,
332              &[DEPLOY_HISTORY_COL_TX_HASH, DEPLOY_HISTORY_COL_TYPE, DEPLOY_HISTORY_COL_BLOCK_HEIGHT],
333              convert_named_params! {(DEPLOY_HISTORY_COL_CONTRACT, serialize_async(contract_id).await)},
334          ) {
335              Ok(r) => r,
336              Err(e) => {
337                  return Err(Error::DatabaseError(format!(
338                  "[get_deploy_auth_history] Failed to retrieve deploy authority history records: {e}",
339              )))
340              }
341          };
342  
343          let mut ret = Vec::with_capacity(rows.len());
344          for row in rows {
345              let Value::Text(ref tx_hash) = row[0] else {
346                  return Err(Error::ParseFailed(
347                      "[get_deploy_auth_history] Transaction hash parsing failed",
348                  ))
349              };
350  
351              let Value::Text(ref tx_type) = row[1] else {
352                  return Err(Error::ParseFailed("[get_deploy_auth_history] Type parsing failed"))
353              };
354  
355              let Value::Integer(block_height) = row[2] else {
356                  return Err(Error::ParseFailed(
357                      "[get_deploy_auth_history] Block height parsing failed",
358                  ))
359              };
360              let Ok(block_height) = u32::try_from(block_height) else {
361                  return Err(Error::ParseFailed(
362                      "[get_deploy_auth_history] Block height parsing failed",
363                  ))
364              };
365  
366              ret.push((tx_hash.clone(), tx_type.clone(), block_height));
367          }
368  
369          Ok(ret)
370      }
371  
372      /// Retrieve deploy history record WASM bincode and deployed
373      /// instruction, for provided transaction hash.
374      pub async fn get_deploy_history_record_data(
375          &self,
376          tx_hash: &str,
377      ) -> Result<(Option<Vec<u8>>, Option<Vec<u8>>)> {
378          let row = match self.wallet.query_single(
379              &DEPLOY_HISTORY_TABLE,
380              &[DEPLOY_HISTORY_COL_WASM_BINCODE, DEPLOY_HISTORY_COL_DEPLOY_IX],
381              convert_named_params! {(DEPLOY_HISTORY_COL_TX_HASH, tx_hash)},
382          ) {
383              Ok(v) => v,
384              Err(e) => {
385                  return Err(Error::DatabaseError(format!(
386                      "[get_deploy_history_record] Failed to retrieve deploy history record: {e}"
387                  )))
388              }
389          };
390  
391          let Value::Blob(ref wasm_bincode_bytes) = row[0] else {
392              return Err(Error::ParseFailed(
393                  "[get_deploy_history_record] Failed to parse wasm bincode bytes",
394              ))
395          };
396          let wasm_bincode: Option<Vec<u8>> = deserialize_async(wasm_bincode_bytes).await?;
397  
398          let Value::Blob(ref deploy_ix_bytes) = row[1] else {
399              return Err(Error::ParseFailed(
400                  "[get_deploy_history_record] Failed to parse deploy ix bytes",
401              ))
402          };
403          let deploy_ix: Option<Vec<u8>> = deserialize_async(deploy_ix_bytes).await?;
404  
405          Ok((wasm_bincode, deploy_ix))
406      }
407  
408      /// Auxiliary function to apply `DeployFunction::DeployV1` call
409      /// data to the wallet.
410      /// Returns a flag indicating if the provided call refers to our
411      /// own wallet.
412      fn apply_deploy_deploy_data(
413          &self,
414          scan_cache: &ScanCache,
415          params: &DeployParamsV1,
416          tx_hash: &TransactionHash,
417          block_height: &u32,
418      ) -> Result<bool> {
419          // Check if we have the deploy authority key
420          let Some(_) = scan_cache.own_deploy_auths.get(&params.public_key.to_bytes()) else {
421              return Ok(false)
422          };
423  
424          // Create a new history record containing the deployment data
425          if let Err(e) = self.put_deploy_history_record(
426              tx_hash,
427              &ContractId::derive_public(params.public_key),
428              "DEPLOYMENT",
429              block_height,
430              &Some(params.wasm_bincode.clone()),
431              &Some(params.ix.clone()),
432          ) {
433              return Err(Error::DatabaseError(format!(
434                  "[apply_deploy_deploy_data] Inserting deploy history recod failed: {e}"
435              )))
436          }
437  
438          Ok(true)
439      }
440  
441      /// Auxiliary function to apply `DeployFunction::LockV1` call
442      /// data to the wallet.
443      /// Returns a flag indicating if the provided call refers to our
444      /// own wallet.
445      async fn apply_deploy_lock_data(
446          &self,
447          scan_cache: &ScanCache,
448          public_key: &PublicKey,
449          tx_hash: &TransactionHash,
450          lock_height: &u32,
451      ) -> Result<bool> {
452          // Check if we have the deploy authority key
453          let Some(secret_key) = scan_cache.own_deploy_auths.get(&public_key.to_bytes()) else {
454              return Ok(false)
455          };
456  
457          // Lock contract
458          let secret_key = serialize_async(secret_key).await;
459          let query = format!(
460              "UPDATE {} SET {} = 1, {} = ?1 WHERE {} = ?2;",
461              *DEPLOY_AUTH_TABLE,
462              DEPLOY_AUTH_COL_IS_LOCKED,
463              DEPLOY_AUTH_COL_LOCK_HEIGHT,
464              DEPLOY_AUTH_COL_SECRET_KEY
465          );
466          if let Err(e) =
467              self.wallet.exec_sql(&query, rusqlite::params![Some(*lock_height), secret_key])
468          {
469              return Err(Error::DatabaseError(format!(
470                  "[apply_deploy_lock_data] Lock deploy authority failed: {e}"
471              )))
472          }
473  
474          // Create a new history record for the lock transaction
475          if let Err(e) = self.put_deploy_history_record(
476              tx_hash,
477              &ContractId::derive_public(*public_key),
478              "LOCK",
479              lock_height,
480              &None,
481              &None,
482          ) {
483              return Err(Error::DatabaseError(format!(
484                  "[apply_deploy_lock_data] Inserting deploy history recod failed: {e}"
485              )))
486          }
487  
488          Ok(true)
489      }
490  
491      /// Append data related to DeployoOor contract transactions into
492      /// the wallet database and update the provided scan cache.
493      /// Returns a flag indicating if provided data refer to our own
494      /// wallet.
495      pub async fn apply_tx_deploy_data(
496          &self,
497          scan_cache: &mut ScanCache,
498          data: &[u8],
499          tx_hash: &TransactionHash,
500          block_height: &u32,
501      ) -> Result<bool> {
502          // Run through the transaction call data and see what we got:
503          match DeployFunction::try_from(data[0])? {
504              DeployFunction::DeployV1 => {
505                  scan_cache.log(String::from("[apply_tx_deploy_data] Found Deploy::DeployV1 call"));
506                  let params: DeployParamsV1 = deserialize_async(&data[1..]).await?;
507                  self.apply_deploy_deploy_data(scan_cache, &params, tx_hash, block_height)
508              }
509              DeployFunction::LockV1 => {
510                  scan_cache.log(String::from("[apply_tx_deploy_data] Found Deploy::LockV1 call"));
511                  let params: LockParamsV1 = deserialize_async(&data[1..]).await?;
512                  self.apply_deploy_lock_data(scan_cache, &params.public_key, tx_hash, block_height)
513                      .await
514              }
515          }
516      }
517  
518      /// Create a feeless contract deployment transaction.
519      pub async fn deploy_contract(
520          &self,
521          deploy_auth: &ContractId,
522          wasm_bincode: Vec<u8>,
523          deploy_ix: Vec<u8>,
524      ) -> Result<Transaction> {
525          // Fetch the keypair and its status
526          let (deploy_keypair, is_locked) = self.get_deploy_auth(deploy_auth).await?;
527  
528          // Check lock status
529          if is_locked {
530              return Err(Error::Custom("[deploy_contract] Contract is locked".to_string()))
531          }
532  
533          // Now we need to do a lookup for the zkas proof bincodes, and create
534          // the circuit objects and proving keys so we can build the transaction.
535          // We also do this through the RPC.
536          let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
537  
538          let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
539          else {
540              return Err(Error::Custom("[deploy_contract] Fee circuit not found".to_string()))
541          };
542  
543          let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
544  
545          let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
546  
547          // Creating Fee circuit proving keys
548          let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
549  
550          // Create the contract call
551          let deploy_call = DeployCallBuilder { deploy_keypair, wasm_bincode, deploy_ix };
552          let deploy_debris = deploy_call.build()?;
553  
554          // Encode the call
555          let mut data = vec![DeployFunction::DeployV1 as u8];
556          deploy_debris.params.encode_async(&mut data).await?;
557          let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
558  
559          // Create the TransactionBuilder containing above call
560          let mut tx_builder =
561              TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
562  
563          // We first have to execute the fee-less tx to gather its used gas, and then we feed
564          // it into the fee-creating function.
565          let mut tx = tx_builder.build()?;
566          let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
567          tx.signatures.push(sigs);
568  
569          let tree = self.get_money_tree().await?;
570          let (fee_call, fee_proofs, fee_secrets) =
571              self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
572  
573          // Append the fee call to the transaction
574          tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
575  
576          // Now build the actual transaction and sign it with all necessary keys.
577          let mut tx = tx_builder.build()?;
578          let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
579          tx.signatures.push(sigs);
580          let sigs = tx.create_sigs(&fee_secrets)?;
581          tx.signatures.push(sigs);
582  
583          Ok(tx)
584      }
585  
586      /// Create a feeless contract redeployment lock transaction.
587      pub async fn lock_contract(&self, deploy_auth: &ContractId) -> Result<Transaction> {
588          // Fetch the keypair and its status
589          let (deploy_keypair, is_locked) = self.get_deploy_auth(deploy_auth).await?;
590  
591          // Check lock status
592          if is_locked {
593              return Err(Error::Custom("[lock_contract] Contract is already locked".to_string()))
594          }
595  
596          // Now we need to do a lookup for the zkas proof bincodes, and create
597          // the circuit objects and proving keys so we can build the transaction.
598          // We also do this through the RPC.
599          let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
600  
601          let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
602          else {
603              return Err(Error::Custom("[lock_contract] Fee circuit not found".to_string()))
604          };
605  
606          let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
607  
608          let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
609  
610          // Creating Fee circuit proving keys
611          let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
612  
613          // Create the contract call
614          let lock_call = LockCallBuilder { deploy_keypair };
615          let lock_debris = lock_call.build()?;
616  
617          // Encode the call
618          let mut data = vec![DeployFunction::LockV1 as u8];
619          lock_debris.params.encode_async(&mut data).await?;
620          let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
621  
622          // Create the TransactionBuilder containing above call
623          let mut tx_builder =
624              TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
625  
626          // We first have to execute the fee-less tx to gather its used gas, and then we feed
627          // it into the fee-creating function.
628          let mut tx = tx_builder.build()?;
629          let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
630          tx.signatures.push(sigs);
631  
632          let tree = self.get_money_tree().await?;
633          let (fee_call, fee_proofs, fee_secrets) =
634              self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
635  
636          // Append the fee call to the transaction
637          tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
638  
639          // Now build the actual transaction and sign it with all necessary keys.
640          let mut tx = tx_builder.build()?;
641          let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
642          tx.signatures.push(sigs);
643          let sigs = tx.create_sigs(&fee_secrets)?;
644          tx.signatures.push(sigs);
645  
646          Ok(tx)
647      }
648  }