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(¶ms.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, ¶ms, 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, ¶ms.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 }