wallet_dao.rs 45 KB

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