1use std::collections::HashMap;
20
21use lazy_static::lazy_static;
22use rand::rngs::OsRng;
23
24use darkfi::{
25    tx::{ContractCallLeaf, Transaction, TransactionBuilder},
26    zk::{proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
27    zkas::ZkBinary,
28    Error, Result,
29};
30use darkfi_deployooor_contract::{
31    client::{deploy_v1::DeployCallBuilder, lock_v1::LockCallBuilder},
32    model::LockParamsV1,
33    DeployFunction,
34};
35use darkfi_money_contract::MONEY_CONTRACT_ZKAS_FEE_NS_V1;
36use 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};
44use darkfi_serial::{deserialize_async, serialize, serialize_async, AsyncEncodable};
45use rusqlite::types::Value;
46
47use crate::{convert_named_params, error::WalletDbResult, rpc::ScanCache, Drk};
48
49lazy_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
58pub const DEPLOY_AUTH_COL_CONTRACT_ID: &str = "contract_id";
60pub const DEPLOY_AUTH_COL_SECRET_KEY: &str = "secret_key";
61pub const DEPLOY_AUTH_COL_IS_LOCKED: &str = "is_locked";
62pub const DEPLOY_AUTH_COL_LOCK_HEIGHT: &str = "lock_height";
63
64pub const DEPLOY_HISTORY_COL_TX_HASH: &str = "tx_hash";
66pub const DEPLOY_HISTORY_COL_CONTRACT: &str = "contract";
67pub const DEPLOY_HISTORY_COL_TYPE: &str = "type";
68pub const DEPLOY_HISTORY_COL_BLOCK_HEIGHT: &str = "block_height";
69pub const DEPLOY_HISTORY_COL_WASM_BINCODE: &str = "wasm_bincode";
70pub const DEPLOY_HISTORY_COL_DEPLOY_IX: &str = "deploy_ix";
71
72impl Drk {
73    pub fn initialize_deployooor(&self) -> WalletDbResult<()> {
75        let wallet_schema = include_str!("../deploy.sql");
77        self.wallet.exec_batch_sql(wallet_schema)?;
78
79        Ok(())
80    }
81
82    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    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    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    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    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    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    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    async fn get_deploy_auth(&self, contract_id: &ContractId) -> Result<(Keypair, bool)> {
268        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    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    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    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    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        let Some(_) = scan_cache.own_deploy_auths.get(¶ms.public_key.to_bytes()) else {
421            return Ok(false)
422        };
423
424        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    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        let Some(secret_key) = scan_cache.own_deploy_auths.get(&public_key.to_bytes()) else {
454            return Ok(false)
455        };
456
457        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        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    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        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    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        let (deploy_keypair, is_locked) = self.get_deploy_auth(deploy_auth).await?;
527
528        if is_locked {
530            return Err(Error::Custom("[deploy_contract] Contract is locked".to_string()))
531        }
532
533        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        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
549
550        let deploy_call = DeployCallBuilder { deploy_keypair, wasm_bincode, deploy_ix };
552        let deploy_debris = deploy_call.build()?;
553
554        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        let mut tx_builder =
561            TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
562
563        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        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
575
576        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    pub async fn lock_contract(&self, deploy_auth: &ContractId) -> Result<Transaction> {
588        let (deploy_keypair, is_locked) = self.get_deploy_auth(deploy_auth).await?;
590
591        if is_locked {
593            return Err(Error::Custom("[lock_contract] Contract is already locked".to_string()))
594        }
595
596        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        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
612
613        let lock_call = LockCallBuilder { deploy_keypair };
615        let lock_debris = lock_call.build()?;
616
617        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        let mut tx_builder =
624            TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
625
626        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        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
638
639        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}