deploy.rs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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, output: &mut Vec<String>) -> WalletDbResult<()> {
  56. output.push(String::from("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. output.push(String::from("Created new contract deploy authority"));
  71. output.push(format!("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, output: &mut Vec<String>) -> WalletDbResult<()> {
  76. output.push(String::from("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. output.push(String::from("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(
  88. &self,
  89. height: &u32,
  90. output: &mut Vec<String>,
  91. ) -> WalletDbResult<()> {
  92. output.push(format!("Resetting deploy authorities frozen status after: {height}"));
  93. let query = format!(
  94. "UPDATE {} SET {} = 0, {} = NULL WHERE {} > ?1;",
  95. *DEPLOY_AUTH_TABLE,
  96. DEPLOY_AUTH_COL_IS_FROZEN,
  97. DEPLOY_AUTH_COL_FREEZE_HEIGHT,
  98. DEPLOY_AUTH_COL_FREEZE_HEIGHT
  99. );
  100. self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
  101. output.push(String::from("Successfully reset deploy authorities frozen status"));
  102. Ok(())
  103. }
  104. /// List contract deploy authorities from the wallet
  105. pub async fn list_deploy_auth(&self) -> Result<Vec<(i64, ContractId, bool, Option<u32>)>> {
  106. let rows = match self.wallet.query_multiple(&DEPLOY_AUTH_TABLE, &[], &[]) {
  107. Ok(r) => r,
  108. Err(e) => {
  109. return Err(Error::DatabaseError(format!(
  110. "[list_deploy_auth] Deploy auth retrieval failed: {e}",
  111. )))
  112. }
  113. };
  114. let mut ret = Vec::with_capacity(rows.len());
  115. for row in rows {
  116. let Value::Integer(idx) = row[0] else {
  117. return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse index"))
  118. };
  119. let Value::Blob(ref auth_bytes) = row[1] else {
  120. return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse keypair bytes"))
  121. };
  122. let deploy_auth: Keypair = deserialize_async(auth_bytes).await?;
  123. let Value::Integer(frozen) = row[2] else {
  124. return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse \"is_frozen\""))
  125. };
  126. let freeze_height = match row[3] {
  127. Value::Integer(freeze_height) => {
  128. let Ok(freeze_height) = u32::try_from(freeze_height) else {
  129. return Err(Error::ParseFailed(
  130. "[list_deploy_auth] Freeze height parsing failed",
  131. ))
  132. };
  133. Some(freeze_height)
  134. }
  135. Value::Null => None,
  136. _ => {
  137. return Err(Error::ParseFailed(
  138. "[list_deploy_auth] Freeze height parsing failed",
  139. ))
  140. }
  141. };
  142. ret.push((
  143. idx,
  144. ContractId::derive_public(deploy_auth.public),
  145. frozen != 0,
  146. freeze_height,
  147. ))
  148. }
  149. Ok(ret)
  150. }
  151. /// Retrieve a deploy authority keypair given an index
  152. async fn get_deploy_auth(&self, idx: u64) -> Result<Keypair> {
  153. // Find the deploy authority keypair
  154. let row = match self.wallet.query_single(
  155. &DEPLOY_AUTH_TABLE,
  156. &[DEPLOY_AUTH_COL_DEPLOY_AUTHORITY],
  157. convert_named_params! {(DEPLOY_AUTH_COL_ID, idx)},
  158. ) {
  159. Ok(v) => v,
  160. Err(e) => {
  161. return Err(Error::DatabaseError(format!(
  162. "[deploy_contract] Failed to retrieve deploy authority keypair: {e}"
  163. )))
  164. }
  165. };
  166. let Value::Blob(ref keypair_bytes) = row[0] else {
  167. return Err(Error::ParseFailed("[deploy_contract] Failed to parse keypair bytes"))
  168. };
  169. let keypair: Keypair = deserialize_async(keypair_bytes).await?;
  170. Ok(keypair)
  171. }
  172. /// Create a feeless contract deployment transaction.
  173. pub async fn deploy_contract(
  174. &self,
  175. deploy_auth: u64,
  176. wasm_bincode: Vec<u8>,
  177. deploy_ix: Vec<u8>,
  178. ) -> Result<Transaction> {
  179. // Fetch the keypair
  180. let deploy_keypair = self.get_deploy_auth(deploy_auth).await?;
  181. // Create the contract call
  182. let deploy_call = DeployCallBuilder { deploy_keypair, wasm_bincode, deploy_ix };
  183. let deploy_debris = deploy_call.build()?;
  184. // Encode the call
  185. let mut data = vec![DeployFunction::DeployV1 as u8];
  186. deploy_debris.params.encode_async(&mut data).await?;
  187. let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
  188. let mut tx_builder =
  189. TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
  190. let mut tx = tx_builder.build()?;
  191. let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
  192. tx.signatures = vec![sigs];
  193. Ok(tx)
  194. }
  195. /// Create a feeless contract redeployment lock transaction.
  196. pub async fn lock_contract(&self, deploy_auth: u64) -> Result<Transaction> {
  197. // Fetch the keypair
  198. let deploy_keypair = self.get_deploy_auth(deploy_auth).await?;
  199. // Create the contract call
  200. let lock_call = LockCallBuilder { deploy_keypair };
  201. let lock_debris = lock_call.build()?;
  202. // Encode the call
  203. let mut data = vec![DeployFunction::LockV1 as u8];
  204. lock_debris.params.encode_async(&mut data).await?;
  205. let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
  206. let mut tx_builder =
  207. TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
  208. let mut tx = tx_builder.build()?;
  209. let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
  210. tx.signatures = vec![sigs];
  211. Ok(tx)
  212. }
  213. }