deploy.rs 8.9 KB

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