drk/
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
19use lazy_static::lazy_static;
20use rand::rngs::OsRng;
21
22use darkfi::{
23    tx::{ContractCallLeaf, Transaction, TransactionBuilder},
24    Error, Result,
25};
26use darkfi_deployooor_contract::{
27    client::{deploy_v1::DeployCallBuilder, lock_v1::LockCallBuilder},
28    DeployFunction,
29};
30use darkfi_sdk::{
31    crypto::{ContractId, Keypair, DEPLOYOOOR_CONTRACT_ID},
32    ContractCall,
33};
34use darkfi_serial::{deserialize_async, serialize_async, AsyncEncodable};
35use rusqlite::types::Value;
36
37use crate::{convert_named_params, error::WalletDbResult, Drk};
38
39// Wallet SQL table constant names. These have to represent the `wallet.sql`
40// SQL schema. Table names are prefixed with the contract ID to avoid collisions.
41lazy_static! {
42    pub static ref DEPLOY_AUTH_TABLE: String =
43        format!("{}_deploy_auth", DEPLOYOOOR_CONTRACT_ID.to_string());
44}
45
46// DEPLOY_AUTH_TABLE
47pub const DEPLOY_AUTH_COL_ID: &str = "id";
48pub const DEPLOY_AUTH_COL_DEPLOY_AUTHORITY: &str = "deploy_authority";
49pub const DEPLOY_AUTH_COL_IS_FROZEN: &str = "is_frozen";
50
51impl Drk {
52    /// Initialize wallet with tables for the Deployooor contract.
53    pub fn initialize_deployooor(&self) -> WalletDbResult<()> {
54        // Initialize Deployooor wallet schema
55        let wallet_schema = include_str!("../deploy.sql");
56        self.wallet.exec_batch_sql(wallet_schema)?;
57
58        Ok(())
59    }
60
61    /// Generate a new deploy authority keypair and place it into the wallet
62    pub async fn deploy_auth_keygen(&self) -> WalletDbResult<()> {
63        eprintln!("Generating a new keypair");
64
65        let keypair = Keypair::random(&mut OsRng);
66
67        let query = format!(
68            "INSERT INTO {} ({}, {}) VALUES (?1, ?2);",
69            *DEPLOY_AUTH_TABLE, DEPLOY_AUTH_COL_DEPLOY_AUTHORITY, DEPLOY_AUTH_COL_IS_FROZEN,
70        );
71        self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&keypair).await, 0])?;
72
73        eprintln!("Created new contract deploy authority");
74        println!("Contract ID: {}", ContractId::derive_public(keypair.public));
75
76        Ok(())
77    }
78
79    /// List contract deploy authorities from the wallet
80    pub async fn list_deploy_auth(&self) -> Result<Vec<(i64, ContractId, bool)>> {
81        let rows = match self.wallet.query_multiple(&DEPLOY_AUTH_TABLE, &[], &[]) {
82            Ok(r) => r,
83            Err(e) => {
84                return Err(Error::DatabaseError(format!(
85                    "[list_deploy_auth] Deploy auth retrieval failed: {e:?}",
86                )))
87            }
88        };
89
90        let mut ret = Vec::with_capacity(rows.len());
91        for row in rows {
92            let Value::Integer(idx) = row[0] else {
93                return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse index"))
94            };
95
96            let Value::Blob(ref auth_bytes) = row[1] else {
97                return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse keypair bytes"))
98            };
99            let deploy_auth: Keypair = deserialize_async(auth_bytes).await?;
100
101            let Value::Integer(frozen) = row[2] else {
102                return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse \"is_frozen\""))
103            };
104
105            ret.push((idx, ContractId::derive_public(deploy_auth.public), frozen != 0))
106        }
107
108        Ok(ret)
109    }
110
111    /// Retrieve a deploy authority keypair given an index
112    async fn get_deploy_auth(&self, idx: u64) -> Result<Keypair> {
113        // Find the deploy authority keypair
114        let row = match self.wallet.query_single(
115            &DEPLOY_AUTH_TABLE,
116            &[DEPLOY_AUTH_COL_DEPLOY_AUTHORITY],
117            convert_named_params! {(DEPLOY_AUTH_COL_ID, idx)},
118        ) {
119            Ok(v) => v,
120            Err(e) => {
121                return Err(Error::DatabaseError(format!(
122                    "[deploy_contract] Failed to retrieve deploy authority keypair: {e:?}"
123                )))
124            }
125        };
126
127        let Value::Blob(ref keypair_bytes) = row[0] else {
128            return Err(Error::ParseFailed("[deploy_contract] Failed to parse keypair bytes"))
129        };
130        let keypair: Keypair = deserialize_async(keypair_bytes).await?;
131
132        Ok(keypair)
133    }
134
135    /// Create a feeless contract deployment transaction.
136    pub async fn deploy_contract(
137        &self,
138        deploy_auth: u64,
139        wasm_bincode: Vec<u8>,
140        deploy_ix: Vec<u8>,
141    ) -> Result<Transaction> {
142        // Fetch the keypair
143        let deploy_keypair = self.get_deploy_auth(deploy_auth).await?;
144
145        // Create the contract call
146        let deploy_call = DeployCallBuilder { deploy_keypair, wasm_bincode, deploy_ix };
147        let deploy_debris = deploy_call.build()?;
148
149        // Encode the call
150        let mut data = vec![DeployFunction::DeployV1 as u8];
151        deploy_debris.params.encode_async(&mut data).await?;
152        let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
153        let mut tx_builder =
154            TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
155
156        let mut tx = tx_builder.build()?;
157        let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
158        tx.signatures = vec![sigs];
159
160        Ok(tx)
161    }
162
163    /// Create a feeless contract redeployment lock transaction.
164    pub async fn lock_contract(&self, deploy_auth: u64) -> Result<Transaction> {
165        // Fetch the keypair
166        let deploy_keypair = self.get_deploy_auth(deploy_auth).await?;
167
168        // Create the contract call
169        let lock_call = LockCallBuilder { deploy_keypair };
170        let lock_debris = lock_call.build()?;
171
172        // Encode the call
173        let mut data = vec![DeployFunction::LockV1 as u8];
174        lock_debris.params.encode_async(&mut data).await?;
175        let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
176        let mut tx_builder =
177            TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
178
179        let mut tx = tx_builder.build()?;
180        let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
181        tx.signatures = vec![sigs];
182
183        Ok(tx)
184    }
185}