wallet_dao.rs 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 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 std::{collections::HashMap, fmt};
  19. use anyhow::{anyhow, Result};
  20. use darkfi::{
  21. rpc::jsonrpc::JsonRequest, tx::Transaction, util::parse::encode_base10,
  22. wallet::walletdb::QueryType,
  23. };
  24. use darkfi_dao_contract::{
  25. client::{
  26. DaoProposeNote, DaoVoteNote, DAO_DAOS_COL_APPROVAL_RATIO_BASE,
  27. DAO_DAOS_COL_APPROVAL_RATIO_QUOT, DAO_DAOS_COL_BULLA_BLIND, DAO_DAOS_COL_CALL_INDEX,
  28. DAO_DAOS_COL_DAO_ID, DAO_DAOS_COL_GOV_TOKEN_ID, DAO_DAOS_COL_LEAF_POSITION,
  29. DAO_DAOS_COL_NAME, DAO_DAOS_COL_PROPOSER_LIMIT, DAO_DAOS_COL_QUORUM, DAO_DAOS_COL_SECRET,
  30. DAO_DAOS_COL_TX_HASH, DAO_DAOS_TABLE, DAO_PROPOSALS_COL_AMOUNT,
  31. DAO_PROPOSALS_COL_BULLA_BLIND, DAO_PROPOSALS_COL_CALL_INDEX, DAO_PROPOSALS_COL_DAO_ID,
  32. DAO_PROPOSALS_COL_LEAF_POSITION, DAO_PROPOSALS_COL_MONEY_SNAPSHOT_TREE,
  33. DAO_PROPOSALS_COL_OUR_VOTE_ID, DAO_PROPOSALS_COL_PROPOSAL_ID,
  34. DAO_PROPOSALS_COL_RECV_PUBLIC, DAO_PROPOSALS_COL_SENDCOIN_TOKEN_ID,
  35. DAO_PROPOSALS_COL_TX_HASH, DAO_PROPOSALS_TABLE, DAO_TREES_COL_DAOS_TREE,
  36. DAO_TREES_COL_PROPOSALS_TREE, DAO_TREES_TABLE, DAO_VOTES_COL_ALL_VOTE_BLIND,
  37. DAO_VOTES_COL_ALL_VOTE_VALUE, DAO_VOTES_COL_CALL_INDEX, DAO_VOTES_COL_PROPOSAL_ID,
  38. DAO_VOTES_COL_TX_HASH, DAO_VOTES_COL_VOTE_ID, DAO_VOTES_COL_VOTE_OPTION,
  39. DAO_VOTES_COL_YES_VOTE_BLIND, DAO_VOTES_TABLE,
  40. },
  41. model::{DaoBulla, DaoMintParams, DaoProposeParams, DaoVoteParams},
  42. DaoFunction,
  43. };
  44. use darkfi_sdk::{
  45. bridgetree,
  46. crypto::{
  47. poseidon_hash, Keypair, MerkleNode, MerkleTree, PublicKey, SecretKey, TokenId,
  48. DAO_CONTRACT_ID,
  49. },
  50. pasta::pallas,
  51. };
  52. use darkfi_serial::{deserialize, serialize, SerialDecodable, SerialEncodable};
  53. use serde_json::json;
  54. use super::Drk;
  55. #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
  56. /// Parameters representing a DAO to be initialized
  57. pub struct DaoParams {
  58. /// The minimum amount of governance tokens needed to open a proposal
  59. pub proposer_limit: u64,
  60. /// Minimal threshold of participating total tokens needed for a proposal to pass
  61. pub quorum: u64,
  62. /// The ratio of winning/total votes needed for a proposal to pass
  63. pub approval_ratio_base: u64,
  64. pub approval_ratio_quot: u64,
  65. /// DAO's governance token ID
  66. pub gov_token_id: TokenId,
  67. /// Secret key for the DAO
  68. pub secret_key: SecretKey,
  69. /// DAO bulla blind
  70. pub bulla_blind: pallas::Base,
  71. }
  72. impl fmt::Display for DaoParams {
  73. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  74. let s = format!(
  75. "{}\n{}\n{}: {} ({})\n{}: {} ({})\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {:?}",
  76. "DAO Parameters",
  77. "==============",
  78. "Proposer limit",
  79. encode_base10(self.proposer_limit, 8),
  80. self.proposer_limit,
  81. "Quorum",
  82. encode_base10(self.quorum, 8),
  83. self.quorum,
  84. "Approval ratio",
  85. self.approval_ratio_quot as f64 / self.approval_ratio_base as f64,
  86. "Governance Token ID",
  87. self.gov_token_id,
  88. "Public key",
  89. PublicKey::from_secret(self.secret_key),
  90. "Secret key",
  91. self.secret_key,
  92. "Bulla blind",
  93. self.bulla_blind,
  94. );
  95. write!(f, "{}", s)
  96. }
  97. }
  98. #[derive(Debug, Clone)]
  99. /// Parameters representing an intialized DAO, optionally deployed on-chain
  100. pub struct Dao {
  101. /// Numeric identifier for the DAO
  102. pub id: u64,
  103. /// Named identifier for the DAO
  104. pub name: String,
  105. /// The minimum amount of governance tokens needed to open a proposal
  106. pub proposer_limit: u64,
  107. /// Minimal threshold of participating total tokens needed for a proposal to pass
  108. pub quorum: u64,
  109. /// The ratio of winning/total votes needed for a proposal to pass
  110. pub approval_ratio_base: u64,
  111. pub approval_ratio_quot: u64,
  112. /// DAO's governance token ID
  113. pub gov_token_id: TokenId,
  114. /// Secret key for the DAO
  115. pub secret_key: SecretKey,
  116. /// DAO bulla blind
  117. pub bulla_blind: pallas::Base,
  118. /// Leaf position of the DAO in the Merkle tree of DAOs
  119. pub leaf_position: Option<bridgetree::Position>,
  120. /// The transaction hash where the DAO was deployed
  121. pub tx_hash: Option<blake3::Hash>,
  122. /// The call index in the transaction where the DAO was deployed
  123. pub call_index: Option<u32>,
  124. }
  125. impl Dao {
  126. pub fn bulla(&self) -> DaoBulla {
  127. let (x, y) = PublicKey::from_secret(self.secret_key).xy();
  128. DaoBulla::from(poseidon_hash([
  129. pallas::Base::from(self.proposer_limit),
  130. pallas::Base::from(self.quorum),
  131. pallas::Base::from(self.approval_ratio_quot),
  132. pallas::Base::from(self.approval_ratio_base),
  133. self.gov_token_id.inner(),
  134. x,
  135. y,
  136. self.bulla_blind,
  137. ]))
  138. }
  139. pub fn keypair(&self) -> Keypair {
  140. let public = PublicKey::from_secret(self.secret_key);
  141. Keypair { public, secret: self.secret_key }
  142. }
  143. }
  144. impl fmt::Display for Dao {
  145. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  146. let s = format!(
  147. "{}\n{}\n{}: {}\n{}: {}\n{}: {} ({})\n{}: {} ({})\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {:?}\n{}: {:?}\n{}: {:?}\n{}: {:?}",
  148. "DAO Parameters",
  149. "==============",
  150. "Name",
  151. self.name,
  152. "Bulla",
  153. self.bulla(),
  154. "Proposer limit",
  155. encode_base10(self.proposer_limit, 8),
  156. self.proposer_limit,
  157. "Quorum",
  158. encode_base10(self.quorum, 8),
  159. self.quorum,
  160. "Approval ratio",
  161. self.approval_ratio_quot as f64 / self.approval_ratio_base as f64,
  162. "Governance Token ID",
  163. self.gov_token_id,
  164. "Public key",
  165. PublicKey::from_secret(self.secret_key),
  166. "Secret key",
  167. self.secret_key,
  168. "Bulla blind",
  169. self.bulla_blind,
  170. "Leaf position",
  171. self.leaf_position,
  172. "Tx hash",
  173. self.tx_hash,
  174. "Call idx",
  175. self.call_index,
  176. );
  177. write!(f, "{}", s)
  178. }
  179. }
  180. #[derive(Debug, Clone)]
  181. /// Parameters representing an initialized DAO proposal, optionally deployed on-chain
  182. pub struct DaoProposal {
  183. /// Numeric identifier for the proposal
  184. pub id: u64,
  185. /// The DAO bulla related to this proposal
  186. pub dao_bulla: DaoBulla,
  187. /// Recipient of this proposal's funds
  188. pub recipient: PublicKey,
  189. /// Amount of this proposal
  190. pub amount: u64,
  191. /// Token ID to be sent
  192. pub token_id: TokenId,
  193. /// Proposal's bulla blind
  194. pub bulla_blind: pallas::Base,
  195. /// Leaf position of this proposal in the Merkle tree of proposals
  196. pub leaf_position: Option<bridgetree::Position>,
  197. /// Snapshotted Money Merkle tree
  198. pub money_snapshot_tree: Option<MerkleTree>,
  199. /// Transaction hash where this proposal was proposed
  200. pub tx_hash: Option<blake3::Hash>,
  201. /// call index in the transaction where this proposal was proposed
  202. pub call_index: Option<u32>,
  203. /// The vote ID we've voted on this proposal
  204. pub vote_id: Option<pallas::Base>,
  205. }
  206. impl DaoProposal {
  207. pub fn bulla(&self) -> pallas::Base {
  208. let (dest_x, dest_y) = self.recipient.xy();
  209. poseidon_hash([
  210. dest_x,
  211. dest_y,
  212. pallas::Base::from(self.amount),
  213. self.token_id.inner(),
  214. self.dao_bulla.inner(),
  215. self.bulla_blind,
  216. ])
  217. }
  218. }
  219. impl fmt::Display for DaoProposal {
  220. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  221. let s = format!(
  222. concat!(
  223. "Proposal parameters\n",
  224. "===================\n",
  225. "DAO Bulla: {}\n",
  226. "Recipient: {}\n",
  227. "Proposal amount: {} ({})\n",
  228. "Proposal Token ID: {:?}\n",
  229. "Proposal bulla blind: {:?}\n",
  230. "Proposal leaf position: {:?}\n",
  231. "Proposal tx hash: {:?}\n",
  232. "Proposal call index: {:?}\n",
  233. "Proposal vote ID: {:?}",
  234. ),
  235. self.dao_bulla,
  236. self.recipient,
  237. encode_base10(self.amount, 8),
  238. self.amount,
  239. self.token_id,
  240. self.bulla_blind,
  241. self.leaf_position,
  242. self.tx_hash,
  243. self.call_index,
  244. self.vote_id,
  245. );
  246. write!(f, "{}", s)
  247. }
  248. }
  249. #[derive(Debug, Clone)]
  250. /// Parameters representing a vote we've made on a DAO proposal
  251. pub struct DaoVote {
  252. /// Numeric identifier for the vote
  253. pub id: u64,
  254. /// Numeric identifier for the proposal related to this vote
  255. pub proposal_id: u64,
  256. /// The vote
  257. pub vote_option: bool,
  258. /// Blinding factor for the yes vote
  259. pub yes_vote_blind: pallas::Scalar,
  260. /// Value of all votes
  261. pub all_vote_value: u64,
  262. /// Blinding facfor of all votes
  263. pub all_vote_blind: pallas::Scalar,
  264. /// Transaction hash where this vote was casted
  265. pub tx_hash: Option<blake3::Hash>,
  266. /// call index in the transaction where this vote was casted
  267. pub call_index: Option<u32>,
  268. }
  269. impl Drk {
  270. /// Initialize wallet with tables for the DAO contract
  271. pub async fn initialize_dao(&self) -> Result<()> {
  272. let wallet_schema = include_str!("../../../src/contract/dao/wallet.sql");
  273. // We perform a request to darkfid with the schema to initialize
  274. // the necessary tables in the wallet.
  275. let req = JsonRequest::new("wallet.exec_sql", json!([wallet_schema]));
  276. let rep = self.rpc_client.request(req).await?;
  277. if rep == true {
  278. eprintln!("Successfully initialized wallet schema for the DAO contract");
  279. } else {
  280. eprintln!("[initialize_dao] Got unexpected reply from darkfid: {}", rep);
  281. }
  282. // Check if we have to initialize the Merkle trees.
  283. // We check if one exists, but we actually create two. This should be written
  284. // a bit better and safer.
  285. let mut tree_needs_init = false;
  286. let query = format!("SELECT {} FROM {}", DAO_TREES_COL_DAOS_TREE, DAO_TREES_TABLE);
  287. let params = json!([query, QueryType::Blob as u8, DAO_TREES_COL_DAOS_TREE]);
  288. let req = JsonRequest::new("wallet.query_row_single", params);
  289. // For now, on success, we don't care what's returned, but in the future
  290. // we should actually check it.
  291. // TODO: The RPC needs a better variant for errors so detailed inspection
  292. // can be done with error codes and all that.
  293. if (self.rpc_client.request(req).await).is_err() {
  294. tree_needs_init = true;
  295. }
  296. if tree_needs_init {
  297. eprintln!("Initializing DAO Merkle trees");
  298. let tree = MerkleTree::new(100);
  299. self.put_dao_trees(&tree, &tree).await?;
  300. eprintln!("Successfully initialized Merkle trees for the DAO contract");
  301. }
  302. Ok(())
  303. }
  304. /// Fetch all DAO secret keys from the wallet
  305. pub async fn get_dao_secrets(&self) -> Result<Vec<SecretKey>> {
  306. let daos = self.get_daos().await?;
  307. let mut ret = Vec::with_capacity(daos.len());
  308. for dao in daos {
  309. ret.push(dao.secret_key);
  310. }
  311. Ok(ret)
  312. }
  313. /// Replace the DAO Merkle trees in the wallet.
  314. pub async fn put_dao_trees(
  315. &self,
  316. daos_tree: &MerkleTree,
  317. proposals_tree: &MerkleTree,
  318. ) -> Result<()> {
  319. let query = format!(
  320. "DELETE FROM {}; INSERT INTO {} ({}, {}) VALUES (?1, ?2);",
  321. DAO_TREES_TABLE, DAO_TREES_TABLE, DAO_TREES_COL_DAOS_TREE, DAO_TREES_COL_PROPOSALS_TREE,
  322. );
  323. let params = json!([
  324. query,
  325. QueryType::Blob as u8,
  326. serialize(daos_tree),
  327. QueryType::Blob as u8,
  328. serialize(proposals_tree),
  329. ]);
  330. let req = JsonRequest::new("wallet.exec_sql", params);
  331. let _ = self.rpc_client.request(req).await?;
  332. Ok(())
  333. }
  334. /// Fetch DAO Merkle trees from the wallet
  335. pub async fn get_dao_trees(&self) -> Result<(MerkleTree, MerkleTree)> {
  336. let query = format!("SELECT * FROM {}", DAO_TREES_TABLE);
  337. let params = json!([
  338. query,
  339. QueryType::Blob as u8,
  340. DAO_TREES_COL_DAOS_TREE,
  341. QueryType::Blob as u8,
  342. DAO_TREES_COL_PROPOSALS_TREE,
  343. ]);
  344. let req = JsonRequest::new("wallet.query_row_single", params);
  345. let rep = self.rpc_client.request(req).await?;
  346. let daos_tree_bytes: Vec<u8> = serde_json::from_value(rep[0].clone())?;
  347. let daos_tree = deserialize(&daos_tree_bytes)?;
  348. let proposals_tree_bytes: Vec<u8> = serde_json::from_value(rep[1].clone())?;
  349. let proposals_tree = deserialize(&proposals_tree_bytes)?;
  350. Ok((daos_tree, proposals_tree))
  351. }
  352. /// Reset the DAO Merkle trees in the wallet
  353. pub async fn reset_dao_trees(&self) -> Result<()> {
  354. eprintln!("Resetting DAO Merkle trees");
  355. let tree = MerkleTree::new(100);
  356. self.put_dao_trees(&tree, &tree).await?;
  357. eprintln!("Successfully reset DAO Merkle trees");
  358. Ok(())
  359. }
  360. /// Reset confirmed DAOs in the wallet
  361. pub async fn reset_daos(&self) -> Result<()> {
  362. eprintln!("Resetting DAO confirmations");
  363. let daos = self.get_daos().await?;
  364. self.unconfirm_daos(&daos).await?;
  365. eprintln!("Successfully unconfirmed DAOs");
  366. Ok(())
  367. }
  368. pub async fn reset_dao_proposals(&self) -> Result<()> {
  369. eprintln!("Resetting DAO proposals");
  370. let query = format!("DELETE FROM {};", DAO_PROPOSALS_TABLE);
  371. let params = json!([query]);
  372. let req = JsonRequest::new("wallet.exec_sql", params);
  373. let _ = self.rpc_client.request(req).await?;
  374. Ok(())
  375. }
  376. pub async fn reset_dao_votes(&self) -> Result<()> {
  377. eprintln!("Resetting DAO votes");
  378. let query = format!("DELETE FROM {};", DAO_VOTES_TABLE);
  379. let params = json!([query]);
  380. let req = JsonRequest::new("wallet.exec_sql", params);
  381. let _ = self.rpc_client.request(req).await?;
  382. Ok(())
  383. }
  384. pub async fn get_dao_id_by_alias(&self, alias_filter: &str) -> Result<u64> {
  385. let query = format!(
  386. "SELECT {}, {} FROM {}",
  387. DAO_DAOS_COL_DAO_ID, DAO_DAOS_COL_NAME, DAO_DAOS_TABLE,
  388. );
  389. let params = json!([
  390. query,
  391. QueryType::Integer as u8,
  392. DAO_DAOS_COL_DAO_ID,
  393. QueryType::Text as u8,
  394. DAO_DAOS_COL_NAME,
  395. ]);
  396. let req = JsonRequest::new("wallet.query_row_multi", params);
  397. let rep = self.rpc_client.request(req).await?;
  398. // The returned thing should be an array of found rows.
  399. let Some(rows) = rep.as_array() else {
  400. return Err(anyhow!("[get_dao_id_by_alias] Unexpected response from darkfid: {}", rep))
  401. };
  402. for row in rows {
  403. let Some(row) = row.as_array() else {
  404. return Err(anyhow!(
  405. "[get_dao_id_by_alias] Unexpected response from darkfid: {}",
  406. rep
  407. ))
  408. };
  409. let alias: String = serde_json::from_value(row[1].clone())?;
  410. if alias != alias_filter {
  411. continue
  412. }
  413. let dao_id: u64 = serde_json::from_value(row[0].clone())?;
  414. return Ok(dao_id)
  415. }
  416. Err(anyhow!("[get_dao_id_by_alias] DAO not found"))
  417. }
  418. /// Convenience function. Interprets the alias either as the DAO alias or its ID
  419. pub async fn get_dao_id(&self, alias: &str) -> Result<u64> {
  420. if let Ok(id) = self.get_dao_id_by_alias(alias).await {
  421. return Ok(id)
  422. }
  423. Ok(alias.parse()?)
  424. }
  425. /// Import given DAO params into the wallet with a given name.
  426. pub async fn import_dao(&self, dao_name: String, dao_params: DaoParams) -> Result<()> {
  427. // First let's check if we've imported this DAO with the given name before.
  428. let daos = self.get_daos().await?;
  429. if daos.iter().any(|x| x.name == dao_name) {
  430. return Err(anyhow!("This DAO has already been imported"))
  431. }
  432. eprintln!("Importing \"{}\" DAO into the wallet", dao_name);
  433. let query = format!(
  434. "INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8);",
  435. DAO_DAOS_TABLE,
  436. DAO_DAOS_COL_NAME,
  437. DAO_DAOS_COL_PROPOSER_LIMIT,
  438. DAO_DAOS_COL_QUORUM,
  439. DAO_DAOS_COL_APPROVAL_RATIO_BASE,
  440. DAO_DAOS_COL_APPROVAL_RATIO_QUOT,
  441. DAO_DAOS_COL_GOV_TOKEN_ID,
  442. DAO_DAOS_COL_SECRET,
  443. DAO_DAOS_COL_BULLA_BLIND,
  444. );
  445. let params = json!([
  446. query,
  447. QueryType::Text as u8,
  448. dao_name,
  449. QueryType::Blob as u8,
  450. serialize(&dao_params.proposer_limit),
  451. QueryType::Blob as u8,
  452. serialize(&dao_params.quorum),
  453. QueryType::Integer as u8,
  454. dao_params.approval_ratio_base,
  455. QueryType::Integer as u8,
  456. dao_params.approval_ratio_quot,
  457. QueryType::Blob as u8,
  458. serialize(&dao_params.gov_token_id),
  459. QueryType::Blob as u8,
  460. serialize(&dao_params.secret_key),
  461. QueryType::Blob as u8,
  462. serialize(&dao_params.bulla_blind),
  463. ]);
  464. let req = JsonRequest::new("wallet.exec_sql", params);
  465. let _ = self.rpc_client.request(req).await?;
  466. eprintln!("DAO imported successfully");
  467. Ok(())
  468. }
  469. /// List DAO(s) imported in the wallet. If an ID is given, just print the
  470. /// metadata for that specific one, if found.
  471. pub async fn dao_list(&self, dao_id: Option<u64>) -> Result<()> {
  472. if let Some(dao_id) = dao_id {
  473. return self.dao_list_single(dao_id).await
  474. }
  475. let daos = self.get_daos().await?;
  476. for dao in daos {
  477. println!("[{}] {}", dao.id, dao.name);
  478. }
  479. Ok(())
  480. }
  481. async fn dao_list_single(&self, dao_id: u64) -> Result<()> {
  482. let dao = self.get_dao_by_id(dao_id).await?;
  483. println!("{}", dao);
  484. Ok(())
  485. }
  486. /// Fetch a DAO given a numeric ID
  487. pub async fn get_dao_by_id(&self, dao_id: u64) -> Result<Dao> {
  488. let daos = self.get_daos().await?;
  489. let Some(dao) = daos.iter().find(|x| x.id == dao_id) else {
  490. return Err(anyhow!("DAO not found in wallet"))
  491. };
  492. Ok(dao.clone())
  493. }
  494. /// Fetch all known DAOs from the wallet.
  495. pub async fn get_daos(&self) -> Result<Vec<Dao>> {
  496. let query = format!("SELECT * FROM {}", DAO_DAOS_TABLE);
  497. let params = json!([
  498. query,
  499. QueryType::Integer as u8,
  500. DAO_DAOS_COL_DAO_ID,
  501. QueryType::Text as u8,
  502. DAO_DAOS_COL_NAME,
  503. QueryType::Blob as u8,
  504. DAO_DAOS_COL_PROPOSER_LIMIT,
  505. QueryType::Blob as u8,
  506. DAO_DAOS_COL_QUORUM,
  507. QueryType::Integer as u8,
  508. DAO_DAOS_COL_APPROVAL_RATIO_BASE,
  509. QueryType::Integer as u8,
  510. DAO_DAOS_COL_APPROVAL_RATIO_QUOT,
  511. QueryType::Blob as u8,
  512. DAO_DAOS_COL_GOV_TOKEN_ID,
  513. QueryType::Blob as u8,
  514. DAO_DAOS_COL_SECRET,
  515. QueryType::Blob as u8,
  516. DAO_DAOS_COL_BULLA_BLIND,
  517. QueryType::OptionBlob as u8,
  518. DAO_DAOS_COL_LEAF_POSITION,
  519. QueryType::OptionBlob as u8,
  520. DAO_DAOS_COL_TX_HASH,
  521. QueryType::OptionInteger as u8,
  522. DAO_DAOS_COL_CALL_INDEX,
  523. ]);
  524. let req = JsonRequest::new("wallet.query_row_multi", params);
  525. let rep = self.rpc_client.request(req).await?;
  526. let Some(rows) = rep.as_array() else {
  527. return Err(anyhow!("[get_daos] Unexpected response from darkfid: {}", rep))
  528. };
  529. let mut daos = Vec::with_capacity(rows.len());
  530. for row in rows {
  531. let Some(row) = row.as_array() else {
  532. return Err(anyhow!("[get_daos] Unexpected response from darkfid: {}", rep))
  533. };
  534. let id: u64 = serde_json::from_value(row[0].clone())?;
  535. let name: String = serde_json::from_value(row[1].clone())?;
  536. let proposer_limit_bytes: Vec<u8> = serde_json::from_value(row[2].clone())?;
  537. let proposer_limit = deserialize(&proposer_limit_bytes)?;
  538. let quorum_bytes: Vec<u8> = serde_json::from_value(row[3].clone())?;
  539. let quorum = deserialize(&quorum_bytes)?;
  540. let approval_ratio_base = serde_json::from_value(row[4].clone())?;
  541. let approval_ratio_quot = serde_json::from_value(row[5].clone())?;
  542. let gov_token_bytes: Vec<u8> = serde_json::from_value(row[6].clone())?;
  543. let gov_token_id = deserialize(&gov_token_bytes)?;
  544. let secret_bytes: Vec<u8> = serde_json::from_value(row[7].clone())?;
  545. let secret_key = deserialize(&secret_bytes)?;
  546. let bulla_blind_bytes: Vec<u8> = serde_json::from_value(row[8].clone())?;
  547. let bulla_blind = deserialize(&bulla_blind_bytes)?;
  548. let leaf_position_bytes: Vec<u8> = serde_json::from_value(row[9].clone())?;
  549. let tx_hash_bytes: Vec<u8> = serde_json::from_value(row[10].clone())?;
  550. let call_index = serde_json::from_value(row[11].clone())?;
  551. let leaf_position = if leaf_position_bytes.is_empty() {
  552. None
  553. } else {
  554. Some(deserialize(&leaf_position_bytes)?)
  555. };
  556. let tx_hash =
  557. if tx_hash_bytes.is_empty() { None } else { Some(deserialize(&tx_hash_bytes)?) };
  558. let dao = Dao {
  559. id,
  560. name,
  561. proposer_limit,
  562. quorum,
  563. approval_ratio_base,
  564. approval_ratio_quot,
  565. gov_token_id,
  566. secret_key,
  567. bulla_blind,
  568. leaf_position,
  569. tx_hash,
  570. call_index,
  571. };
  572. daos.push(dao);
  573. }
  574. // Here we sort the vec by ID. The SQL SELECT statement does not guarantee
  575. // this, so just do it here.
  576. daos.sort_by(|a, b| a.id.cmp(&b.id));
  577. Ok(daos)
  578. }
  579. /// Fetch known unspent balances from the wallet for the given DAO ID
  580. pub async fn dao_balance(&self, dao_id: u64) -> Result<HashMap<String, u64>> {
  581. let daos = self.get_daos().await?;
  582. let Some(dao) = daos.get(dao_id as usize - 1) else {
  583. return Err(anyhow!("DAO with ID {} not found in wallet", dao_id))
  584. };
  585. let mut coins = self.get_coins(false).await?;
  586. coins.retain(|x| x.0.note.spend_hook == DAO_CONTRACT_ID.inner());
  587. coins.retain(|x| x.0.note.user_data == dao.bulla().inner());
  588. // Fill this map with balances
  589. let mut balmap: HashMap<String, u64> = HashMap::new();
  590. for coin in coins {
  591. let mut value = coin.0.note.value;
  592. if let Some(prev) = balmap.get(&coin.0.note.token_id.to_string()) {
  593. value += prev;
  594. }
  595. balmap.insert(coin.0.note.token_id.to_string(), value);
  596. }
  597. Ok(balmap)
  598. }
  599. /// Fetch all known DAO proposals from the wallet given a DAO ID
  600. pub async fn get_dao_proposals(&self, dao_id: u64) -> Result<Vec<DaoProposal>> {
  601. let daos = self.get_daos().await?;
  602. let Some(dao) = daos.get(dao_id as usize - 1) else {
  603. return Err(anyhow!("DAO with ID {} not found in wallet", dao_id))
  604. };
  605. let query = format!(
  606. "SELECT * FROM {} WHERE {} = {}",
  607. DAO_PROPOSALS_TABLE, DAO_PROPOSALS_COL_DAO_ID, dao_id
  608. );
  609. let params = json!([
  610. query,
  611. QueryType::Integer as u8,
  612. DAO_PROPOSALS_COL_PROPOSAL_ID,
  613. QueryType::Integer as u8,
  614. DAO_PROPOSALS_COL_DAO_ID,
  615. QueryType::Blob as u8,
  616. DAO_PROPOSALS_COL_RECV_PUBLIC,
  617. QueryType::Blob as u8,
  618. DAO_PROPOSALS_COL_AMOUNT,
  619. QueryType::Blob as u8,
  620. DAO_PROPOSALS_COL_SENDCOIN_TOKEN_ID,
  621. QueryType::Blob as u8,
  622. DAO_PROPOSALS_COL_BULLA_BLIND,
  623. QueryType::OptionBlob as u8,
  624. DAO_PROPOSALS_COL_LEAF_POSITION,
  625. QueryType::OptionBlob as u8,
  626. DAO_PROPOSALS_COL_MONEY_SNAPSHOT_TREE,
  627. QueryType::OptionBlob as u8,
  628. DAO_PROPOSALS_COL_TX_HASH,
  629. QueryType::OptionInteger as u8,
  630. DAO_PROPOSALS_COL_CALL_INDEX,
  631. QueryType::OptionBlob as u8,
  632. DAO_PROPOSALS_COL_OUR_VOTE_ID,
  633. ]);
  634. let req = JsonRequest::new("wallet.query_row_multi", params);
  635. let rep = self.rpc_client.request(req).await?;
  636. let Some(rows) = rep.as_array() else {
  637. return Err(anyhow!("[get_proposals] Unexpected response from darkfid: {}", rep))
  638. };
  639. let mut proposals = Vec::with_capacity(rows.len());
  640. for row in rows {
  641. let Some(row) = row.as_array() else {
  642. return Err(anyhow!("[get_proposals] Unexpected response from darkfid: {}", rep))
  643. };
  644. let id: u64 = serde_json::from_value(row[0].clone())?;
  645. let dao_id: u64 = serde_json::from_value(row[1].clone())?;
  646. assert!(dao_id == dao.id);
  647. let dao_bulla = dao.bulla();
  648. let recipient_bytes: Vec<u8> = serde_json::from_value(row[2].clone())?;
  649. let recipient = deserialize(&recipient_bytes)?;
  650. let amount_bytes: Vec<u8> = serde_json::from_value(row[3].clone())?;
  651. let amount = deserialize(&amount_bytes)?;
  652. let token_id_bytes: Vec<u8> = serde_json::from_value(row[4].clone())?;
  653. let token_id = deserialize(&token_id_bytes)?;
  654. let bulla_blind_bytes: Vec<u8> = serde_json::from_value(row[5].clone())?;
  655. let bulla_blind = deserialize(&bulla_blind_bytes)?;
  656. let leaf_position_bytes: Vec<u8> = serde_json::from_value(row[6].clone())?;
  657. let money_snapshot_tree_bytes: Vec<u8> = serde_json::from_value(row[7].clone())?;
  658. let tx_hash_bytes: Vec<u8> = serde_json::from_value(row[8].clone())?;
  659. let call_index = serde_json::from_value(row[9].clone())?;
  660. let vote_id_bytes: Vec<u8> = serde_json::from_value(row[10].clone())?;
  661. let leaf_position = if leaf_position_bytes.is_empty() {
  662. None
  663. } else {
  664. Some(deserialize(&leaf_position_bytes)?)
  665. };
  666. let money_snapshot_tree = if money_snapshot_tree_bytes.is_empty() {
  667. None
  668. } else {
  669. Some(deserialize(&money_snapshot_tree_bytes)?)
  670. };
  671. let tx_hash =
  672. if tx_hash_bytes.is_empty() { None } else { Some(deserialize(&tx_hash_bytes)?) };
  673. let vote_id =
  674. if vote_id_bytes.is_empty() { None } else { Some(deserialize(&vote_id_bytes)?) };
  675. let proposal = DaoProposal {
  676. id,
  677. dao_bulla,
  678. recipient,
  679. amount,
  680. token_id,
  681. bulla_blind,
  682. leaf_position,
  683. money_snapshot_tree,
  684. tx_hash,
  685. call_index,
  686. vote_id,
  687. };
  688. proposals.push(proposal);
  689. }
  690. // Here we sort the vec by ID. The SQL SELECT statement does not guarantee
  691. // this, so just do it here.
  692. proposals.sort_by(|a, b| a.id.cmp(&b.id));
  693. Ok(proposals)
  694. }
  695. /// Fetch a DAO proposal by its ID
  696. pub async fn get_dao_proposal_by_id(&self, proposal_id: u64) -> Result<DaoProposal> {
  697. let query = format!(
  698. "SELECT * FROM {} WHERE {} = {}",
  699. DAO_PROPOSALS_TABLE, DAO_PROPOSALS_COL_PROPOSAL_ID, proposal_id
  700. );
  701. let params = json!([
  702. query,
  703. QueryType::Integer as u8,
  704. DAO_PROPOSALS_COL_PROPOSAL_ID,
  705. QueryType::Integer as u8,
  706. DAO_PROPOSALS_COL_DAO_ID,
  707. QueryType::Blob as u8,
  708. DAO_PROPOSALS_COL_RECV_PUBLIC,
  709. QueryType::Blob as u8,
  710. DAO_PROPOSALS_COL_AMOUNT,
  711. QueryType::Blob as u8,
  712. DAO_PROPOSALS_COL_SENDCOIN_TOKEN_ID,
  713. QueryType::Blob as u8,
  714. DAO_PROPOSALS_COL_BULLA_BLIND,
  715. QueryType::OptionBlob as u8,
  716. DAO_PROPOSALS_COL_LEAF_POSITION,
  717. QueryType::OptionBlob as u8,
  718. DAO_PROPOSALS_COL_MONEY_SNAPSHOT_TREE,
  719. QueryType::OptionBlob as u8,
  720. DAO_PROPOSALS_COL_TX_HASH,
  721. QueryType::OptionInteger as u8,
  722. DAO_PROPOSALS_COL_CALL_INDEX,
  723. QueryType::OptionBlob as u8,
  724. DAO_PROPOSALS_COL_OUR_VOTE_ID,
  725. ]);
  726. let req = JsonRequest::new("wallet.query_row_single", params);
  727. let rep = self.rpc_client.request(req).await?;
  728. let Some(row) = rep.as_array() else {
  729. return Err(anyhow!("[get_proposal_by_id] Unexpected response from darkfid: {}", rep))
  730. };
  731. let id: u64 = serde_json::from_value(row[0].clone())?;
  732. let dao_id: u64 = serde_json::from_value(row[1].clone())?;
  733. let recipient_bytes: Vec<u8> = serde_json::from_value(row[2].clone())?;
  734. let recipient = deserialize(&recipient_bytes)?;
  735. let amount_bytes: Vec<u8> = serde_json::from_value(row[3].clone())?;
  736. let amount = deserialize(&amount_bytes)?;
  737. let token_id_bytes: Vec<u8> = serde_json::from_value(row[4].clone())?;
  738. let token_id = deserialize(&token_id_bytes)?;
  739. let bulla_blind_bytes: Vec<u8> = serde_json::from_value(row[5].clone())?;
  740. let bulla_blind = deserialize(&bulla_blind_bytes)?;
  741. let leaf_position_bytes: Vec<u8> = serde_json::from_value(row[6].clone())?;
  742. let money_snapshot_tree_bytes: Vec<u8> = serde_json::from_value(row[7].clone())?;
  743. let tx_hash_bytes: Vec<u8> = serde_json::from_value(row[8].clone())?;
  744. let call_index = serde_json::from_value(row[9].clone())?;
  745. let vote_id_bytes: Vec<u8> = serde_json::from_value(row[10].clone())?;
  746. let leaf_position = if leaf_position_bytes.is_empty() {
  747. None
  748. } else {
  749. Some(deserialize(&leaf_position_bytes)?)
  750. };
  751. let tx_hash =
  752. if tx_hash_bytes.is_empty() { None } else { Some(deserialize(&tx_hash_bytes)?) };
  753. let money_snapshot_tree = if money_snapshot_tree_bytes.is_empty() {
  754. None
  755. } else {
  756. Some(deserialize(&money_snapshot_tree_bytes)?)
  757. };
  758. let vote_id =
  759. if vote_id_bytes.is_empty() { None } else { Some(deserialize(&vote_id_bytes)?) };
  760. let dao = self.get_dao_by_id(dao_id).await?;
  761. let proposal = DaoProposal {
  762. id,
  763. dao_bulla: dao.bulla(),
  764. recipient,
  765. amount,
  766. token_id,
  767. bulla_blind,
  768. leaf_position,
  769. money_snapshot_tree,
  770. tx_hash,
  771. call_index,
  772. vote_id,
  773. };
  774. Ok(proposal)
  775. }
  776. // Fetch all known DAO proposal votes from the wallet given a proposal ID
  777. pub async fn get_dao_proposal_votes(&self, proposal_id: u64) -> Result<Vec<DaoVote>> {
  778. let query = format!(
  779. "SELECT * FROM {} WHERE {} = {}",
  780. DAO_VOTES_TABLE, DAO_VOTES_COL_PROPOSAL_ID, proposal_id
  781. );
  782. let params = json!([
  783. query,
  784. QueryType::Integer as u8,
  785. DAO_VOTES_COL_VOTE_ID,
  786. QueryType::Integer as u8,
  787. DAO_VOTES_COL_PROPOSAL_ID,
  788. QueryType::Integer as u8,
  789. DAO_VOTES_COL_VOTE_OPTION,
  790. QueryType::Blob as u8,
  791. DAO_VOTES_COL_YES_VOTE_BLIND,
  792. QueryType::Blob as u8,
  793. DAO_VOTES_COL_ALL_VOTE_VALUE,
  794. QueryType::Blob as u8,
  795. DAO_VOTES_COL_ALL_VOTE_BLIND,
  796. QueryType::OptionBlob as u8,
  797. DAO_VOTES_COL_TX_HASH,
  798. QueryType::OptionInteger as u8,
  799. DAO_VOTES_COL_CALL_INDEX,
  800. ]);
  801. let req = JsonRequest::new("wallet.query_row_multi", params);
  802. let rep = self.rpc_client.request(req).await?;
  803. let Some(rows) = rep.as_array() else {
  804. return Err(anyhow!(
  805. "[get_dao_proposal_votes] Unexpected response from darkfid: {}",
  806. rep
  807. ))
  808. };
  809. let mut votes = Vec::with_capacity(rows.len());
  810. for row in rows {
  811. let Some(row) = row.as_array() else {
  812. return Err(anyhow!(
  813. "[get_dao_proposal_votes] Unexpected response from darkfid: {}",
  814. rep
  815. ))
  816. };
  817. let id: u64 = serde_json::from_value(row[0].clone())?;
  818. let proposal_id: u64 = serde_json::from_value(row[1].clone())?;
  819. let vote_option: u32 = serde_json::from_value(row[2].clone())?;
  820. let vote_option = vote_option != 0;
  821. let yes_vote_blind_bytes: Vec<u8> = serde_json::from_value(row[3].clone())?;
  822. let yes_vote_blind = deserialize(&yes_vote_blind_bytes)?;
  823. let all_vote_value_bytes: Vec<u8> = serde_json::from_value(row[4].clone())?;
  824. let all_vote_value = deserialize(&all_vote_value_bytes)?;
  825. let all_vote_blind_bytes: Vec<u8> = serde_json::from_value(row[5].clone())?;
  826. let all_vote_blind = deserialize(&all_vote_blind_bytes)?;
  827. let tx_hash_bytes: Vec<u8> = serde_json::from_value(row[6].clone())?;
  828. let call_index = serde_json::from_value(row[7].clone())?;
  829. let tx_hash =
  830. if tx_hash_bytes.is_empty() { None } else { Some(deserialize(&tx_hash_bytes)?) };
  831. let vote = DaoVote {
  832. id,
  833. proposal_id,
  834. vote_option,
  835. yes_vote_blind,
  836. all_vote_value,
  837. all_vote_blind,
  838. tx_hash,
  839. call_index,
  840. };
  841. votes.push(vote);
  842. }
  843. Ok(votes)
  844. }
  845. /// Append data related to DAO contract transactions into the wallet database.
  846. /// Optionally, if `confirm` is true, also append the data in the Merkle trees, etc.
  847. pub async fn apply_tx_dao_data(&self, tx: &Transaction, confirm: bool) -> Result<()> {
  848. let cid = *DAO_CONTRACT_ID;
  849. let mut daos = self.get_daos().await?;
  850. let mut daos_to_confirm = vec![];
  851. let (mut daos_tree, mut proposals_tree) = self.get_dao_trees().await?;
  852. // DAOs that have been minted
  853. let mut new_dao_bullas: Vec<(DaoBulla, Option<blake3::Hash>, u32)> = vec![];
  854. // DAO proposals that have been minted
  855. let mut new_dao_proposals: Vec<(
  856. DaoProposeParams,
  857. Option<MerkleTree>,
  858. Option<blake3::Hash>,
  859. u32,
  860. )> = vec![];
  861. let mut our_proposals: Vec<DaoProposal> = vec![];
  862. // DAO votes that have been seen
  863. let mut new_dao_votes: Vec<(DaoVoteParams, Option<blake3::Hash>, u32)> = vec![];
  864. let mut dao_votes: Vec<DaoVote> = vec![];
  865. // Run through the transaction and see what we got:
  866. for (i, call) in tx.calls.iter().enumerate() {
  867. if call.contract_id == cid && call.data[0] == DaoFunction::Mint as u8 {
  868. eprintln!("Found Dao::Mint in call {}", i);
  869. let params: DaoMintParams = deserialize(&call.data[1..])?;
  870. let tx_hash = if confirm { Some(blake3::hash(&serialize(tx))) } else { None };
  871. new_dao_bullas.push((params.dao_bulla, tx_hash, i as u32));
  872. continue
  873. }
  874. if call.contract_id == cid && call.data[0] == DaoFunction::Propose as u8 {
  875. eprintln!("Found Dao::Propose in call {}", i);
  876. let params: DaoProposeParams = deserialize(&call.data[1..])?;
  877. let tx_hash = if confirm { Some(blake3::hash(&serialize(tx))) } else { None };
  878. // We need to clone the tree here for reproducing the snapshot Merkle root
  879. let money_tree = if confirm { Some(self.get_money_tree().await?) } else { None };
  880. new_dao_proposals.push((params, money_tree, tx_hash, i as u32));
  881. continue
  882. }
  883. if call.contract_id == cid && call.data[0] == DaoFunction::Vote as u8 {
  884. eprintln!("Found Dao::Vote in call {}", i);
  885. let params: DaoVoteParams = deserialize(&call.data[1..])?;
  886. let tx_hash = if confirm { Some(blake3::hash(&serialize(tx))) } else { None };
  887. new_dao_votes.push((params, tx_hash, i as u32));
  888. continue
  889. }
  890. if call.contract_id == cid && call.data[0] == DaoFunction::Exec as u8 {
  891. // This seems to not need any special action
  892. eprintln!("Found Dao::Exec in call {}", i);
  893. continue
  894. }
  895. }
  896. // This code should only be executed when finalized blocks are being scanned.
  897. // Here we write the tx metadata, and actually do Merkle tree appends so we
  898. // have to make sure it's the same for everyone.
  899. if confirm {
  900. for new_bulla in new_dao_bullas {
  901. daos_tree.append(MerkleNode::from(new_bulla.0.inner()));
  902. for dao in daos.iter_mut() {
  903. if dao.bulla() == new_bulla.0 {
  904. eprintln!(
  905. "Found minted DAO {}, noting down for wallet update",
  906. new_bulla.0
  907. );
  908. // We have this DAO imported in our wallet. Add the metadata:
  909. dao.leaf_position = daos_tree.mark();
  910. dao.tx_hash = new_bulla.1;
  911. dao.call_index = Some(new_bulla.2);
  912. daos_to_confirm.push(dao.clone());
  913. }
  914. }
  915. }
  916. for proposal in new_dao_proposals {
  917. proposals_tree.append(MerkleNode::from(proposal.0.proposal_bulla.inner()));
  918. // If we're able to decrypt this note, that's the way to link it
  919. // to a specific DAO.
  920. for dao in &daos {
  921. if let Ok(note) = proposal.0.note.decrypt::<DaoProposeNote>(&dao.secret_key) {
  922. // We managed to decrypt it. Let's place this in a proper
  923. // DaoProposal object. We assume we can just increment the
  924. // ID by looking at how many proposals we already have.
  925. // We also assume we don't mantain duplicate DAOs in the
  926. // wallet.
  927. eprintln!("Managed to decrypt DAO proposal note");
  928. let daos_proposals = self.get_dao_proposals(dao.id).await?;
  929. let our_prop = DaoProposal {
  930. // This ID stuff is flaky.
  931. id: daos_proposals.len() as u64 + our_proposals.len() as u64 + 1,
  932. dao_bulla: dao.bulla(),
  933. recipient: note.proposal.dest,
  934. amount: note.proposal.amount,
  935. token_id: note.proposal.token_id,
  936. bulla_blind: note.proposal.blind,
  937. leaf_position: proposals_tree.mark(),
  938. money_snapshot_tree: proposal.1,
  939. tx_hash: proposal.2,
  940. call_index: Some(proposal.3),
  941. vote_id: None,
  942. };
  943. our_proposals.push(our_prop);
  944. break
  945. }
  946. }
  947. }
  948. for vote in new_dao_votes {
  949. for dao in &daos {
  950. if let Ok(note) = vote.0.note.decrypt::<DaoVoteNote>(&dao.secret_key) {
  951. eprintln!("Managed to decrypt DAO proposal vote note");
  952. let daos_proposals = self.get_dao_proposals(dao.id).await?;
  953. let mut proposal_id = None;
  954. for i in daos_proposals {
  955. if i.bulla() == vote.0.proposal_bulla.inner() {
  956. proposal_id = Some(i.id);
  957. break
  958. }
  959. }
  960. if proposal_id.is_none() {
  961. eprintln!("Warning: Decrypted DaoVoteNote but did not find proposal");
  962. break
  963. }
  964. let v = DaoVote {
  965. id: 0,
  966. proposal_id: proposal_id.unwrap(),
  967. vote_option: note.vote_option,
  968. yes_vote_blind: note.yes_vote_blind,
  969. all_vote_value: note.all_vote_value,
  970. all_vote_blind: note.all_vote_blind,
  971. tx_hash: vote.1,
  972. call_index: Some(vote.2),
  973. };
  974. dao_votes.push(v);
  975. }
  976. }
  977. }
  978. }
  979. if confirm {
  980. self.put_dao_trees(&daos_tree, &proposals_tree).await?;
  981. self.confirm_daos(&daos_to_confirm).await?;
  982. self.put_dao_proposals(&our_proposals).await?;
  983. self.put_dao_votes(&dao_votes).await?;
  984. }
  985. Ok(())
  986. }
  987. /// Confirm already imported DAO metadata into the wallet.
  988. /// Here we just write the leaf position, tx hash, and call index.
  989. /// Panics if the fields are None.
  990. pub async fn confirm_daos(&self, daos: &[Dao]) -> Result<()> {
  991. for dao in daos {
  992. let query = format!(
  993. "UPDATE {} SET {} = ?1, {} = ?2, {} = ?3 WHERE {} = {};",
  994. DAO_DAOS_TABLE,
  995. DAO_DAOS_COL_LEAF_POSITION,
  996. DAO_DAOS_COL_TX_HASH,
  997. DAO_DAOS_COL_CALL_INDEX,
  998. DAO_DAOS_COL_DAO_ID,
  999. dao.id,
  1000. );
  1001. let params = json!([
  1002. query,
  1003. QueryType::Blob as u8,
  1004. serialize(&dao.leaf_position.unwrap()),
  1005. QueryType::Blob as u8,
  1006. serialize(&dao.tx_hash.unwrap()),
  1007. QueryType::Integer as u8,
  1008. dao.call_index.unwrap(),
  1009. ]);
  1010. let req = JsonRequest::new("wallet.exec_sql", params);
  1011. let _ = self.rpc_client.request(req).await?;
  1012. }
  1013. Ok(())
  1014. }
  1015. /// Unconfirm imported DAOs by removing the leaf position, txid, and call index.
  1016. pub async fn unconfirm_daos(&self, daos: &[Dao]) -> Result<()> {
  1017. for dao in daos {
  1018. let query = format!(
  1019. "UPDATE {} SET {} = ?1, {} = ?2, {} = ?3 WHERE {} = {};",
  1020. DAO_DAOS_TABLE,
  1021. DAO_DAOS_COL_LEAF_POSITION,
  1022. DAO_DAOS_COL_TX_HASH,
  1023. DAO_DAOS_COL_CALL_INDEX,
  1024. DAO_DAOS_COL_DAO_ID,
  1025. dao.id,
  1026. );
  1027. let params = json!([
  1028. query,
  1029. QueryType::OptionBlob as u8,
  1030. None::<Vec<u8>>,
  1031. QueryType::OptionBlob as u8,
  1032. None::<Vec<u8>>,
  1033. QueryType::OptionInteger as u8,
  1034. None::<u64>,
  1035. ]);
  1036. let req = JsonRequest::new("wallet.exec_sql", params);
  1037. let _ = self.rpc_client.request(req).await?;
  1038. }
  1039. Ok(())
  1040. }
  1041. /// Import given DAO proposals into the wallet
  1042. pub async fn put_dao_proposals(&self, proposals: &[DaoProposal]) -> Result<()> {
  1043. let daos = self.get_daos().await?;
  1044. for proposal in proposals {
  1045. let Some(dao) = daos.iter().find(|x| x.bulla() == proposal.dao_bulla) else {
  1046. return Err(anyhow!("[put_dao_proposals] Couldn't find respective DAO"))
  1047. };
  1048. let query = format!(
  1049. "INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9);",
  1050. DAO_PROPOSALS_TABLE,
  1051. DAO_PROPOSALS_COL_DAO_ID,
  1052. DAO_PROPOSALS_COL_RECV_PUBLIC,
  1053. DAO_PROPOSALS_COL_AMOUNT,
  1054. DAO_PROPOSALS_COL_SENDCOIN_TOKEN_ID,
  1055. DAO_PROPOSALS_COL_BULLA_BLIND,
  1056. DAO_PROPOSALS_COL_LEAF_POSITION,
  1057. DAO_PROPOSALS_COL_MONEY_SNAPSHOT_TREE,
  1058. DAO_PROPOSALS_COL_TX_HASH,
  1059. DAO_PROPOSALS_COL_CALL_INDEX,
  1060. );
  1061. let params = json!([
  1062. query,
  1063. QueryType::Integer as u8,
  1064. dao.id,
  1065. QueryType::Blob as u8,
  1066. serialize(&proposal.recipient),
  1067. QueryType::Blob as u8,
  1068. serialize(&proposal.amount),
  1069. QueryType::Blob as u8,
  1070. serialize(&proposal.token_id),
  1071. QueryType::Blob as u8,
  1072. serialize(&proposal.bulla_blind),
  1073. QueryType::Blob as u8,
  1074. serialize(&proposal.leaf_position.unwrap()),
  1075. QueryType::Blob as u8,
  1076. serialize(&proposal.money_snapshot_tree.clone().unwrap()),
  1077. QueryType::Blob as u8,
  1078. serialize(&proposal.tx_hash.unwrap()),
  1079. QueryType::Integer as u8,
  1080. proposal.call_index,
  1081. ]);
  1082. let req = JsonRequest::new("wallet.exec_sql", params);
  1083. let _ = self.rpc_client.request(req).await?;
  1084. }
  1085. Ok(())
  1086. }
  1087. /// Import given DAO votes into the wallet
  1088. pub async fn put_dao_votes(&self, votes: &[DaoVote]) -> Result<()> {
  1089. for vote in votes {
  1090. eprintln!("Importing DAO vote into wallet");
  1091. let query = format!(
  1092. "INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7);",
  1093. DAO_VOTES_TABLE,
  1094. DAO_VOTES_COL_PROPOSAL_ID,
  1095. DAO_VOTES_COL_VOTE_OPTION,
  1096. DAO_VOTES_COL_YES_VOTE_BLIND,
  1097. DAO_VOTES_COL_ALL_VOTE_VALUE,
  1098. DAO_VOTES_COL_ALL_VOTE_BLIND,
  1099. DAO_VOTES_COL_TX_HASH,
  1100. DAO_VOTES_COL_CALL_INDEX,
  1101. );
  1102. let params = json!([
  1103. query,
  1104. QueryType::Integer as u8,
  1105. vote.proposal_id,
  1106. QueryType::Integer as u8,
  1107. vote.vote_option as u64,
  1108. QueryType::Blob as u8,
  1109. serialize(&vote.yes_vote_blind),
  1110. QueryType::Blob as u8,
  1111. serialize(&vote.all_vote_value),
  1112. QueryType::Blob as u8,
  1113. serialize(&vote.all_vote_blind),
  1114. QueryType::Blob as u8,
  1115. serialize(&vote.tx_hash.unwrap()),
  1116. QueryType::Integer as u8,
  1117. vote.call_index.unwrap(),
  1118. ]);
  1119. let req = JsonRequest::new("wallet.exec_sql", params);
  1120. let _ = self.rpc_client.request(req).await?;
  1121. eprintln!("DAO vote added to wallet");
  1122. }
  1123. Ok(())
  1124. }
  1125. }