dao.rs 92 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427
  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 lazy_static::lazy_static;
  20. use num_bigint::BigUint;
  21. use rand::rngs::OsRng;
  22. use rusqlite::types::Value;
  23. use darkfi::{
  24. tx::{ContractCallLeaf, Transaction, TransactionBuilder},
  25. util::parse::{decode_base10, encode_base10},
  26. zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit},
  27. zkas::ZkBinary,
  28. Error, Result,
  29. };
  30. use darkfi_dao_contract::{
  31. blockwindow,
  32. client::{
  33. make_mint_call, DaoAuthMoneyTransferCall, DaoExecCall, DaoProposeCall,
  34. DaoProposeStakeInput, DaoVoteCall, DaoVoteInput,
  35. },
  36. model::{
  37. Dao, DaoAuthCall, DaoBulla, DaoExecParams, DaoMintParams, DaoProposal, DaoProposalBulla,
  38. DaoProposeParams, DaoVoteParams,
  39. },
  40. DaoFunction, DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS,
  41. DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS, DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
  42. DAO_CONTRACT_ZKAS_DAO_MINT_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS,
  43. DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS, DAO_CONTRACT_ZKAS_DAO_VOTE_INPUT_NS,
  44. DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS,
  45. };
  46. use darkfi_money_contract::{
  47. client::transfer_v1::{select_coins, TransferCallBuilder, TransferCallInput},
  48. model::{CoinAttributes, Nullifier, TokenId},
  49. MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
  50. MONEY_CONTRACT_ZKAS_MINT_NS_V1,
  51. };
  52. use darkfi_sdk::{
  53. bridgetree,
  54. crypto::{
  55. poseidon_hash,
  56. smt::{MemoryStorageFp, PoseidonFp, SmtMemoryFp, EMPTY_NODES_FP},
  57. util::{fp_mod_fv, fp_to_u64},
  58. BaseBlind, Blind, FuncId, FuncRef, Keypair, MerkleNode, MerkleTree, PublicKey, ScalarBlind,
  59. SecretKey, DAO_CONTRACT_ID, MONEY_CONTRACT_ID,
  60. },
  61. dark_tree::DarkTree,
  62. pasta::pallas,
  63. tx::TransactionHash,
  64. ContractCall,
  65. };
  66. use darkfi_serial::{
  67. async_trait, deserialize_async, serialize_async, AsyncEncodable, SerialDecodable,
  68. SerialEncodable,
  69. };
  70. use crate::{
  71. convert_named_params,
  72. error::{WalletDbError, WalletDbResult},
  73. money::{BALANCE_BASE10_DECIMALS, MONEY_SMT_COL_KEY, MONEY_SMT_COL_VALUE, MONEY_SMT_TABLE},
  74. walletdb::{WalletSmt, WalletStorage},
  75. Drk,
  76. };
  77. // Wallet SQL table constant names. These have to represent the `dao.sql`
  78. // SQL schema. Table names are prefixed with the contract ID to avoid collisions.
  79. lazy_static! {
  80. pub static ref DAO_DAOS_TABLE: String = format!("{}_dao_daos", DAO_CONTRACT_ID.to_string());
  81. pub static ref DAO_TREES_TABLE: String = format!("{}_dao_trees", DAO_CONTRACT_ID.to_string());
  82. pub static ref DAO_COINS_TABLE: String = format!("{}_dao_coins", DAO_CONTRACT_ID.to_string());
  83. pub static ref DAO_PROPOSALS_TABLE: String =
  84. format!("{}_dao_proposals", DAO_CONTRACT_ID.to_string());
  85. pub static ref DAO_VOTES_TABLE: String = format!("{}_dao_votes", DAO_CONTRACT_ID.to_string());
  86. }
  87. // DAO_DAOS_TABLE
  88. pub const DAO_DAOS_COL_BULLA: &str = "bulla";
  89. pub const DAO_DAOS_COL_NAME: &str = "name";
  90. pub const DAO_DAOS_COL_PARAMS: &str = "params";
  91. pub const DAO_DAOS_COL_LEAF_POSITION: &str = "leaf_position";
  92. pub const DAO_DAOS_COL_TX_HASH: &str = "tx_hash";
  93. pub const DAO_DAOS_COL_CALL_INDEX: &str = "call_index";
  94. // DAO_TREES_TABLE
  95. pub const DAO_TREES_COL_DAOS_TREE: &str = "daos_tree";
  96. pub const DAO_TREES_COL_PROPOSALS_TREE: &str = "proposals_tree";
  97. // DAO_PROPOSALS_TABLE
  98. pub const DAO_PROPOSALS_COL_BULLA: &str = "bulla";
  99. pub const DAO_PROPOSALS_COL_DAO_BULLA: &str = "dao_bulla";
  100. pub const DAO_PROPOSALS_COL_PROPOSAL: &str = "proposal";
  101. pub const DAO_PROPOSALS_COL_DATA: &str = "data";
  102. pub const DAO_PROPOSALS_COL_LEAF_POSITION: &str = "leaf_position";
  103. pub const DAO_PROPOSALS_COL_MONEY_SNAPSHOT_TREE: &str = "money_snapshot_tree";
  104. pub const DAO_PROPOSALS_COL_NULLIFIERS_SMT_SNAPSHOT: &str = "nullifiers_smt_snapshot";
  105. pub const DAO_PROPOSALS_COL_TX_HASH: &str = "tx_hash";
  106. pub const DAO_PROPOSALS_COL_CALL_INDEX: &str = "call_index";
  107. pub const DAO_PROPOSALS_COL_EXEC_TX_HASH: &str = "exec_tx_hash";
  108. // DAO_VOTES_TABLE
  109. pub const DAO_VOTES_COL_PROPOSAL_BULLA: &str = "proposal_bulla";
  110. pub const DAO_VOTES_COL_VOTE_OPTION: &str = "vote_option";
  111. pub const DAO_VOTES_COL_YES_VOTE_BLIND: &str = "yes_vote_blind";
  112. pub const DAO_VOTES_COL_ALL_VOTE_VALUE: &str = "all_vote_value";
  113. pub const DAO_VOTES_COL_ALL_VOTE_BLIND: &str = "all_vote_blind";
  114. pub const DAO_VOTES_COL_TX_HASH: &str = "tx_hash";
  115. pub const DAO_VOTES_COL_CALL_INDEX: &str = "call_index";
  116. pub const DAO_VOTES_COL_NULLIFIERS: &str = "nullifiers";
  117. #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
  118. /// Parameters representing a DAO to be initialized
  119. pub struct DaoParams {
  120. /// The on chain representation of the DAO
  121. pub dao: Dao,
  122. /// Secret key for the DAO
  123. pub secret_key: SecretKey,
  124. }
  125. impl DaoParams {
  126. pub fn new(
  127. proposer_limit: u64,
  128. quorum: u64,
  129. approval_ratio_base: u64,
  130. approval_ratio_quot: u64,
  131. gov_token_id: TokenId,
  132. secret_key: SecretKey,
  133. bulla_blind: BaseBlind,
  134. ) -> Self {
  135. // TODO: use diff keys and early exec quorum
  136. let dao = Dao {
  137. proposer_limit,
  138. quorum,
  139. early_exec_quorum: quorum,
  140. approval_ratio_base,
  141. approval_ratio_quot,
  142. gov_token_id,
  143. notes_public_key: PublicKey::from_secret(secret_key),
  144. proposer_public_key: PublicKey::from_secret(secret_key),
  145. proposals_public_key: PublicKey::from_secret(secret_key),
  146. votes_public_key: PublicKey::from_secret(secret_key),
  147. exec_public_key: PublicKey::from_secret(secret_key),
  148. early_exec_public_key: PublicKey::from_secret(secret_key),
  149. bulla_blind,
  150. };
  151. Self { dao, secret_key }
  152. }
  153. }
  154. impl fmt::Display for DaoParams {
  155. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  156. // TODO: add missing fields
  157. let s = format!(
  158. "{}\n{}\n{}: {} ({})\n{}: {} ({})\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {:?}",
  159. "DAO Parameters",
  160. "==============",
  161. "Proposer limit",
  162. encode_base10(self.dao.proposer_limit, BALANCE_BASE10_DECIMALS),
  163. self.dao.proposer_limit,
  164. "Quorum",
  165. encode_base10(self.dao.quorum, BALANCE_BASE10_DECIMALS),
  166. self.dao.quorum,
  167. "Approval ratio",
  168. self.dao.approval_ratio_quot as f64 / self.dao.approval_ratio_base as f64,
  169. "Governance Token ID",
  170. self.dao.gov_token_id,
  171. "Public key",
  172. self.dao.notes_public_key,
  173. "Secret key",
  174. self.secret_key,
  175. "Bulla blind",
  176. self.dao.bulla_blind,
  177. );
  178. write!(f, "{}", s)
  179. }
  180. }
  181. #[derive(Debug, Clone)]
  182. /// Structure representing a `DAO_DAOS_TABLE` record.
  183. pub struct DaoRecord {
  184. /// Name identifier for the DAO
  185. pub name: String,
  186. /// DAO parameters
  187. pub params: DaoParams,
  188. /// Leaf position of the DAO in the Merkle tree of DAOs
  189. pub leaf_position: Option<bridgetree::Position>,
  190. /// The transaction hash where the DAO was deployed
  191. pub tx_hash: Option<TransactionHash>,
  192. /// The call index in the transaction where the DAO was deployed
  193. pub call_index: Option<u8>,
  194. }
  195. impl DaoRecord {
  196. pub fn new(
  197. name: String,
  198. params: DaoParams,
  199. leaf_position: Option<bridgetree::Position>,
  200. tx_hash: Option<TransactionHash>,
  201. call_index: Option<u8>,
  202. ) -> Self {
  203. Self { name, params, leaf_position, tx_hash, call_index }
  204. }
  205. pub fn bulla(&self) -> DaoBulla {
  206. self.params.dao.to_bulla()
  207. }
  208. pub fn keypair(&self) -> Keypair {
  209. let public = PublicKey::from_secret(self.params.secret_key);
  210. Keypair { public, secret: self.params.secret_key }
  211. }
  212. }
  213. impl fmt::Display for DaoRecord {
  214. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  215. let leaf_position = match self.leaf_position {
  216. Some(p) => format!("{p:?}"),
  217. None => "None".to_string(),
  218. };
  219. let tx_hash = match self.tx_hash {
  220. Some(t) => format!("{t}"),
  221. None => "None".to_string(),
  222. };
  223. let call_index = match self.call_index {
  224. Some(c) => format!("{c}"),
  225. None => "None".to_string(),
  226. };
  227. // TODO: add missing fields
  228. let s = format!(
  229. "{}\n{}\n{}: {}\n{}: {}\n{}: {} ({})\n{}: {} ({})\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}",
  230. "DAO Parameters",
  231. "==============",
  232. "Name",
  233. self.name,
  234. "Bulla",
  235. self.bulla(),
  236. "Proposer limit",
  237. encode_base10(self.params.dao.proposer_limit, BALANCE_BASE10_DECIMALS),
  238. self.params.dao.proposer_limit,
  239. "Quorum",
  240. encode_base10(self.params.dao.quorum, BALANCE_BASE10_DECIMALS),
  241. self.params.dao.quorum,
  242. "Approval ratio",
  243. self.params.dao.approval_ratio_quot as f64 / self.params.dao.approval_ratio_base as f64,
  244. "Governance Token ID",
  245. self.params.dao.gov_token_id,
  246. "Public key",
  247. self.params.dao.notes_public_key,
  248. "Secret key",
  249. self.params.secret_key,
  250. "Bulla blind",
  251. self.params.dao.bulla_blind,
  252. "Leaf position",
  253. leaf_position,
  254. "Transaction hash",
  255. tx_hash,
  256. "Call index",
  257. call_index,
  258. );
  259. write!(f, "{}", s)
  260. }
  261. }
  262. #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
  263. /// Structure representing a `DAO_PROPOSALS_TABLE` record.
  264. pub struct ProposalRecord {
  265. /// The on chain representation of the proposal
  266. pub proposal: DaoProposal,
  267. /// Plaintext proposal call data the members share between them
  268. pub data: Option<Vec<u8>>,
  269. /// Leaf position of the proposal in the Merkle tree of proposals
  270. pub leaf_position: Option<bridgetree::Position>,
  271. /// Money merkle tree snapshot for reproducing the snapshot Merkle root
  272. pub money_snapshot_tree: Option<MerkleTree>,
  273. /// Money nullifiers SMT snapshot for reproducing the snapshot Merkle root
  274. pub nullifiers_smt_snapshot: Option<HashMap<BigUint, pallas::Base>>,
  275. /// The transaction hash where the proposal was deployed
  276. pub tx_hash: Option<TransactionHash>,
  277. /// The call index in the transaction where the proposal was deployed
  278. pub call_index: Option<u8>,
  279. /// The transaction hash where the proposal was executed
  280. pub exec_tx_hash: Option<TransactionHash>,
  281. }
  282. impl ProposalRecord {
  283. pub fn bulla(&self) -> DaoProposalBulla {
  284. self.proposal.to_bulla()
  285. }
  286. }
  287. impl fmt::Display for ProposalRecord {
  288. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  289. let leaf_position = match self.leaf_position {
  290. Some(p) => format!("{p:?}"),
  291. None => "None".to_string(),
  292. };
  293. let tx_hash = match self.tx_hash {
  294. Some(t) => format!("{t}"),
  295. None => "None".to_string(),
  296. };
  297. let call_index = match self.call_index {
  298. Some(c) => format!("{c}"),
  299. None => "None".to_string(),
  300. };
  301. let s = format!(
  302. "{}\n{}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {} ({})",
  303. "Proposal parameters",
  304. "===================",
  305. "Bulla",
  306. self.bulla(),
  307. "DAO Bulla",
  308. self.proposal.dao_bulla,
  309. "Proposal leaf position",
  310. leaf_position,
  311. "Proposal transaction hash",
  312. tx_hash,
  313. "Proposal call index",
  314. call_index,
  315. "Creation block window",
  316. self.proposal.creation_blockwindow,
  317. "Duration",
  318. self.proposal.duration_blockwindows,
  319. "Block windows"
  320. );
  321. write!(f, "{}", s)
  322. }
  323. }
  324. #[derive(Debug, Clone)]
  325. /// Structure representing a `DAO_VOTES_TABLE` record.
  326. pub struct VoteRecord {
  327. /// Numeric identifier for the vote
  328. pub id: u64,
  329. /// Bulla identifier of the proposal this vote is for
  330. pub proposal: DaoProposalBulla,
  331. /// The vote
  332. pub vote_option: bool,
  333. /// Blinding factor for the yes vote
  334. pub yes_vote_blind: ScalarBlind,
  335. /// Value of all votes
  336. pub all_vote_value: u64,
  337. /// Blinding facfor of all votes
  338. pub all_vote_blind: ScalarBlind,
  339. /// Transaction hash where this vote was casted
  340. pub tx_hash: TransactionHash,
  341. /// Call index in the transaction where this vote was casted
  342. pub call_index: u8,
  343. /// Vote input nullifiers
  344. pub nullifiers: Vec<Nullifier>,
  345. }
  346. impl Drk {
  347. /// Initialize wallet with tables for the DAO contract.
  348. pub async fn initialize_dao(&self) -> WalletDbResult<()> {
  349. // Initialize DAO wallet schema
  350. let wallet_schema = include_str!("../dao.sql");
  351. self.wallet.exec_batch_sql(wallet_schema)?;
  352. // Check if we have to initialize the Merkle trees.
  353. // We check if one exists, but we actually create two. This should be written
  354. // a bit better and safer.
  355. // For now, on success, we don't care what's returned, but in the future
  356. // we should actually check it.
  357. if self.get_dao_trees().await.is_err() {
  358. println!("Initializing DAO Merkle trees");
  359. let tree = serialize_async(&MerkleTree::new(1)).await;
  360. let query = format!(
  361. "INSERT INTO {} ({}, {}) VALUES (?1, ?2);",
  362. *DAO_TREES_TABLE, DAO_TREES_COL_DAOS_TREE, DAO_TREES_COL_PROPOSALS_TREE
  363. );
  364. self.wallet.exec_sql(&query, rusqlite::params![tree, tree])?;
  365. println!("Successfully initialized Merkle trees for the DAO contract");
  366. }
  367. Ok(())
  368. }
  369. /// Replace the DAO Merkle trees in the wallet.
  370. pub async fn put_dao_trees(
  371. &self,
  372. daos_tree: &MerkleTree,
  373. proposals_tree: &MerkleTree,
  374. ) -> WalletDbResult<()> {
  375. let query = format!(
  376. "UPDATE {} SET {} = ?1, {} = ?2;",
  377. *DAO_TREES_TABLE, DAO_TREES_COL_DAOS_TREE, DAO_TREES_COL_PROPOSALS_TREE
  378. );
  379. self.wallet.exec_sql(
  380. &query,
  381. rusqlite::params![
  382. serialize_async(daos_tree).await,
  383. serialize_async(proposals_tree).await
  384. ],
  385. )
  386. }
  387. /// Fetch DAO Merkle trees from the wallet.
  388. pub async fn get_dao_trees(&self) -> Result<(MerkleTree, MerkleTree)> {
  389. let row = match self.wallet.query_single(&DAO_TREES_TABLE, &[], &[]) {
  390. Ok(r) => r,
  391. Err(e) => {
  392. return Err(Error::DatabaseError(format!(
  393. "[get_dao_trees] Trees retrieval failed: {e:?}"
  394. )))
  395. }
  396. };
  397. let Value::Blob(ref daos_tree_bytes) = row[0] else {
  398. return Err(Error::ParseFailed("[get_dao_trees] DAO tree bytes parsing failed"))
  399. };
  400. let daos_tree = deserialize_async(daos_tree_bytes).await?;
  401. let Value::Blob(ref proposals_tree_bytes) = row[1] else {
  402. return Err(Error::ParseFailed("[get_dao_trees] Proposals tree bytes parsing failed"))
  403. };
  404. let proposals_tree = deserialize_async(proposals_tree_bytes).await?;
  405. Ok((daos_tree, proposals_tree))
  406. }
  407. /// Auxiliary function to fetch the current DAO Merkle trees state,
  408. /// as an update query.
  409. pub async fn get_dao_trees_state_query(&self) -> Result<String> {
  410. // Grab current DAO trees
  411. let (daos_tree, proposals_tree) = self.get_dao_trees().await?;
  412. // Create the update query
  413. match self.wallet.create_prepared_statement(
  414. &format!(
  415. "UPDATE {} SET {} = ?1, {} = ?2;",
  416. *DAO_TREES_TABLE, DAO_TREES_COL_DAOS_TREE, DAO_TREES_COL_PROPOSALS_TREE
  417. ),
  418. rusqlite::params![
  419. serialize_async(&daos_tree).await,
  420. serialize_async(&proposals_tree).await
  421. ],
  422. ) {
  423. Ok(q) => Ok(q),
  424. Err(e) => Err(Error::DatabaseError(format!(
  425. "[get_dao_trees_state_query] Creating query for DAO trees failed: {e:?}"
  426. ))),
  427. }
  428. }
  429. /// Fetch all DAO secret keys from the wallet.
  430. pub async fn get_dao_secrets(&self) -> Result<Vec<SecretKey>> {
  431. let daos = self.get_daos().await?;
  432. let mut ret = Vec::with_capacity(daos.len());
  433. for dao in daos {
  434. ret.push(dao.params.secret_key);
  435. }
  436. Ok(ret)
  437. }
  438. /// Auxiliary function to parse a `DAO_DAOS_TABLE` record.
  439. async fn parse_dao_record(&self, row: &[Value]) -> Result<DaoRecord> {
  440. let Value::Text(ref name) = row[1] else {
  441. return Err(Error::ParseFailed("[parse_dao_record] Name parsing failed"))
  442. };
  443. let name = name.clone();
  444. let Value::Blob(ref params_bytes) = row[2] else {
  445. return Err(Error::ParseFailed("[parse_dao_record] Params bytes parsing failed"))
  446. };
  447. let params = deserialize_async(params_bytes).await?;
  448. let leaf_position = match row[3] {
  449. Value::Blob(ref leaf_position_bytes) => {
  450. Some(deserialize_async(leaf_position_bytes).await?)
  451. }
  452. Value::Null => None,
  453. _ => {
  454. return Err(Error::ParseFailed(
  455. "[parse_dao_record] Leaf position bytes parsing failed",
  456. ))
  457. }
  458. };
  459. let tx_hash = match row[4] {
  460. Value::Blob(ref tx_hash_bytes) => Some(deserialize_async(tx_hash_bytes).await?),
  461. Value::Null => None,
  462. _ => {
  463. return Err(Error::ParseFailed(
  464. "[parse_dao_record] Transaction hash bytes parsing failed",
  465. ))
  466. }
  467. };
  468. let call_index = match row[5] {
  469. Value::Integer(call_index) => {
  470. let Ok(call_index) = u8::try_from(call_index) else {
  471. return Err(Error::ParseFailed("[parse_dao_record] Call index parsing failed"))
  472. };
  473. Some(call_index)
  474. }
  475. Value::Null => None,
  476. _ => return Err(Error::ParseFailed("[parse_dao_record] Call index parsing failed")),
  477. };
  478. let dao = DaoRecord::new(name, params, leaf_position, tx_hash, call_index);
  479. Ok(dao)
  480. }
  481. /// Fetch all known DAOs from the wallet.
  482. pub async fn get_daos(&self) -> Result<Vec<DaoRecord>> {
  483. let rows = match self.wallet.query_multiple(&DAO_DAOS_TABLE, &[], &[]) {
  484. Ok(r) => r,
  485. Err(e) => {
  486. return Err(Error::DatabaseError(format!("[get_daos] DAOs retrieval failed: {e:?}")))
  487. }
  488. };
  489. let mut daos = Vec::with_capacity(rows.len());
  490. for row in rows {
  491. daos.push(self.parse_dao_record(&row).await?);
  492. }
  493. Ok(daos)
  494. }
  495. /// Auxiliary function to parse a proposal record row.
  496. async fn parse_dao_proposal(&self, row: &[Value]) -> Result<ProposalRecord> {
  497. let Value::Blob(ref proposal_bytes) = row[2] else {
  498. return Err(Error::ParseFailed(
  499. "[get_dao_proposals] Proposal bytes bytes parsing failed",
  500. ))
  501. };
  502. let proposal = deserialize_async(proposal_bytes).await?;
  503. let data = match row[3] {
  504. Value::Blob(ref data_bytes) => Some(data_bytes.clone()),
  505. Value::Null => None,
  506. _ => return Err(Error::ParseFailed("[get_dao_proposals] Data bytes parsing failed")),
  507. };
  508. let leaf_position = match row[4] {
  509. Value::Blob(ref leaf_position_bytes) => {
  510. Some(deserialize_async(leaf_position_bytes).await?)
  511. }
  512. Value::Null => None,
  513. _ => {
  514. return Err(Error::ParseFailed(
  515. "[get_dao_proposals] Leaf position bytes parsing failed",
  516. ))
  517. }
  518. };
  519. let money_snapshot_tree = match row[5] {
  520. Value::Blob(ref money_snapshot_tree_bytes) => {
  521. Some(deserialize_async(money_snapshot_tree_bytes).await?)
  522. }
  523. Value::Null => None,
  524. _ => {
  525. return Err(Error::ParseFailed(
  526. "[get_dao_proposals] Money snapshot tree bytes parsing failed",
  527. ))
  528. }
  529. };
  530. let nullifiers_smt_snapshot = match row[6] {
  531. Value::Blob(ref nullifiers_smt_snapshot_bytes) => {
  532. Some(deserialize_async(nullifiers_smt_snapshot_bytes).await?)
  533. }
  534. Value::Null => None,
  535. _ => {
  536. return Err(Error::ParseFailed(
  537. "[get_dao_proposals] Nullifiers SMT snapshot bytes parsing failed",
  538. ))
  539. }
  540. };
  541. let tx_hash = match row[7] {
  542. Value::Blob(ref tx_hash_bytes) => Some(deserialize_async(tx_hash_bytes).await?),
  543. Value::Null => None,
  544. _ => {
  545. return Err(Error::ParseFailed(
  546. "[get_dao_proposals] Transaction hash bytes parsing failed",
  547. ))
  548. }
  549. };
  550. let call_index = match row[8] {
  551. Value::Integer(call_index) => {
  552. let Ok(call_index) = u8::try_from(call_index) else {
  553. return Err(Error::ParseFailed("[get_dao_proposals] Call index parsing failed"))
  554. };
  555. Some(call_index)
  556. }
  557. Value::Null => None,
  558. _ => return Err(Error::ParseFailed("[get_dao_proposals] Call index parsing failed")),
  559. };
  560. let exec_tx_hash = match row[9] {
  561. Value::Blob(ref exec_tx_hash_bytes) => {
  562. Some(deserialize_async(exec_tx_hash_bytes).await?)
  563. }
  564. Value::Null => None,
  565. _ => {
  566. return Err(Error::ParseFailed(
  567. "[get_dao_proposals] Execution transaction hash bytes parsing failed",
  568. ))
  569. }
  570. };
  571. Ok(ProposalRecord {
  572. proposal,
  573. data,
  574. leaf_position,
  575. money_snapshot_tree,
  576. nullifiers_smt_snapshot,
  577. tx_hash,
  578. call_index,
  579. exec_tx_hash,
  580. })
  581. }
  582. /// Fetch all known DAO proposals from the wallet given a DAO name.
  583. pub async fn get_dao_proposals(&self, name: &str) -> Result<Vec<ProposalRecord>> {
  584. let Ok(dao) = self.get_dao_by_name(name).await else {
  585. return Err(Error::DatabaseError(format!(
  586. "[get_dao_proposals] DAO with name {name} not found in wallet"
  587. )))
  588. };
  589. let rows = match self.wallet.query_multiple(
  590. &DAO_PROPOSALS_TABLE,
  591. &[],
  592. convert_named_params! {(DAO_PROPOSALS_COL_DAO_BULLA, serialize_async(&dao.bulla()).await)},
  593. ) {
  594. Ok(r) => r,
  595. Err(e) => {
  596. return Err(Error::DatabaseError(format!(
  597. "[get_dao_proposals] Proposals retrieval failed: {e:?}"
  598. )))
  599. }
  600. };
  601. let mut proposals = Vec::with_capacity(rows.len());
  602. for row in rows {
  603. let proposal = self.parse_dao_proposal(&row).await?;
  604. proposals.push(proposal);
  605. }
  606. Ok(proposals)
  607. }
  608. /// Auxiliary function to apply `DaoFunction::Mint` call data to the wallet,
  609. /// and store its inverse query into the cache.
  610. /// Returns a flag indicating if the provided call refers to our own wallet.
  611. async fn apply_dao_mint_data(
  612. &self,
  613. new_bulla: DaoBulla,
  614. tx_hash: TransactionHash,
  615. call_index: u8,
  616. ) -> Result<bool> {
  617. let daos = self.get_daos().await?;
  618. let (mut daos_tree, proposals_tree) = self.get_dao_trees().await?;
  619. daos_tree.append(MerkleNode::from(new_bulla.inner()));
  620. for dao in &daos {
  621. if dao.bulla() == new_bulla {
  622. println!(
  623. "[apply_dao_mint_data] Found minted DAO {}, noting down for wallet update",
  624. new_bulla
  625. );
  626. // We have this DAO imported in our wallet. Add the metadata:
  627. let mut dao_to_confirm = dao.clone();
  628. dao_to_confirm.leaf_position = daos_tree.mark();
  629. dao_to_confirm.tx_hash = Some(tx_hash);
  630. dao_to_confirm.call_index = Some(call_index);
  631. // Update wallet data
  632. if let Err(e) = self.put_dao_trees(&daos_tree, &proposals_tree).await {
  633. return Err(Error::DatabaseError(format!(
  634. "[apply_dao_mint_data] Put DAO tree failed: {e:?}"
  635. )))
  636. }
  637. if let Err(e) = self.confirm_dao(&dao_to_confirm).await {
  638. return Err(Error::DatabaseError(format!(
  639. "[apply_dao_mint_data] Confirm DAO failed: {e:?}"
  640. )))
  641. }
  642. return Ok(true);
  643. }
  644. }
  645. Ok(false)
  646. }
  647. /// Auxiliary function to apply `DaoFunction::Propose` call data to the wallet,
  648. /// and store its inverse query into the cache.
  649. /// Returns a flag indicating if the provided call refers to our own wallet.
  650. async fn apply_dao_propose_data(
  651. &self,
  652. params: DaoProposeParams,
  653. tx_hash: TransactionHash,
  654. call_index: u8,
  655. ) -> Result<bool> {
  656. let daos = self.get_daos().await?;
  657. let (daos_tree, mut proposals_tree) = self.get_dao_trees().await?;
  658. proposals_tree.append(MerkleNode::from(params.proposal_bulla.inner()));
  659. // If we're able to decrypt this note, that's the way to link it
  660. // to a specific DAO.
  661. for dao in &daos {
  662. if let Ok(note) = params.note.decrypt::<DaoProposal>(&dao.params.secret_key) {
  663. // We managed to decrypt it. Let's place this in a proper ProposalRecord object
  664. println!("[apply_dao_propose_data] Managed to decrypt DAO proposal note");
  665. // We need to clone the trees here for reproducing the snapshot Merkle roots
  666. let money_tree = self.get_money_tree().await?;
  667. let nullifiers_smt = self.get_nullifiers_smt().await?;
  668. // Check if we already got the record
  669. let our_proposal =
  670. match self.get_dao_proposal_by_bulla(&params.proposal_bulla).await {
  671. Ok(p) => {
  672. let mut our_proposal = p;
  673. our_proposal.leaf_position = proposals_tree.mark();
  674. our_proposal.money_snapshot_tree = Some(money_tree);
  675. our_proposal.nullifiers_smt_snapshot = Some(nullifiers_smt);
  676. our_proposal.tx_hash = Some(tx_hash);
  677. our_proposal.call_index = Some(call_index);
  678. our_proposal
  679. }
  680. Err(_) => ProposalRecord {
  681. proposal: note,
  682. data: None,
  683. leaf_position: proposals_tree.mark(),
  684. money_snapshot_tree: Some(money_tree),
  685. nullifiers_smt_snapshot: Some(nullifiers_smt),
  686. tx_hash: Some(tx_hash),
  687. call_index: Some(call_index),
  688. exec_tx_hash: None,
  689. },
  690. };
  691. if let Err(e) = self.put_dao_trees(&daos_tree, &proposals_tree).await {
  692. return Err(Error::DatabaseError(format!(
  693. "[apply_dao_propose_data] Put DAO tree failed: {e:?}"
  694. )))
  695. }
  696. if let Err(e) = self.put_dao_proposal(&our_proposal).await {
  697. return Err(Error::DatabaseError(format!(
  698. "[apply_dao_propose_data] Put DAO proposals failed: {e:?}"
  699. )))
  700. }
  701. return Ok(true);
  702. }
  703. }
  704. Ok(false)
  705. }
  706. /// Auxiliary function to apply `DaoFunction::Vote` call data to the wallet,
  707. /// and store its inverse query into the cache.
  708. /// Returns a flag indicating if the provided call refers to our own wallet.
  709. async fn apply_dao_vote_data(
  710. &self,
  711. params: DaoVoteParams,
  712. tx_hash: TransactionHash,
  713. call_index: u8,
  714. ) -> Result<bool> {
  715. // Check if we got the corresponding proposal
  716. let Ok(proposal) = self.get_dao_proposal_by_bulla(&params.proposal_bulla).await else {
  717. return Ok(false)
  718. };
  719. // Grab the proposal DAO
  720. let dao = match self.get_dao_by_bulla(&proposal.proposal.dao_bulla).await {
  721. Ok(d) => d,
  722. Err(e) => {
  723. return Err(Error::DatabaseError(format!(
  724. "[apply_dao_vote_data] Couldn't find proposal {} DAO {}: {e}",
  725. proposal.bulla(),
  726. proposal.proposal.dao_bulla,
  727. )))
  728. }
  729. };
  730. // Decrypt the vote note
  731. let note = match params.note.decrypt_unsafe(&dao.params.secret_key) {
  732. Ok(n) => n,
  733. Err(e) => {
  734. return Err(Error::DatabaseError(format!(
  735. "[apply_dao_vote_data] Couldn't decrypt proposal {} vote with DAO {} keys: {e}",
  736. proposal.bulla(),
  737. proposal.proposal.dao_bulla,
  738. )))
  739. }
  740. };
  741. // Create the DAO vote record
  742. let vote_option = fp_to_u64(note[0]).unwrap();
  743. if vote_option > 1 {
  744. return Err(Error::DatabaseError(format!(
  745. "[apply_dao_vote_data] Malformed vote for proposal {}: {vote_option}",
  746. proposal.bulla(),
  747. )))
  748. }
  749. let vote_option = vote_option != 0;
  750. let yes_vote_blind = Blind(fp_mod_fv(note[1]));
  751. let all_vote_value = fp_to_u64(note[2]).unwrap();
  752. let all_vote_blind = Blind(fp_mod_fv(note[3]));
  753. let v = VoteRecord {
  754. id: 0, // This will be set by SQLite AUTOINCREMENT
  755. proposal: params.proposal_bulla,
  756. vote_option,
  757. yes_vote_blind,
  758. all_vote_value,
  759. all_vote_blind,
  760. tx_hash,
  761. call_index,
  762. nullifiers: params.inputs.iter().map(|i| i.vote_nullifier).collect(),
  763. };
  764. if let Err(e) = self.put_dao_vote(&v).await {
  765. return Err(Error::DatabaseError(format!(
  766. "[apply_dao_vote_data] Put DAO votes failed: {e:?}"
  767. )))
  768. }
  769. Ok(true)
  770. }
  771. /// Auxiliary function to apply `DaoFunction::Exec` call data to the wallet,
  772. /// and store its inverse query into the cache.
  773. /// Returns a flag indicating if the provided call refers to our own wallet.
  774. async fn apply_dao_exec_data(
  775. &self,
  776. params: DaoExecParams,
  777. tx_hash: TransactionHash,
  778. ) -> Result<bool> {
  779. // Check if we got the corresponding proposal
  780. if self.get_dao_proposal_by_bulla(&params.proposal_bulla).await.is_err() {
  781. return Ok(false)
  782. };
  783. // Grab proposal record key
  784. let key = serialize_async(&params.proposal_bulla).await;
  785. // Create an SQL `UPDATE` query to update proposal exec transaction hash
  786. let query = format!(
  787. "UPDATE {} SET {} = ?1 WHERE {} = ?2;",
  788. *DAO_PROPOSALS_TABLE, DAO_PROPOSALS_COL_EXEC_TX_HASH, DAO_PROPOSALS_COL_BULLA,
  789. );
  790. // Create its inverse query
  791. let inverse = match self.wallet.create_prepared_statement(
  792. &format!(
  793. "UPDATE {} SET {} = NULL WHERE {} = ?1;",
  794. *DAO_PROPOSALS_TABLE, DAO_PROPOSALS_COL_EXEC_TX_HASH, DAO_PROPOSALS_COL_BULLA,
  795. ),
  796. rusqlite::params![key],
  797. ) {
  798. Ok(q) => q,
  799. Err(e) => {
  800. return Err(Error::DatabaseError(format!(
  801. "[apply_dao_exec_data] Creating DAO proposal update inverse query failed: {e:?}"
  802. )))
  803. }
  804. };
  805. // Execute the query
  806. if let Err(e) = self
  807. .wallet
  808. .exec_sql(&query, rusqlite::params![Some(serialize_async(&tx_hash).await), key])
  809. {
  810. return Err(Error::DatabaseError(format!(
  811. "[apply_dao_exec_data] Update DAO proposal failed: {e:?}"
  812. )))
  813. }
  814. // Store its inverse
  815. if let Err(e) = self.wallet.cache_inverse(inverse) {
  816. return Err(Error::DatabaseError(format!(
  817. "[apply_dao_exec_data] Inserting inverse query into cache failed: {e:?}"
  818. )))
  819. }
  820. Ok(true)
  821. }
  822. /// Append data related to DAO contract transactions into the wallet database,
  823. /// and store their inverse queries into the cache.
  824. /// Returns a flag indicating if the provided data refer to our own wallet.
  825. pub async fn apply_tx_dao_data(
  826. &self,
  827. data: &[u8],
  828. tx_hash: TransactionHash,
  829. call_idx: u8,
  830. ) -> Result<bool> {
  831. // Run through the transaction call data and see what we got:
  832. match DaoFunction::try_from(data[0])? {
  833. DaoFunction::Mint => {
  834. println!("[apply_tx_dao_data] Found Dao::Mint call");
  835. let params: DaoMintParams = deserialize_async(&data[1..]).await?;
  836. self.apply_dao_mint_data(params.dao_bulla, tx_hash, call_idx).await
  837. }
  838. DaoFunction::Propose => {
  839. println!("[apply_tx_dao_data] Found Dao::Propose call");
  840. let params: DaoProposeParams = deserialize_async(&data[1..]).await?;
  841. self.apply_dao_propose_data(params, tx_hash, call_idx).await
  842. }
  843. DaoFunction::Vote => {
  844. println!("[apply_tx_dao_data] Found Dao::Vote call");
  845. let params: DaoVoteParams = deserialize_async(&data[1..]).await?;
  846. self.apply_dao_vote_data(params, tx_hash, call_idx).await
  847. }
  848. DaoFunction::Exec => {
  849. println!("[apply_tx_dao_data] Found Dao::Exec call");
  850. let params: DaoExecParams = deserialize_async(&data[1..]).await?;
  851. self.apply_dao_exec_data(params, tx_hash).await
  852. }
  853. DaoFunction::AuthMoneyTransfer => {
  854. println!("[apply_tx_dao_data] Found Dao::AuthMoneyTransfer call");
  855. // Does nothing, just verifies the other calls are correct
  856. Ok(false)
  857. }
  858. }
  859. }
  860. /// Confirm already imported DAO metadata into the wallet,
  861. /// and store its inverse query into the cache.
  862. /// Here we just write the leaf position, tx hash, and call index.
  863. /// Panics if the fields are None.
  864. pub async fn confirm_dao(&self, dao: &DaoRecord) -> WalletDbResult<()> {
  865. // Grab dao record key
  866. let key = serialize_async(&dao.bulla()).await;
  867. // Create an SQL `UPDATE` query
  868. let query = format!(
  869. "UPDATE {} SET {} = ?1, {} = ?2, {} = ?3 WHERE {} = ?4;",
  870. *DAO_DAOS_TABLE,
  871. DAO_DAOS_COL_LEAF_POSITION,
  872. DAO_DAOS_COL_TX_HASH,
  873. DAO_DAOS_COL_CALL_INDEX,
  874. DAO_DAOS_COL_BULLA
  875. );
  876. // Create its params
  877. let params = rusqlite::params![
  878. serialize_async(&dao.leaf_position.unwrap()).await,
  879. serialize_async(&dao.tx_hash.unwrap()).await,
  880. dao.call_index.unwrap(),
  881. key,
  882. ];
  883. // Create its inverse query
  884. let inverse_query = format!(
  885. "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL WHERE {} = ?1;",
  886. *DAO_DAOS_TABLE,
  887. DAO_DAOS_COL_LEAF_POSITION,
  888. DAO_DAOS_COL_TX_HASH,
  889. DAO_DAOS_COL_CALL_INDEX,
  890. DAO_DAOS_COL_BULLA
  891. );
  892. let inverse =
  893. self.wallet.create_prepared_statement(&inverse_query, rusqlite::params![key])?;
  894. // Execute the query
  895. self.wallet.exec_sql(&query, params)?;
  896. // Store its inverse
  897. self.wallet.cache_inverse(inverse)
  898. }
  899. /// Import given DAO proposal into the wallet,
  900. /// and store its inverse query into the cache.
  901. pub async fn put_dao_proposal(&self, proposal: &ProposalRecord) -> Result<()> {
  902. // Check that we already have the proposal DAO
  903. if let Err(e) = self.get_dao_by_bulla(&proposal.proposal.dao_bulla).await {
  904. return Err(Error::DatabaseError(format!(
  905. "[put_dao_proposal] Couldn't find proposal {} DAO {}: {e}",
  906. proposal.bulla(),
  907. proposal.proposal.dao_bulla
  908. )))
  909. }
  910. // Grab proposal record key
  911. let key = serialize_async(&proposal.bulla()).await;
  912. // Create an SQL `INSERT OR REPLACE` query
  913. let query = format!(
  914. "INSERT OR REPLACE INTO {} ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10);",
  915. *DAO_PROPOSALS_TABLE,
  916. DAO_PROPOSALS_COL_BULLA,
  917. DAO_PROPOSALS_COL_DAO_BULLA,
  918. DAO_PROPOSALS_COL_PROPOSAL,
  919. DAO_PROPOSALS_COL_DATA,
  920. DAO_PROPOSALS_COL_LEAF_POSITION,
  921. DAO_PROPOSALS_COL_MONEY_SNAPSHOT_TREE,
  922. DAO_PROPOSALS_COL_NULLIFIERS_SMT_SNAPSHOT,
  923. DAO_PROPOSALS_COL_TX_HASH,
  924. DAO_PROPOSALS_COL_CALL_INDEX,
  925. DAO_PROPOSALS_COL_EXEC_TX_HASH,
  926. );
  927. // Create its params
  928. let data = match &proposal.data {
  929. Some(data) => Some(data),
  930. None => None,
  931. };
  932. let leaf_position = match &proposal.leaf_position {
  933. Some(leaf_position) => Some(serialize_async(leaf_position).await),
  934. None => None,
  935. };
  936. let money_snapshot_tree = match &proposal.money_snapshot_tree {
  937. Some(money_snapshot_tree) => Some(serialize_async(money_snapshot_tree).await),
  938. None => None,
  939. };
  940. let nullifiers_smt_snapshot = match &proposal.nullifiers_smt_snapshot {
  941. Some(nullifiers_smt_snapshot) => Some(serialize_async(nullifiers_smt_snapshot).await),
  942. None => None,
  943. };
  944. let tx_hash = match &proposal.tx_hash {
  945. Some(tx_hash) => Some(serialize_async(tx_hash).await),
  946. None => None,
  947. };
  948. let exec_tx_hash = match &proposal.exec_tx_hash {
  949. Some(exec_tx_hash) => Some(serialize_async(exec_tx_hash).await),
  950. None => None,
  951. };
  952. let params = rusqlite::params![
  953. key,
  954. serialize_async(&proposal.proposal.dao_bulla).await,
  955. serialize_async(&proposal.proposal).await,
  956. data,
  957. leaf_position,
  958. money_snapshot_tree,
  959. nullifiers_smt_snapshot,
  960. tx_hash,
  961. proposal.call_index,
  962. exec_tx_hash,
  963. ];
  964. // Create its inverse query
  965. let inverse_query = format!(
  966. "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL WHERE {} = ?1;",
  967. *DAO_PROPOSALS_TABLE,
  968. DAO_PROPOSALS_COL_LEAF_POSITION,
  969. DAO_PROPOSALS_COL_MONEY_SNAPSHOT_TREE,
  970. DAO_PROPOSALS_COL_NULLIFIERS_SMT_SNAPSHOT,
  971. DAO_PROPOSALS_COL_TX_HASH,
  972. DAO_PROPOSALS_COL_CALL_INDEX,
  973. DAO_PROPOSALS_COL_EXEC_TX_HASH,
  974. DAO_PROPOSALS_COL_BULLA
  975. );
  976. let inverse =
  977. match self.wallet.create_prepared_statement(&inverse_query, rusqlite::params![key]) {
  978. Ok(q) => q,
  979. Err(e) => {
  980. return Err(Error::DatabaseError(format!(
  981. "[put_dao_proposal] Creating DAO proposal insert inverse query failed: {e:?}"
  982. )))
  983. }
  984. };
  985. // Execute the query
  986. if let Err(e) = self.wallet.exec_sql(&query, params) {
  987. return Err(Error::DatabaseError(format!(
  988. "[put_dao_proposal] Proposal insert failed: {e:?}"
  989. )))
  990. };
  991. // Store its inverse
  992. if let Err(e) = self.wallet.cache_inverse(inverse) {
  993. return Err(Error::DatabaseError(format!(
  994. "[put_dao_proposal] Inserting inverse query into cache failed: {e:?}"
  995. )))
  996. }
  997. Ok(())
  998. }
  999. /// Unconfirm imported DAO proposals by removing the leaf position, tx hash, and call index.
  1000. pub async fn unconfirm_proposals(&self, proposals: &[ProposalRecord]) -> WalletDbResult<()> {
  1001. for proposal in proposals {
  1002. let query = format!(
  1003. "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL WHERE {} = ?1;",
  1004. *DAO_PROPOSALS_TABLE,
  1005. DAO_PROPOSALS_COL_LEAF_POSITION,
  1006. DAO_PROPOSALS_COL_MONEY_SNAPSHOT_TREE,
  1007. DAO_PROPOSALS_COL_NULLIFIERS_SMT_SNAPSHOT,
  1008. DAO_PROPOSALS_COL_TX_HASH,
  1009. DAO_PROPOSALS_COL_CALL_INDEX,
  1010. DAO_PROPOSALS_COL_EXEC_TX_HASH,
  1011. DAO_PROPOSALS_COL_BULLA
  1012. );
  1013. self.wallet
  1014. .exec_sql(&query, rusqlite::params![serialize_async(&proposal.bulla()).await])?;
  1015. }
  1016. Ok(())
  1017. }
  1018. /// Import given DAO vote into the wallet,
  1019. /// and store its inverse query into the cache.
  1020. pub async fn put_dao_vote(&self, vote: &VoteRecord) -> WalletDbResult<()> {
  1021. println!("Importing DAO vote into wallet");
  1022. // Create an SQL `INSERT OR REPLACE` query
  1023. let query = format!(
  1024. "INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8);",
  1025. *DAO_VOTES_TABLE,
  1026. DAO_VOTES_COL_PROPOSAL_BULLA,
  1027. DAO_VOTES_COL_VOTE_OPTION,
  1028. DAO_VOTES_COL_YES_VOTE_BLIND,
  1029. DAO_VOTES_COL_ALL_VOTE_VALUE,
  1030. DAO_VOTES_COL_ALL_VOTE_BLIND,
  1031. DAO_VOTES_COL_TX_HASH,
  1032. DAO_VOTES_COL_CALL_INDEX,
  1033. DAO_VOTES_COL_NULLIFIERS,
  1034. );
  1035. // Create its params
  1036. let params = rusqlite::params![
  1037. serialize_async(&vote.proposal).await,
  1038. vote.vote_option as u64,
  1039. serialize_async(&vote.yes_vote_blind).await,
  1040. serialize_async(&vote.all_vote_value).await,
  1041. serialize_async(&vote.all_vote_blind).await,
  1042. serialize_async(&vote.tx_hash).await,
  1043. vote.call_index,
  1044. serialize_async(&vote.nullifiers).await,
  1045. ];
  1046. // Create its inverse query.
  1047. // Since we don't know the record ID we will remove it
  1048. // using all its fields.
  1049. let inverse_query = format!(
  1050. "DELETE FROM {} WHERE {} = ?1 AND {} = ?2 AND {} = ?3 AND {} = ?4 AND {} = ?5 AND {} = ?6 AND {} = ?7 AND {} = ?8;",
  1051. *DAO_VOTES_TABLE,
  1052. DAO_VOTES_COL_PROPOSAL_BULLA,
  1053. DAO_VOTES_COL_VOTE_OPTION,
  1054. DAO_VOTES_COL_YES_VOTE_BLIND,
  1055. DAO_VOTES_COL_ALL_VOTE_VALUE,
  1056. DAO_VOTES_COL_ALL_VOTE_BLIND,
  1057. DAO_VOTES_COL_TX_HASH,
  1058. DAO_VOTES_COL_CALL_INDEX,
  1059. DAO_VOTES_COL_NULLIFIERS,
  1060. );
  1061. let inverse = self.wallet.create_prepared_statement(&inverse_query, params)?;
  1062. // Execute the query
  1063. self.wallet.exec_sql(&query, params)?;
  1064. // Store its inverse
  1065. self.wallet.cache_inverse(inverse)?;
  1066. println!("DAO vote added to wallet");
  1067. Ok(())
  1068. }
  1069. /// Reset the DAO Merkle trees in the wallet.
  1070. pub async fn reset_dao_trees(&self) -> WalletDbResult<()> {
  1071. println!("Resetting DAO Merkle trees");
  1072. let tree = MerkleTree::new(1);
  1073. self.put_dao_trees(&tree, &tree).await?;
  1074. println!("Successfully reset DAO Merkle trees");
  1075. Ok(())
  1076. }
  1077. /// Reset confirmed DAOs in the wallet.
  1078. pub async fn reset_daos(&self) -> WalletDbResult<()> {
  1079. println!("Resetting DAO confirmations");
  1080. let query = format!(
  1081. "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL;",
  1082. *DAO_DAOS_TABLE,
  1083. DAO_DAOS_COL_LEAF_POSITION,
  1084. DAO_DAOS_COL_TX_HASH,
  1085. DAO_DAOS_COL_CALL_INDEX,
  1086. );
  1087. self.wallet.exec_sql(&query, &[])?;
  1088. println!("Successfully unconfirmed DAOs");
  1089. Ok(())
  1090. }
  1091. /// Reset all DAO proposals in the wallet.
  1092. pub async fn reset_dao_proposals(&self) -> WalletDbResult<()> {
  1093. println!("Resetting DAO proposals confirmations");
  1094. let proposals = match self.get_proposals().await {
  1095. Ok(p) => p,
  1096. Err(e) => {
  1097. println!("[reset_dao_proposals] DAO proposals retrieval failed: {e:?}");
  1098. return Err(WalletDbError::GenericError);
  1099. }
  1100. };
  1101. self.unconfirm_proposals(&proposals).await?;
  1102. println!("Successfully unconfirmed DAO proposals");
  1103. Ok(())
  1104. }
  1105. /// Reset all DAO votes in the wallet.
  1106. pub fn reset_dao_votes(&self) -> WalletDbResult<()> {
  1107. println!("Resetting DAO votes");
  1108. let query = format!("DELETE FROM {};", *DAO_VOTES_TABLE);
  1109. self.wallet.exec_sql(&query, &[])
  1110. }
  1111. /// Import given DAO params into the wallet with a given name.
  1112. pub async fn import_dao(&self, name: &str, params: DaoParams) -> Result<()> {
  1113. // First let's check if we've imported this DAO with the given name before.
  1114. if self.get_dao_by_name(name).await.is_ok() {
  1115. return Err(Error::DatabaseError(
  1116. "[import_dao] This DAO has already been imported".to_string(),
  1117. ))
  1118. }
  1119. println!("Importing \"{name}\" DAO into the wallet");
  1120. let query = format!(
  1121. "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
  1122. *DAO_DAOS_TABLE, DAO_DAOS_COL_BULLA, DAO_DAOS_COL_NAME, DAO_DAOS_COL_PARAMS,
  1123. );
  1124. if let Err(e) = self.wallet.exec_sql(
  1125. &query,
  1126. rusqlite::params![
  1127. serialize_async(&params.dao.to_bulla()).await,
  1128. name,
  1129. serialize_async(&params).await,
  1130. ],
  1131. ) {
  1132. return Err(Error::DatabaseError(format!("[import_dao] DAO insert failed: {e:?}")))
  1133. };
  1134. Ok(())
  1135. }
  1136. /// Fetch a DAO given its bulla.
  1137. pub async fn get_dao_by_bulla(&self, bulla: &DaoBulla) -> Result<DaoRecord> {
  1138. let row = match self.wallet.query_single(
  1139. &DAO_DAOS_TABLE,
  1140. &[],
  1141. convert_named_params! {(DAO_DAOS_COL_BULLA, serialize_async(bulla).await)},
  1142. ) {
  1143. Ok(r) => r,
  1144. Err(e) => {
  1145. return Err(Error::DatabaseError(format!(
  1146. "[get_dao_by_bulla] DAO retrieval failed: {e:?}"
  1147. )))
  1148. }
  1149. };
  1150. self.parse_dao_record(&row).await
  1151. }
  1152. /// Fetch a DAO given its name.
  1153. pub async fn get_dao_by_name(&self, name: &str) -> Result<DaoRecord> {
  1154. let row = match self.wallet.query_single(
  1155. &DAO_DAOS_TABLE,
  1156. &[],
  1157. convert_named_params! {(DAO_DAOS_COL_NAME, name)},
  1158. ) {
  1159. Ok(r) => r,
  1160. Err(e) => {
  1161. return Err(Error::DatabaseError(format!(
  1162. "[get_dao_by_name] DAO retrieval failed: {e:?}"
  1163. )))
  1164. }
  1165. };
  1166. self.parse_dao_record(&row).await
  1167. }
  1168. /// List DAO(s) imported in the wallet. If a name is given, just print the
  1169. /// metadata for that specific one, if found.
  1170. pub async fn dao_list(&self, name: &Option<String>) -> Result<()> {
  1171. if let Some(name) = name {
  1172. let dao = self.get_dao_by_name(name).await?;
  1173. println!("{dao}");
  1174. return Ok(());
  1175. }
  1176. let daos = self.get_daos().await?;
  1177. for (i, dao) in daos.iter().enumerate() {
  1178. println!("{i}. {}", dao.name);
  1179. }
  1180. Ok(())
  1181. }
  1182. /// Fetch known unspent balances from the wallet for the given DAO name.
  1183. pub async fn dao_balance(&self, name: &str) -> Result<HashMap<String, u64>> {
  1184. let dao = self.get_dao_by_name(name).await?;
  1185. let dao_spend_hook =
  1186. FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
  1187. .to_func_id();
  1188. let mut coins = self.get_coins(false).await?;
  1189. coins.retain(|x| x.0.note.spend_hook == dao_spend_hook);
  1190. coins.retain(|x| x.0.note.user_data == dao.bulla().inner());
  1191. // Fill this map with balances
  1192. let mut balmap: HashMap<String, u64> = HashMap::new();
  1193. for coin in coins {
  1194. let mut value = coin.0.note.value;
  1195. if let Some(prev) = balmap.get(&coin.0.note.token_id.to_string()) {
  1196. value += prev;
  1197. }
  1198. balmap.insert(coin.0.note.token_id.to_string(), value);
  1199. }
  1200. Ok(balmap)
  1201. }
  1202. /// Fetch all known DAO proposalss from the wallet.
  1203. pub async fn get_proposals(&self) -> Result<Vec<ProposalRecord>> {
  1204. let rows = match self.wallet.query_multiple(&DAO_PROPOSALS_TABLE, &[], &[]) {
  1205. Ok(r) => r,
  1206. Err(e) => {
  1207. return Err(Error::DatabaseError(format!(
  1208. "[get_proposals] DAO proposalss retrieval failed: {e:?}"
  1209. )))
  1210. }
  1211. };
  1212. let mut daos = Vec::with_capacity(rows.len());
  1213. for row in rows {
  1214. daos.push(self.parse_dao_proposal(&row).await?);
  1215. }
  1216. Ok(daos)
  1217. }
  1218. /// Fetch a DAO proposal by its bulla.
  1219. pub async fn get_dao_proposal_by_bulla(
  1220. &self,
  1221. bulla: &DaoProposalBulla,
  1222. ) -> Result<ProposalRecord> {
  1223. // Grab the proposal record
  1224. let row = match self.wallet.query_single(
  1225. &DAO_PROPOSALS_TABLE,
  1226. &[],
  1227. convert_named_params! {(DAO_PROPOSALS_COL_BULLA, serialize_async(bulla).await)},
  1228. ) {
  1229. Ok(r) => r,
  1230. Err(e) => {
  1231. return Err(Error::DatabaseError(format!(
  1232. "[get_dao_proposal_by_bulla] DAO proposal retrieval failed: {e:?}"
  1233. )))
  1234. }
  1235. };
  1236. // Parse rest of the record
  1237. self.parse_dao_proposal(&row).await
  1238. }
  1239. // Fetch all known DAO proposal votes from the wallet given a proposal ID.
  1240. pub async fn get_dao_proposal_votes(
  1241. &self,
  1242. proposal: &DaoProposalBulla,
  1243. ) -> Result<Vec<VoteRecord>> {
  1244. let rows = match self.wallet.query_multiple(
  1245. &DAO_VOTES_TABLE,
  1246. &[],
  1247. convert_named_params! {(DAO_VOTES_COL_PROPOSAL_BULLA, serialize_async(proposal).await)},
  1248. ) {
  1249. Ok(r) => r,
  1250. Err(e) => {
  1251. return Err(Error::DatabaseError(format!(
  1252. "[get_dao_proposal_votes] Votes retrieval failed: {e:?}"
  1253. )))
  1254. }
  1255. };
  1256. let mut votes = Vec::with_capacity(rows.len());
  1257. for row in rows {
  1258. let Value::Integer(id) = row[0] else {
  1259. return Err(Error::ParseFailed("[get_dao_proposal_votes] ID parsing failed"))
  1260. };
  1261. let Ok(id) = u64::try_from(id) else {
  1262. return Err(Error::ParseFailed("[get_dao_proposal_votes] ID parsing failed"))
  1263. };
  1264. let Value::Blob(ref proposal_bytes) = row[1] else {
  1265. return Err(Error::ParseFailed(
  1266. "[get_dao_proposal_votes] Proposal bytes bytes parsing failed",
  1267. ))
  1268. };
  1269. let proposal = deserialize_async(proposal_bytes).await?;
  1270. let Value::Integer(vote_option) = row[2] else {
  1271. return Err(Error::ParseFailed(
  1272. "[get_dao_proposal_votes] Vote option parsing failed",
  1273. ))
  1274. };
  1275. let Ok(vote_option) = u32::try_from(vote_option) else {
  1276. return Err(Error::ParseFailed(
  1277. "[get_dao_proposal_votes] Vote option parsing failed",
  1278. ))
  1279. };
  1280. let vote_option = vote_option != 0;
  1281. let Value::Blob(ref yes_vote_blind_bytes) = row[3] else {
  1282. return Err(Error::ParseFailed(
  1283. "[get_dao_proposal_votes] Yes vote blind bytes parsing failed",
  1284. ))
  1285. };
  1286. let yes_vote_blind = deserialize_async(yes_vote_blind_bytes).await?;
  1287. let Value::Blob(ref all_vote_value_bytes) = row[4] else {
  1288. return Err(Error::ParseFailed(
  1289. "[get_dao_proposal_votes] All vote value bytes parsing failed",
  1290. ))
  1291. };
  1292. let all_vote_value = deserialize_async(all_vote_value_bytes).await?;
  1293. let Value::Blob(ref all_vote_blind_bytes) = row[5] else {
  1294. return Err(Error::ParseFailed(
  1295. "[get_dao_proposal_votes] All vote blind bytes parsing failed",
  1296. ))
  1297. };
  1298. let all_vote_blind = deserialize_async(all_vote_blind_bytes).await?;
  1299. let Value::Blob(ref tx_hash_bytes) = row[6] else {
  1300. return Err(Error::ParseFailed(
  1301. "[get_dao_proposal_votes] Transaction hash bytes parsing failed",
  1302. ))
  1303. };
  1304. let tx_hash = deserialize_async(tx_hash_bytes).await?;
  1305. let Value::Integer(call_index) = row[7] else {
  1306. return Err(Error::ParseFailed("[get_dao_proposal_votes] Call index parsing failed"))
  1307. };
  1308. let Ok(call_index) = u8::try_from(call_index) else {
  1309. return Err(Error::ParseFailed("[get_dao_proposal_votes] Call index parsing failed"))
  1310. };
  1311. let Value::Blob(ref nullifiers_bytes) = row[8] else {
  1312. return Err(Error::ParseFailed(
  1313. "[get_dao_proposal_votes] Nullifiers bytes parsing failed",
  1314. ))
  1315. };
  1316. let nullifiers = deserialize_async(nullifiers_bytes).await?;
  1317. let vote = VoteRecord {
  1318. id,
  1319. proposal,
  1320. vote_option,
  1321. yes_vote_blind,
  1322. all_vote_value,
  1323. all_vote_blind,
  1324. tx_hash,
  1325. call_index,
  1326. nullifiers,
  1327. };
  1328. votes.push(vote);
  1329. }
  1330. Ok(votes)
  1331. }
  1332. /// Mint a DAO on-chain.
  1333. pub async fn dao_mint(&self, name: &str) -> Result<Transaction> {
  1334. // Retrieve the dao record
  1335. let dao = self.get_dao_by_name(name).await?;
  1336. // Check its not already minted
  1337. if dao.tx_hash.is_some() {
  1338. return Err(Error::Custom(
  1339. "[dao_mint] This DAO seems to have already been minted on-chain".to_string(),
  1340. ))
  1341. }
  1342. // Now we need to do a lookup for the zkas proof bincodes, and create
  1343. // the circuit objects and proving keys so we can build the transaction.
  1344. // We also do this through the RPC. First we grab the fee call from money.
  1345. let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
  1346. let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
  1347. else {
  1348. return Err(Error::Custom("Fee circuit not found".to_string()))
  1349. };
  1350. let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
  1351. let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
  1352. // Creating Fee circuit proving key
  1353. let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
  1354. // Now we grab the DAO mint
  1355. let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
  1356. let Some(dao_mint_zkbin) = zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_MINT_NS)
  1357. else {
  1358. return Err(Error::DatabaseError("[dao_mint] DAO Mint circuit not found".to_string()))
  1359. };
  1360. let dao_mint_zkbin = ZkBinary::decode(&dao_mint_zkbin.1)?;
  1361. let dao_mint_circuit = ZkCircuit::new(empty_witnesses(&dao_mint_zkbin)?, &dao_mint_zkbin);
  1362. // Creating DAO Mint circuit proving key
  1363. let dao_mint_pk = ProvingKey::build(dao_mint_zkbin.k, &dao_mint_circuit);
  1364. // Create the DAO mint call
  1365. // TODO: use diff keys
  1366. let (params, proofs) = make_mint_call(
  1367. &dao.params.dao,
  1368. &dao.params.secret_key,
  1369. &dao.params.secret_key,
  1370. &dao.params.secret_key,
  1371. &dao.params.secret_key,
  1372. &dao.params.secret_key,
  1373. &dao.params.secret_key,
  1374. &dao_mint_zkbin,
  1375. &dao_mint_pk,
  1376. )?;
  1377. let mut data = vec![DaoFunction::Mint as u8];
  1378. params.encode_async(&mut data).await?;
  1379. let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
  1380. // Create the TransactionBuilder containing above call
  1381. let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
  1382. // We first have to execute the fee-less tx to gather its used gas, and then we feed
  1383. // it into the fee-creating function.
  1384. let mut tx = tx_builder.build()?;
  1385. let sigs = tx.create_sigs(&[dao.params.secret_key])?;
  1386. tx.signatures.push(sigs);
  1387. let tree = self.get_money_tree().await?;
  1388. let (fee_call, fee_proofs, fee_secrets) =
  1389. self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
  1390. // Append the fee call to the transaction
  1391. tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
  1392. // Now build the actual transaction and sign it with all necessary keys.
  1393. let mut tx = tx_builder.build()?;
  1394. let sigs = tx.create_sigs(&[dao.params.secret_key])?;
  1395. tx.signatures.push(sigs);
  1396. let sigs = tx.create_sigs(&fee_secrets)?;
  1397. tx.signatures.push(sigs);
  1398. Ok(tx)
  1399. }
  1400. /// Create a DAO transfer proposal.
  1401. #[allow(clippy::too_many_arguments)]
  1402. pub async fn dao_propose_transfer(
  1403. &self,
  1404. name: &str,
  1405. duration_blockwindows: u64,
  1406. amount: &str,
  1407. token_id: TokenId,
  1408. recipient: PublicKey,
  1409. spend_hook: Option<FuncId>,
  1410. user_data: Option<pallas::Base>,
  1411. ) -> Result<ProposalRecord> {
  1412. // Fetch DAO and check its deployed
  1413. let dao = self.get_dao_by_name(name).await?;
  1414. if dao.leaf_position.is_none() || dao.tx_hash.is_none() || dao.call_index.is_none() {
  1415. return Err(Error::Custom(
  1416. "[dao_propose_transfer] DAO seems to not have been deployed yet".to_string(),
  1417. ))
  1418. }
  1419. // Fetch DAO unspent OwnCoins to see what its balance is
  1420. let dao_spend_hook =
  1421. FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
  1422. .to_func_id();
  1423. let dao_bulla = dao.bulla();
  1424. let dao_owncoins =
  1425. self.get_contract_token_coins(&token_id, &dao_spend_hook, &dao_bulla.inner()).await?;
  1426. if dao_owncoins.is_empty() {
  1427. return Err(Error::Custom(format!(
  1428. "[dao_propose_transfer] Did not find any {token_id} unspent coins owned by this DAO"
  1429. )))
  1430. }
  1431. // Check DAO balance is sufficient
  1432. let amount = decode_base10(amount, BALANCE_BASE10_DECIMALS, false)?;
  1433. if dao_owncoins.iter().map(|x| x.note.value).sum::<u64>() < amount {
  1434. return Err(Error::Custom(format!(
  1435. "[dao_propose_transfer] Not enough DAO balance for token ID: {token_id}",
  1436. )))
  1437. }
  1438. // Generate proposal coin attributes
  1439. let proposal_coinattrs = CoinAttributes {
  1440. public_key: recipient,
  1441. value: amount,
  1442. token_id,
  1443. spend_hook: spend_hook.unwrap_or(FuncId::none()),
  1444. user_data: user_data.unwrap_or(pallas::Base::ZERO),
  1445. blind: Blind::random(&mut OsRng),
  1446. };
  1447. // Convert coin_params to actual coins
  1448. let proposal_coins = vec![proposal_coinattrs.to_coin()];
  1449. let mut proposal_data = vec![];
  1450. proposal_coins.encode_async(&mut proposal_data).await?;
  1451. // Create Auth calls
  1452. let auth_calls = vec![
  1453. DaoAuthCall {
  1454. contract_id: *DAO_CONTRACT_ID,
  1455. function_code: DaoFunction::AuthMoneyTransfer as u8,
  1456. auth_data: proposal_data,
  1457. },
  1458. DaoAuthCall {
  1459. contract_id: *MONEY_CONTRACT_ID,
  1460. function_code: MoneyFunction::TransferV1 as u8,
  1461. auth_data: vec![],
  1462. },
  1463. ];
  1464. // Retrieve next block height and current block time target,
  1465. // to compute their window.
  1466. let next_block_height = self.get_next_block_height().await?;
  1467. let block_target = self.get_block_target().await?;
  1468. let creation_blockwindow = blockwindow(next_block_height, block_target);
  1469. // Create the actual proposal
  1470. let proposal = DaoProposal {
  1471. auth_calls,
  1472. creation_blockwindow,
  1473. duration_blockwindows,
  1474. user_data: user_data.unwrap_or(pallas::Base::ZERO),
  1475. dao_bulla,
  1476. blind: Blind::random(&mut OsRng),
  1477. };
  1478. let proposal_record = ProposalRecord {
  1479. proposal,
  1480. data: Some(serialize_async(&proposal_coinattrs).await),
  1481. leaf_position: None,
  1482. money_snapshot_tree: None,
  1483. nullifiers_smt_snapshot: None,
  1484. tx_hash: None,
  1485. call_index: None,
  1486. exec_tx_hash: None,
  1487. };
  1488. if let Err(e) = self.put_dao_proposal(&proposal_record).await {
  1489. return Err(Error::DatabaseError(format!(
  1490. "[dao_propose_transfer] Put DAO proposal failed: {e:?}"
  1491. )))
  1492. }
  1493. Ok(proposal_record)
  1494. }
  1495. /// Create a DAO transfer proposal transaction.
  1496. pub async fn dao_transfer_proposal_tx(&self, proposal: &ProposalRecord) -> Result<Transaction> {
  1497. // Check we know the plaintext data
  1498. if proposal.data.is_none() {
  1499. return Err(Error::Custom(
  1500. "[dao_transfer_proposal_tx] Proposal plainext data is empty".to_string(),
  1501. ))
  1502. }
  1503. let proposal_coinattrs: CoinAttributes =
  1504. deserialize_async(proposal.data.as_ref().unwrap()).await?;
  1505. // Fetch DAO and check its deployed
  1506. let Ok(dao) = self.get_dao_by_bulla(&proposal.proposal.dao_bulla).await else {
  1507. return Err(Error::Custom(format!(
  1508. "[dao_transfer_proposal_tx] DAO {} was not found",
  1509. proposal.proposal.dao_bulla
  1510. )))
  1511. };
  1512. if dao.leaf_position.is_none() || dao.tx_hash.is_none() || dao.call_index.is_none() {
  1513. return Err(Error::Custom(
  1514. "[dao_transfer_proposal_tx] DAO seems to not have been deployed yet".to_string(),
  1515. ))
  1516. }
  1517. // Fetch DAO unspent OwnCoins to see what its balance is for the coin
  1518. let dao_spend_hook =
  1519. FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
  1520. .to_func_id();
  1521. let dao_owncoins = self
  1522. .get_contract_token_coins(
  1523. &proposal_coinattrs.token_id,
  1524. &dao_spend_hook,
  1525. &proposal.proposal.dao_bulla.inner(),
  1526. )
  1527. .await?;
  1528. if dao_owncoins.is_empty() {
  1529. return Err(Error::Custom(format!(
  1530. "[dao_transfer_proposal_tx] Did not find any {} unspent coins owned by this DAO",
  1531. proposal_coinattrs.token_id,
  1532. )))
  1533. }
  1534. // Check DAO balance is sufficient
  1535. if dao_owncoins.iter().map(|x| x.note.value).sum::<u64>() < proposal_coinattrs.value {
  1536. return Err(Error::Custom(format!(
  1537. "[dao_transfer_proposal_tx] Not enough DAO balance for token ID: {}",
  1538. proposal_coinattrs.token_id,
  1539. )))
  1540. }
  1541. // Fetch our own governance OwnCoins to see what our balance is
  1542. let gov_owncoins = self.get_token_coins(&dao.params.dao.gov_token_id).await?;
  1543. if gov_owncoins.is_empty() {
  1544. return Err(Error::Custom(format!(
  1545. "[dao_transfer_proposal_tx] Did not find any governance {} coins in wallet",
  1546. dao.params.dao.gov_token_id
  1547. )))
  1548. }
  1549. // Find which governance coins we can use
  1550. let mut total_value = 0;
  1551. let mut gov_owncoins_to_use = vec![];
  1552. for gov_owncoin in gov_owncoins {
  1553. if total_value >= dao.params.dao.proposer_limit {
  1554. break
  1555. }
  1556. total_value += gov_owncoin.note.value;
  1557. gov_owncoins_to_use.push(gov_owncoin);
  1558. }
  1559. // Check our governance coins balance is sufficient
  1560. if total_value < dao.params.dao.proposer_limit {
  1561. return Err(Error::Custom(format!(
  1562. "[dao_transfer_proposal_tx] Not enough gov token {} balance to propose",
  1563. dao.params.dao.gov_token_id
  1564. )))
  1565. }
  1566. // Now we need to do a lookup for the zkas proof bincodes, and create
  1567. // the circuit objects and proving keys so we can build the transaction.
  1568. // We also do this through the RPC. First we grab the fee call from money.
  1569. let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
  1570. let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
  1571. else {
  1572. return Err(Error::Custom(
  1573. "[dao_transfer_proposal_tx] Fee circuit not found".to_string(),
  1574. ))
  1575. };
  1576. let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
  1577. let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
  1578. // Creating Fee circuit proving key
  1579. let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
  1580. // Now we grab the DAO bins
  1581. let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
  1582. let Some(propose_burn_zkbin) =
  1583. zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS)
  1584. else {
  1585. return Err(Error::Custom(
  1586. "[dao_transfer_proposal_tx] Propose Burn circuit not found".to_string(),
  1587. ))
  1588. };
  1589. let Some(propose_main_zkbin) =
  1590. zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS)
  1591. else {
  1592. return Err(Error::Custom(
  1593. "[dao_transfer_proposal_tx] Propose Main circuit not found".to_string(),
  1594. ))
  1595. };
  1596. let propose_burn_zkbin = ZkBinary::decode(&propose_burn_zkbin.1)?;
  1597. let propose_main_zkbin = ZkBinary::decode(&propose_main_zkbin.1)?;
  1598. let propose_burn_circuit =
  1599. ZkCircuit::new(empty_witnesses(&propose_burn_zkbin)?, &propose_burn_zkbin);
  1600. let propose_main_circuit =
  1601. ZkCircuit::new(empty_witnesses(&propose_main_zkbin)?, &propose_main_zkbin);
  1602. // Creating DAO ProposeBurn and ProposeMain circuits proving keys
  1603. let propose_burn_pk = ProvingKey::build(propose_burn_zkbin.k, &propose_burn_circuit);
  1604. let propose_main_pk = ProvingKey::build(propose_main_zkbin.k, &propose_main_circuit);
  1605. // Fetch our money Merkle tree
  1606. let money_merkle_tree = self.get_money_tree().await?;
  1607. // Now we can create the proposal transaction parameters.
  1608. // We first generate the `DaoProposeStakeInput` inputs,
  1609. // using our governance OwnCoins.
  1610. let mut inputs = Vec::with_capacity(gov_owncoins_to_use.len());
  1611. for gov_owncoin in gov_owncoins_to_use {
  1612. let input = DaoProposeStakeInput {
  1613. secret: gov_owncoin.secret,
  1614. note: gov_owncoin.note.clone(),
  1615. leaf_position: gov_owncoin.leaf_position,
  1616. merkle_path: money_merkle_tree.witness(gov_owncoin.leaf_position, 0).unwrap(),
  1617. };
  1618. inputs.push(input);
  1619. }
  1620. // Now create the parameters for the proposal tx
  1621. let signature_secret = SecretKey::random(&mut OsRng);
  1622. // Fetch the daos Merkle tree to compute the DAO Merkle path and root
  1623. let (daos_tree, _) = self.get_dao_trees().await?;
  1624. let (dao_merkle_path, dao_merkle_root) = {
  1625. let root = daos_tree.root(0).unwrap();
  1626. let leaf_pos = dao.leaf_position.unwrap();
  1627. let dao_merkle_path = daos_tree.witness(leaf_pos, 0).unwrap();
  1628. (dao_merkle_path, root)
  1629. };
  1630. // Generate the Money nullifiers Sparse Merkle Tree
  1631. let store = WalletStorage::new(
  1632. &self.wallet,
  1633. &MONEY_SMT_TABLE,
  1634. MONEY_SMT_COL_KEY,
  1635. MONEY_SMT_COL_VALUE,
  1636. );
  1637. let money_null_smt = WalletSmt::new(store, PoseidonFp::new(), &EMPTY_NODES_FP);
  1638. // Create the proposal call
  1639. let call = DaoProposeCall {
  1640. money_null_smt: &money_null_smt,
  1641. inputs,
  1642. proposal: proposal.proposal.clone(),
  1643. dao: dao.params.dao,
  1644. dao_leaf_position: dao.leaf_position.unwrap(),
  1645. dao_merkle_path,
  1646. dao_merkle_root,
  1647. signature_secret,
  1648. };
  1649. // TODO: use the proper key if we have it
  1650. let (params, proofs) = call.make(
  1651. &dao.params.secret_key,
  1652. &propose_burn_zkbin,
  1653. &propose_burn_pk,
  1654. &propose_main_zkbin,
  1655. &propose_main_pk,
  1656. )?;
  1657. // Encode the call
  1658. let mut data = vec![DaoFunction::Propose as u8];
  1659. params.encode_async(&mut data).await?;
  1660. let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
  1661. // Create the TransactionBuilder containing above call
  1662. let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
  1663. // We first have to execute the fee-less tx to gather its used gas, and then we feed
  1664. // it into the fee-creating function.
  1665. let mut tx = tx_builder.build()?;
  1666. let sigs = tx.create_sigs(&[signature_secret])?;
  1667. tx.signatures = vec![sigs];
  1668. let tree = self.get_money_tree().await?;
  1669. let (fee_call, fee_proofs, fee_secrets) =
  1670. self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
  1671. // Append the fee call to the transaction
  1672. tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
  1673. // Now build the actual transaction and sign it with all necessary keys.
  1674. let mut tx = tx_builder.build()?;
  1675. let sigs = tx.create_sigs(&[signature_secret])?;
  1676. tx.signatures.push(sigs);
  1677. let sigs = tx.create_sigs(&fee_secrets)?;
  1678. tx.signatures.push(sigs);
  1679. Ok(tx)
  1680. }
  1681. /// Vote on a DAO proposal
  1682. pub async fn dao_vote(
  1683. &self,
  1684. proposal_bulla: &DaoProposalBulla,
  1685. vote_option: bool,
  1686. weight: Option<u64>,
  1687. ) -> Result<Transaction> {
  1688. // Feth the proposal and check its deployed
  1689. let Ok(proposal) = self.get_dao_proposal_by_bulla(proposal_bulla).await else {
  1690. return Err(Error::Custom(format!("[dao_vote] Proposal {proposal_bulla} was not found")))
  1691. };
  1692. if proposal.leaf_position.is_none() ||
  1693. proposal.money_snapshot_tree.is_none() ||
  1694. proposal.nullifiers_smt_snapshot.is_none() ||
  1695. proposal.tx_hash.is_none() ||
  1696. proposal.call_index.is_none()
  1697. {
  1698. return Err(Error::Custom(
  1699. "[dao_vote] Proposal seems to not have been deployed yet".to_string(),
  1700. ))
  1701. }
  1702. // Check proposal is not executed
  1703. if let Some(exec_tx_hash) = proposal.exec_tx_hash {
  1704. return Err(Error::Custom(format!(
  1705. "[dao_vote] Proposal was executed on transaction: {exec_tx_hash}"
  1706. )))
  1707. }
  1708. // Check we know the plaintext data
  1709. if proposal.data.is_none() {
  1710. return Err(Error::Custom("[dao_vote] Proposal plainext data is empty".to_string()))
  1711. }
  1712. // Fetch DAO and check its deployed
  1713. let Ok(dao) = self.get_dao_by_bulla(&proposal.proposal.dao_bulla).await else {
  1714. return Err(Error::Custom(format!(
  1715. "[dao_vote] DAO {} was not found",
  1716. proposal.proposal.dao_bulla
  1717. )))
  1718. };
  1719. if dao.leaf_position.is_none() || dao.tx_hash.is_none() || dao.call_index.is_none() {
  1720. return Err(Error::Custom(
  1721. "[dao_vote] DAO seems to not have been deployed yet".to_string(),
  1722. ))
  1723. }
  1724. // Fetch all the proposal votes to check for duplicate nullifiers
  1725. let votes = self.get_dao_proposal_votes(proposal_bulla).await?;
  1726. let mut votes_nullifiers = vec![];
  1727. for vote in votes {
  1728. for nullifier in vote.nullifiers {
  1729. if !votes_nullifiers.contains(&nullifier) {
  1730. votes_nullifiers.push(nullifier);
  1731. }
  1732. }
  1733. }
  1734. // Fetch our own governance OwnCoins to see what our balance is
  1735. let gov_owncoins = self.get_token_coins(&dao.params.dao.gov_token_id).await?;
  1736. if gov_owncoins.is_empty() {
  1737. return Err(Error::Custom(format!(
  1738. "[dao_vote] Did not find any governance {} coins in wallet",
  1739. dao.params.dao.gov_token_id
  1740. )))
  1741. }
  1742. // Find which governance coins we can use
  1743. let gov_owncoins_to_use = match weight {
  1744. Some(_weight) => {
  1745. // TODO: Build a proper coin selection algorithm so that we can use a
  1746. // coins combination that matches the requested weight
  1747. return Err(Error::Custom(
  1748. "[dao_vote] Fractional vote weight not supported yet".to_string(),
  1749. ))
  1750. }
  1751. // If no weight was specified, use them all
  1752. None => gov_owncoins,
  1753. };
  1754. // Now we need to do a lookup for the zkas proof bincodes, and create
  1755. // the circuit objects and proving keys so we can build the transaction.
  1756. // We also do this through the RPC. First we grab the fee call from money.
  1757. let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
  1758. let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
  1759. else {
  1760. return Err(Error::Custom("[dao_vote] Fee circuit not found".to_string()))
  1761. };
  1762. let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
  1763. let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
  1764. // Creating Fee circuit proving key
  1765. let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
  1766. // Now we grab the DAO bins
  1767. let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
  1768. let Some(dao_vote_burn_zkbin) =
  1769. zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_VOTE_INPUT_NS)
  1770. else {
  1771. return Err(Error::Custom("[dao_vote] DAO Vote Burn circuit not found".to_string()))
  1772. };
  1773. let Some(dao_vote_main_zkbin) =
  1774. zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS)
  1775. else {
  1776. return Err(Error::Custom("[dao_vote] DAO Vote Main circuit not found".to_string()))
  1777. };
  1778. let dao_vote_burn_zkbin = ZkBinary::decode(&dao_vote_burn_zkbin.1)?;
  1779. let dao_vote_main_zkbin = ZkBinary::decode(&dao_vote_main_zkbin.1)?;
  1780. let dao_vote_burn_circuit =
  1781. ZkCircuit::new(empty_witnesses(&dao_vote_burn_zkbin)?, &dao_vote_burn_zkbin);
  1782. let dao_vote_main_circuit =
  1783. ZkCircuit::new(empty_witnesses(&dao_vote_main_zkbin)?, &dao_vote_main_zkbin);
  1784. // Creating DAO VoteBurn and VoteMain circuits proving keys
  1785. let dao_vote_burn_pk = ProvingKey::build(dao_vote_burn_zkbin.k, &dao_vote_burn_circuit);
  1786. let dao_vote_main_pk = ProvingKey::build(dao_vote_main_zkbin.k, &dao_vote_main_circuit);
  1787. // Now create the parameters for the vote tx
  1788. let signature_secret = SecretKey::random(&mut OsRng);
  1789. let mut inputs = Vec::with_capacity(gov_owncoins_to_use.len());
  1790. for gov_owncoin in gov_owncoins_to_use {
  1791. let nullifier = poseidon_hash([gov_owncoin.secret.inner(), gov_owncoin.coin.inner()]);
  1792. let vote_nullifier =
  1793. poseidon_hash([nullifier, gov_owncoin.secret.inner(), proposal_bulla.inner()]);
  1794. if votes_nullifiers.contains(&vote_nullifier.into()) {
  1795. return Err(Error::Custom("[dao_vote] Duplicate input nullifier found".to_string()))
  1796. };
  1797. let input = DaoVoteInput {
  1798. secret: gov_owncoin.secret,
  1799. note: gov_owncoin.note.clone(),
  1800. leaf_position: gov_owncoin.leaf_position,
  1801. merkle_path: proposal
  1802. .money_snapshot_tree
  1803. .as_ref()
  1804. .unwrap()
  1805. .witness(gov_owncoin.leaf_position, 0)
  1806. .unwrap(),
  1807. signature_secret,
  1808. };
  1809. inputs.push(input);
  1810. }
  1811. // Retrieve next block height and current block time target,
  1812. // to compute their window.
  1813. let next_block_height = self.get_next_block_height().await?;
  1814. let block_target = self.get_block_target().await?;
  1815. let current_blockwindow = blockwindow(next_block_height, block_target);
  1816. // Generate the Money nullifiers Sparse Merkle Tree
  1817. let store = MemoryStorageFp { tree: proposal.nullifiers_smt_snapshot.unwrap() };
  1818. let money_null_smt = SmtMemoryFp::new(store, PoseidonFp::new(), &EMPTY_NODES_FP);
  1819. // Create the vote call
  1820. let call = DaoVoteCall {
  1821. money_null_smt: &money_null_smt,
  1822. inputs,
  1823. vote_option,
  1824. proposal: proposal.proposal.clone(),
  1825. dao: dao.params.dao.clone(),
  1826. current_blockwindow,
  1827. };
  1828. let (params, proofs) = call.make(
  1829. &dao_vote_burn_zkbin,
  1830. &dao_vote_burn_pk,
  1831. &dao_vote_main_zkbin,
  1832. &dao_vote_main_pk,
  1833. )?;
  1834. // Encode the call
  1835. let mut data = vec![DaoFunction::Vote as u8];
  1836. params.encode_async(&mut data).await?;
  1837. let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
  1838. // Create the TransactionBuilder containing above call
  1839. let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
  1840. // We first have to execute the fee-less tx to gather its used gas, and then we feed
  1841. // it into the fee-creating function.
  1842. let mut tx = tx_builder.build()?;
  1843. let sigs = tx.create_sigs(&[signature_secret])?;
  1844. tx.signatures = vec![sigs];
  1845. let tree = self.get_money_tree().await?;
  1846. let (fee_call, fee_proofs, fee_secrets) =
  1847. self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
  1848. // Append the fee call to the transaction
  1849. tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
  1850. // Now build the actual transaction and sign it with all necessary keys.
  1851. let mut tx = tx_builder.build()?;
  1852. let sigs = tx.create_sigs(&[signature_secret])?;
  1853. tx.signatures.push(sigs);
  1854. let sigs = tx.create_sigs(&fee_secrets)?;
  1855. tx.signatures.push(sigs);
  1856. Ok(tx)
  1857. }
  1858. /// Execute a DAO transfer proposal.
  1859. pub async fn dao_exec_transfer(&self, proposal: &ProposalRecord) -> Result<Transaction> {
  1860. if proposal.leaf_position.is_none() ||
  1861. proposal.money_snapshot_tree.is_none() ||
  1862. proposal.nullifiers_smt_snapshot.is_none() ||
  1863. proposal.tx_hash.is_none() ||
  1864. proposal.call_index.is_none()
  1865. {
  1866. return Err(Error::Custom(
  1867. "[dao_exec_transfer] Proposal seems to not have been deployed yet".to_string(),
  1868. ))
  1869. }
  1870. // Check proposal is not executed
  1871. if let Some(exec_tx_hash) = proposal.exec_tx_hash {
  1872. return Err(Error::Custom(format!(
  1873. "[dao_exec_transfer] Proposal was executed on transaction: {exec_tx_hash}"
  1874. )))
  1875. }
  1876. // Check we know the plaintext data and they are valid
  1877. if proposal.data.is_none() {
  1878. return Err(Error::Custom(
  1879. "[dao_exec_transfer] Proposal plainext data is empty".to_string(),
  1880. ))
  1881. }
  1882. let proposal_coinattrs: CoinAttributes =
  1883. deserialize_async(proposal.data.as_ref().unwrap()).await?;
  1884. // Fetch DAO and check its deployed
  1885. let Ok(dao) = self.get_dao_by_bulla(&proposal.proposal.dao_bulla).await else {
  1886. return Err(Error::Custom(format!(
  1887. "[dao_exec_transfer] DAO {} was not found",
  1888. proposal.proposal.dao_bulla
  1889. )))
  1890. };
  1891. if dao.leaf_position.is_none() || dao.tx_hash.is_none() || dao.call_index.is_none() {
  1892. return Err(Error::Custom(
  1893. "[dao_exec_transfer] DAO seems to not have been deployed yet".to_string(),
  1894. ))
  1895. }
  1896. // Check proposal is approved
  1897. let votes = self.get_dao_proposal_votes(&proposal.bulla()).await?;
  1898. let mut yes_vote_value = 0;
  1899. let mut yes_vote_blind = Blind::ZERO;
  1900. let mut all_vote_value = 0;
  1901. let mut all_vote_blind = Blind::ZERO;
  1902. for vote in votes {
  1903. if vote.vote_option {
  1904. yes_vote_value += vote.all_vote_value;
  1905. };
  1906. yes_vote_blind += vote.yes_vote_blind;
  1907. all_vote_value += vote.all_vote_value;
  1908. all_vote_blind += vote.all_vote_blind;
  1909. }
  1910. let approval_ratio = (yes_vote_value as f64 * 100.0) / all_vote_value as f64;
  1911. if all_vote_value < dao.params.dao.quorum ||
  1912. approval_ratio <
  1913. (dao.params.dao.approval_ratio_quot / dao.params.dao.approval_ratio_base)
  1914. as f64
  1915. {
  1916. return Err(Error::Custom(
  1917. "[dao_exec_transfer] Proposal is not approved yet".to_string(),
  1918. ))
  1919. };
  1920. // Fetch DAO unspent OwnCoins to see what its balance is for the coin
  1921. let dao_spend_hook =
  1922. FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
  1923. .to_func_id();
  1924. let dao_owncoins = self
  1925. .get_contract_token_coins(
  1926. &proposal_coinattrs.token_id,
  1927. &dao_spend_hook,
  1928. &proposal.proposal.dao_bulla.inner(),
  1929. )
  1930. .await?;
  1931. if dao_owncoins.is_empty() {
  1932. return Err(Error::Custom(format!(
  1933. "[dao_exec_transfer] Did not find any {} unspent coins owned by this DAO",
  1934. proposal_coinattrs.token_id,
  1935. )))
  1936. }
  1937. // Check DAO balance is sufficient
  1938. if dao_owncoins.iter().map(|x| x.note.value).sum::<u64>() < proposal_coinattrs.value {
  1939. return Err(Error::Custom(format!(
  1940. "[dao_exec_transfer] Not enough DAO balance for token ID: {}",
  1941. proposal_coinattrs.token_id,
  1942. )))
  1943. }
  1944. // Find which DAO coins we can use
  1945. let (spent_coins, change_value) = select_coins(dao_owncoins, proposal_coinattrs.value)?;
  1946. // Now we need to do a lookup for the zkas proof bincodes, and create
  1947. // the circuit objects and proving keys so we can build the transaction.
  1948. // We also do this through the RPC. First we grab the calls from money.
  1949. let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
  1950. let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1)
  1951. else {
  1952. return Err(Error::Custom("Mint circuit not found".to_string()))
  1953. };
  1954. let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1)
  1955. else {
  1956. return Err(Error::Custom("Burn circuit not found".to_string()))
  1957. };
  1958. let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
  1959. else {
  1960. return Err(Error::Custom("Fee circuit not found".to_string()))
  1961. };
  1962. let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
  1963. let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
  1964. let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
  1965. let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
  1966. let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
  1967. let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
  1968. // Creating Mint, Burn and Fee circuits proving keys
  1969. let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
  1970. let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
  1971. let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
  1972. // Now we grab the DAO bins
  1973. let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
  1974. let Some(dao_exec_zkbin) = zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_EXEC_NS)
  1975. else {
  1976. return Err(Error::Custom("[dao_exec_transfer] DAO Exec circuit not found".to_string()))
  1977. };
  1978. let Some(dao_auth_transfer_zkbin) =
  1979. zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS)
  1980. else {
  1981. return Err(Error::Custom(
  1982. "[dao_exec_transfer] DAO AuthTransfer circuit not found".to_string(),
  1983. ))
  1984. };
  1985. let Some(dao_auth_transfer_enc_coin_zkbin) =
  1986. zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS)
  1987. else {
  1988. return Err(Error::Custom(
  1989. "[dao_exec_transfer] DAO AuthTransferEncCoin circuit not found".to_string(),
  1990. ))
  1991. };
  1992. let dao_exec_zkbin = ZkBinary::decode(&dao_exec_zkbin.1)?;
  1993. let dao_auth_transfer_zkbin = ZkBinary::decode(&dao_auth_transfer_zkbin.1)?;
  1994. let dao_auth_transfer_enc_coin_zkbin =
  1995. ZkBinary::decode(&dao_auth_transfer_enc_coin_zkbin.1)?;
  1996. let dao_exec_circuit = ZkCircuit::new(empty_witnesses(&dao_exec_zkbin)?, &dao_exec_zkbin);
  1997. let dao_auth_transfer_circuit =
  1998. ZkCircuit::new(empty_witnesses(&dao_auth_transfer_zkbin)?, &dao_auth_transfer_zkbin);
  1999. let dao_auth_transfer_enc_coin_circuit = ZkCircuit::new(
  2000. empty_witnesses(&dao_auth_transfer_enc_coin_zkbin)?,
  2001. &dao_auth_transfer_enc_coin_zkbin,
  2002. );
  2003. // Creating DAO Exec, AuthTransfer and AuthTransferEncCoin circuits proving keys
  2004. let dao_exec_pk = ProvingKey::build(dao_exec_zkbin.k, &dao_exec_circuit);
  2005. let dao_auth_transfer_pk =
  2006. ProvingKey::build(dao_auth_transfer_zkbin.k, &dao_auth_transfer_circuit);
  2007. let dao_auth_transfer_enc_coin_pk = ProvingKey::build(
  2008. dao_auth_transfer_enc_coin_zkbin.k,
  2009. &dao_auth_transfer_enc_coin_circuit,
  2010. );
  2011. // Fetch our money Merkle tree
  2012. let tree = self.get_money_tree().await?;
  2013. // Retrieve next block height and current block time target,
  2014. // to compute their window.
  2015. let next_block_height = self.get_next_block_height().await?;
  2016. let block_target = self.get_block_target().await?;
  2017. let current_blockwindow = blockwindow(next_block_height, block_target);
  2018. // Now we can create the transfer call parameters
  2019. let input_user_data_blind = Blind::random(&mut OsRng);
  2020. let mut inputs = vec![];
  2021. for coin in &spent_coins {
  2022. inputs.push(TransferCallInput {
  2023. coin: coin.clone(),
  2024. merkle_path: tree.witness(coin.leaf_position, 0).unwrap(),
  2025. user_data_blind: input_user_data_blind,
  2026. });
  2027. }
  2028. let mut outputs = vec![];
  2029. outputs.push(proposal_coinattrs.clone());
  2030. let dao_coin_attrs = CoinAttributes {
  2031. public_key: dao.keypair().public,
  2032. value: change_value,
  2033. token_id: proposal_coinattrs.token_id,
  2034. spend_hook: dao_spend_hook,
  2035. user_data: proposal.proposal.dao_bulla.inner(),
  2036. blind: Blind::random(&mut OsRng),
  2037. };
  2038. outputs.push(dao_coin_attrs.clone());
  2039. // Create the transfer call
  2040. let transfer_builder = TransferCallBuilder {
  2041. clear_inputs: vec![],
  2042. inputs,
  2043. outputs,
  2044. mint_zkbin: mint_zkbin.clone(),
  2045. mint_pk: mint_pk.clone(),
  2046. burn_zkbin: burn_zkbin.clone(),
  2047. burn_pk: burn_pk.clone(),
  2048. };
  2049. let (transfer_params, transfer_secrets) = transfer_builder.build()?;
  2050. // Encode the call
  2051. let mut data = vec![MoneyFunction::TransferV1 as u8];
  2052. transfer_params.encode_async(&mut data).await?;
  2053. let transfer_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
  2054. // Create the exec call
  2055. let exec_signature_secret = SecretKey::random(&mut OsRng);
  2056. let exec_builder = DaoExecCall {
  2057. proposal: proposal.proposal.clone(),
  2058. dao: dao.params.dao.clone(),
  2059. yes_vote_value,
  2060. all_vote_value,
  2061. yes_vote_blind,
  2062. all_vote_blind,
  2063. signature_secret: exec_signature_secret,
  2064. current_blockwindow,
  2065. };
  2066. // TODO: use the proper key if we have it
  2067. let (exec_params, exec_proofs) =
  2068. exec_builder.make(&dao.params.secret_key, &None, &dao_exec_zkbin, &dao_exec_pk)?;
  2069. // Encode the call
  2070. let mut data = vec![DaoFunction::Exec as u8];
  2071. exec_params.encode_async(&mut data).await?;
  2072. let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
  2073. // Now we can create the auth call
  2074. // Auth module
  2075. let auth_transfer_builder = DaoAuthMoneyTransferCall {
  2076. proposal: proposal.proposal.clone(),
  2077. proposal_coinattrs: vec![proposal_coinattrs],
  2078. dao: dao.params.dao.clone(),
  2079. input_user_data_blind,
  2080. dao_coin_attrs,
  2081. };
  2082. let (auth_transfer_params, auth_transfer_proofs) = auth_transfer_builder.make(
  2083. &dao_auth_transfer_zkbin,
  2084. &dao_auth_transfer_pk,
  2085. &dao_auth_transfer_enc_coin_zkbin,
  2086. &dao_auth_transfer_enc_coin_pk,
  2087. )?;
  2088. // Encode the call
  2089. let mut data = vec![DaoFunction::AuthMoneyTransfer as u8];
  2090. auth_transfer_params.encode_async(&mut data).await?;
  2091. let auth_transfer_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
  2092. // Create the TransactionBuilder containing above calls
  2093. let mut tx_builder = TransactionBuilder::new(
  2094. ContractCallLeaf { call: exec_call, proofs: exec_proofs },
  2095. vec![
  2096. DarkTree::new(
  2097. ContractCallLeaf { call: auth_transfer_call, proofs: auth_transfer_proofs },
  2098. vec![],
  2099. None,
  2100. None,
  2101. ),
  2102. DarkTree::new(
  2103. ContractCallLeaf { call: transfer_call, proofs: transfer_secrets.proofs },
  2104. vec![],
  2105. None,
  2106. None,
  2107. ),
  2108. ],
  2109. )?;
  2110. // We first have to execute the fee-less tx to gather its used gas, and then we feed
  2111. // it into the fee-creating function.
  2112. let mut tx = tx_builder.build()?;
  2113. let auth_transfer_sigs = tx.create_sigs(&[])?;
  2114. let transfer_sigs = tx.create_sigs(&transfer_secrets.signature_secrets)?;
  2115. let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
  2116. tx.signatures = vec![auth_transfer_sigs, transfer_sigs, exec_sigs];
  2117. let (fee_call, fee_proofs, fee_secrets) =
  2118. self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
  2119. // Append the fee call to the transaction
  2120. tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
  2121. // Now build the actual transaction and sign it with all necessary keys.
  2122. let mut tx = tx_builder.build()?;
  2123. let sigs = tx.create_sigs(&[])?;
  2124. tx.signatures.push(sigs);
  2125. let sigs = tx.create_sigs(&transfer_secrets.signature_secrets)?;
  2126. tx.signatures.push(sigs);
  2127. let sigs = tx.create_sigs(&[exec_signature_secret])?;
  2128. tx.signatures.push(sigs);
  2129. let sigs = tx.create_sigs(&fee_secrets)?;
  2130. tx.signatures.push(sigs);
  2131. Ok(tx)
  2132. }
  2133. }