integration.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2023 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use darkfi::Result;
  19. use darkfi_contract_test_harness::{init_logger, Holder, TestHarness};
  20. use darkfi_dao_contract::{
  21. client::DaoVoteNote,
  22. model::{Dao, DaoBlindAggregateVote},
  23. };
  24. use darkfi_money_contract::model::CoinAttributes;
  25. use darkfi_sdk::{
  26. crypto::{pasta_prelude::Field, pedersen_commitment_u64, DAO_CONTRACT_ID, DARK_TOKEN_ID},
  27. pasta::pallas,
  28. };
  29. use log::info;
  30. use rand::rngs::OsRng;
  31. #[test]
  32. fn integration_test() -> Result<()> {
  33. smol::block_on(async {
  34. init_logger();
  35. // Holders this test will use:
  36. // * Faucet airdrops DRK
  37. // * Alice, Bob, and Charlie are members of the DAO.
  38. // * Rachel is the proposal recipient.
  39. // * Dao is the DAO wallet
  40. const HOLDERS: [Holder; 6] = [
  41. Holder::Faucet,
  42. Holder::Alice,
  43. Holder::Bob,
  44. Holder::Charlie,
  45. Holder::Rachel,
  46. Holder::Dao,
  47. ];
  48. // Initialize harness
  49. let mut th = TestHarness::new(&["money".to_string(), "dao".to_string()], false).await?;
  50. // We'll use the ALICE token as the DAO governance token
  51. let gov_token_id = th.token_id(&Holder::Alice);
  52. const ALICE_GOV_SUPPLY: u64 = 100_000_000;
  53. const BOB_GOV_SUPPLY: u64 = 100_000_000;
  54. const CHARLIE_GOV_SUPPLY: u64 = 100_000_000;
  55. // And the DRK token as the treasury token
  56. let drk_token_id = *DARK_TOKEN_ID;
  57. const DRK_TOKEN_SUPPLY: u64 = 1_000_000_000;
  58. // The tokens we want to send via the proposal
  59. const PROPOSAL_AMOUNT: u64 = 250_000_000;
  60. // Slot to verify against
  61. let current_slot = 0;
  62. // DAO parameters
  63. let dao_keypair = th.holders.get(&Holder::Dao).unwrap().keypair;
  64. let dao = Dao {
  65. proposer_limit: 100_000_000,
  66. quorum: 199_999_999,
  67. approval_ratio_base: 2,
  68. approval_ratio_quot: 1,
  69. gov_token_id,
  70. public_key: dao_keypair.public,
  71. bulla_blind: pallas::Base::random(&mut OsRng),
  72. };
  73. // ====================
  74. // Dao::Mint
  75. // Create the DAO bulla
  76. // ====================
  77. info!("Stage 1. Creating DAO bulla");
  78. info!("[Dao] Building DAO mint tx");
  79. let (dao_mint_tx, dao_mint_params) = th.dao_mint(&dao, &dao_keypair)?;
  80. for holder in &HOLDERS {
  81. info!("[{holder:?}] Executing DAO Mint tx");
  82. th.execute_dao_mint_tx(holder, &dao_mint_tx, &dao_mint_params, current_slot).await?;
  83. }
  84. th.assert_trees(&HOLDERS);
  85. // =======================================
  86. // Airdrop some treasury tokens to the DAO
  87. // =======================================
  88. info!("Stage 2. Send Treasury token");
  89. info!("[Faucet] Building DAO airdrop tx");
  90. let (airdrop_tx, airdrop_params) = th.airdrop_native(
  91. DRK_TOKEN_SUPPLY,
  92. &Holder::Dao,
  93. Some(DAO_CONTRACT_ID.inner()), // spend_hook
  94. Some(dao_mint_params.dao_bulla.inner()), // user_data
  95. )?;
  96. for holder in &HOLDERS {
  97. info!("[{holder:?}] Executing DAO airdrop tx");
  98. th.execute_airdrop_native_tx(holder, &airdrop_tx, &airdrop_params, current_slot)
  99. .await?;
  100. }
  101. th.assert_trees(&HOLDERS);
  102. // Gather the DAO owncoin
  103. th.gather_owncoin(&Holder::Dao, &airdrop_params.outputs[0], None)?;
  104. // ======================================
  105. // Mint the governance token to 3 holders
  106. // ======================================
  107. info!("Stage 3. Minting governance token");
  108. info!("[Alice] Building governance token mint tx for Alice");
  109. let (a_token_mint_tx, a_token_mint_params) =
  110. th.token_mint(ALICE_GOV_SUPPLY, &Holder::Alice, &Holder::Alice, None, None)?;
  111. for holder in &HOLDERS {
  112. info!("[{holder:?}] Executing governance token mint tx for Alice");
  113. th.execute_token_mint_tx(holder, &a_token_mint_tx, &a_token_mint_params, current_slot)
  114. .await?;
  115. }
  116. th.assert_trees(&HOLDERS);
  117. // Gather owncoin
  118. th.gather_owncoin(&Holder::Alice, &a_token_mint_params.output, None)?;
  119. info!("[Alice] Building governance token mint tx for Bob");
  120. let (b_token_mint_tx, b_token_mint_params) =
  121. th.token_mint(BOB_GOV_SUPPLY, &Holder::Alice, &Holder::Bob, None, None)?;
  122. for holder in &HOLDERS {
  123. info!("[{holder:?}] Executing governance token mint tx for Bob");
  124. th.execute_token_mint_tx(holder, &b_token_mint_tx, &b_token_mint_params, current_slot)
  125. .await?;
  126. }
  127. th.assert_trees(&HOLDERS);
  128. // Gather owncoin
  129. th.gather_owncoin(&Holder::Bob, &b_token_mint_params.output, None)?;
  130. info!("[Alice] Building governance token mint tx for Charlie");
  131. let (c_token_mint_tx, c_token_mint_params) =
  132. th.token_mint(CHARLIE_GOV_SUPPLY, &Holder::Alice, &Holder::Charlie, None, None)?;
  133. for holder in &HOLDERS {
  134. info!("[{holder:?}] Executing governance token mint tx for Charlie");
  135. th.execute_token_mint_tx(holder, &c_token_mint_tx, &c_token_mint_params, current_slot)
  136. .await?;
  137. }
  138. th.assert_trees(&HOLDERS);
  139. // Gather owncoin
  140. th.gather_owncoin(&Holder::Charlie, &c_token_mint_params.output, None)?;
  141. // ================
  142. // Dao::Propose
  143. // Propose the vote
  144. // ================
  145. info!("Stage 4. Propose the vote");
  146. // TODO: look into proposal expiry once time for voting has finished
  147. // TODO: Is it possible for an invalid transfer() to be constructed on exec()?
  148. // Need to look into this.
  149. info!("[Alice] Building DAO proposal tx");
  150. // These coins are passed around to all DAO members who verify its validity
  151. // They also check hashing them equals the proposal_commit
  152. let proposal_coinattrs = vec![CoinAttributes {
  153. public_key: th.holders.get(&Holder::Rachel).unwrap().keypair.public,
  154. value: PROPOSAL_AMOUNT,
  155. token_id: drk_token_id,
  156. serial: pallas::Base::random(&mut OsRng),
  157. spend_hook: pallas::Base::ZERO,
  158. user_data: pallas::Base::ZERO,
  159. }];
  160. // We can add whatever we want in here, even arbitrary text
  161. // It's up to the auth module to decide what to do with it.
  162. let user_data = pallas::Base::ZERO;
  163. let (propose_tx, propose_params, propose_info) = th.dao_propose(
  164. &Holder::Alice,
  165. &proposal_coinattrs,
  166. user_data,
  167. &dao,
  168. &dao_mint_params.dao_bulla,
  169. )?;
  170. for holder in &HOLDERS {
  171. info!("[{holder:?}] Executing DAO proposal tx");
  172. th.execute_dao_propose_tx(holder, &propose_tx, &propose_params, current_slot).await?;
  173. }
  174. th.assert_trees(&HOLDERS);
  175. // =====================================
  176. // Dao::Vote
  177. // Proposal is accepted. Start the vote.
  178. // =====================================
  179. info!("Stage 5. Start voting");
  180. info!("[Alice] Building vote tx (yes)");
  181. let (alice_vote_tx, alice_vote_params) = th.dao_vote(
  182. &Holder::Alice,
  183. &dao_keypair,
  184. true,
  185. &dao,
  186. &propose_info,
  187. &propose_params.proposal_bulla,
  188. )?;
  189. info!("[Bob] Building vote tx (no)");
  190. let (bob_vote_tx, bob_vote_params) = th.dao_vote(
  191. &Holder::Bob,
  192. &dao_keypair,
  193. false,
  194. &dao,
  195. &propose_info,
  196. &propose_params.proposal_bulla,
  197. )?;
  198. info!("[Charlie] Building vote tx (yes)");
  199. let (charlie_vote_tx, charlie_vote_params) = th.dao_vote(
  200. &Holder::Charlie,
  201. &dao_keypair,
  202. true,
  203. &dao,
  204. &propose_info,
  205. &propose_params.proposal_bulla,
  206. )?;
  207. for holder in &HOLDERS {
  208. info!("[{holder:?}] Executing Alice vote tx");
  209. th.execute_dao_vote_tx(holder, &alice_vote_tx, &alice_vote_params, current_slot)
  210. .await?;
  211. info!("[{holder:?}] Executing Bob vote tx");
  212. th.execute_dao_vote_tx(holder, &bob_vote_tx, &bob_vote_params, current_slot).await?;
  213. info!("[{holder:?}] Executing Charlie vote tx");
  214. th.execute_dao_vote_tx(holder, &charlie_vote_tx, &charlie_vote_params, current_slot)
  215. .await?;
  216. }
  217. // Gather and decrypt all vote notes
  218. let vote_note_1: DaoVoteNote = alice_vote_params.note.decrypt(&dao_keypair.secret).unwrap();
  219. let vote_note_2: DaoVoteNote = bob_vote_params.note.decrypt(&dao_keypair.secret).unwrap();
  220. let vote_note_3: DaoVoteNote =
  221. charlie_vote_params.note.decrypt(&dao_keypair.secret).unwrap();
  222. // Count the votes
  223. let mut total_yes_vote_value = 0;
  224. let mut total_all_vote_value = 0;
  225. let mut blind_total_vote = DaoBlindAggregateVote::default();
  226. let mut total_yes_vote_blind = pallas::Scalar::ZERO;
  227. let mut total_all_vote_blind = pallas::Scalar::ZERO;
  228. for (i, note) in [vote_note_1, vote_note_2, vote_note_3].iter().enumerate() {
  229. total_yes_vote_blind += note.yes_vote_blind;
  230. total_all_vote_blind += note.all_vote_blind;
  231. // Update private values
  232. // vote_option is either 0 or 1
  233. let yes_vote_value = note.vote_option as u64 * note.all_vote_value;
  234. total_yes_vote_value += yes_vote_value;
  235. total_all_vote_value += note.all_vote_value;
  236. // Update public values
  237. let yes_vote_commit = pedersen_commitment_u64(yes_vote_value, note.yes_vote_blind);
  238. let all_vote_commit = pedersen_commitment_u64(note.all_vote_value, note.all_vote_blind);
  239. let blind_vote = DaoBlindAggregateVote { yes_vote_commit, all_vote_commit };
  240. blind_total_vote.aggregate(blind_vote);
  241. // Just for the debug
  242. let vote_result = match note.vote_option {
  243. true => "yes",
  244. false => "no",
  245. };
  246. info!("Voter {} voted {} with {} tokens", i, vote_result, note.all_vote_value);
  247. }
  248. info!("Outcome = {} / {}", total_yes_vote_value, total_all_vote_value);
  249. assert!(
  250. blind_total_vote.all_vote_commit ==
  251. pedersen_commitment_u64(total_all_vote_value, total_all_vote_blind)
  252. );
  253. assert!(
  254. blind_total_vote.yes_vote_commit ==
  255. pedersen_commitment_u64(total_yes_vote_value, total_yes_vote_blind)
  256. );
  257. // ================
  258. // Dao::Exec
  259. // Execute the vote
  260. // ================
  261. info!("Stage 6. Execute the vote");
  262. info!("[Dao] Building Dao::Exec tx");
  263. let (exec_tx, xfer_params, exec_params) = th.dao_exec(
  264. &dao,
  265. &dao_mint_params.dao_bulla,
  266. &propose_info,
  267. proposal_coinattrs,
  268. total_yes_vote_value,
  269. total_all_vote_value,
  270. total_yes_vote_blind,
  271. total_all_vote_blind,
  272. )?;
  273. for holder in &HOLDERS {
  274. info!("[{holder:?}] Executing Dao::Exec tx");
  275. th.execute_dao_exec_tx(holder, &exec_tx, &xfer_params, &exec_params, current_slot)
  276. .await?;
  277. }
  278. th.assert_trees(&HOLDERS);
  279. // Gather the coins
  280. th.gather_owncoin(&Holder::Rachel, &xfer_params.outputs[0], None)?;
  281. th.gather_owncoin(&Holder::Dao, &xfer_params.outputs[1], None)?;
  282. let rachel_wallet = th.holders.get(&Holder::Rachel).unwrap();
  283. assert!(rachel_wallet.unspent_money_coins[0].note.value == PROPOSAL_AMOUNT);
  284. assert!(rachel_wallet.unspent_money_coins[0].note.token_id == drk_token_id);
  285. // FIXME: The harness doesn't register that we spent the first coin on the proposal.
  286. let dao_wallet = th.holders.get(&Holder::Dao).unwrap();
  287. assert!(dao_wallet.unspent_money_coins[1].note.value == DRK_TOKEN_SUPPLY - PROPOSAL_AMOUNT);
  288. assert!(dao_wallet.unspent_money_coins[1].note.token_id == drk_token_id);
  289. // Stats
  290. th.statistics();
  291. // Thanks for reading
  292. Ok(())
  293. })
  294. }