integration.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  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 darkfi::Result;
  19. use darkfi_contract_test_harness::{init_logger, Holder, TestHarness};
  20. use darkfi_dao_contract::model::{Dao, DaoBlindAggregateVote};
  21. use darkfi_money_contract::model::CoinAttributes;
  22. use darkfi_sdk::{
  23. crypto::{
  24. pasta_prelude::*,
  25. pedersen_commitment_u64,
  26. util::{fp_mod_fv, fp_to_u64},
  27. DAO_CONTRACT_ID, DARK_TOKEN_ID,
  28. },
  29. pasta::pallas,
  30. };
  31. use log::info;
  32. use rand::rngs::OsRng;
  33. #[test]
  34. fn integration_test() -> Result<()> {
  35. smol::block_on(async {
  36. init_logger();
  37. // Holders this test will use:
  38. // * Faucet airdrops DRK
  39. // * Alice, Bob, and Charlie are members of the DAO.
  40. // * Rachel is the proposal recipient.
  41. // * Dao is the DAO wallet
  42. const HOLDERS: [Holder; 6] = [
  43. Holder::Faucet,
  44. Holder::Alice,
  45. Holder::Bob,
  46. Holder::Charlie,
  47. Holder::Rachel,
  48. Holder::Dao,
  49. ];
  50. // Initialize harness
  51. let mut th = TestHarness::new(&["money".to_string(), "dao".to_string()], false).await?;
  52. // We'll use the ALICE token as the DAO governance token
  53. let gov_token_id = th.token_id(&Holder::Alice);
  54. const ALICE_GOV_SUPPLY: u64 = 100_000_000;
  55. const BOB_GOV_SUPPLY: u64 = 100_000_000;
  56. const CHARLIE_GOV_SUPPLY: u64 = 100_000_000;
  57. // And the DRK token as the treasury token
  58. let drk_token_id = *DARK_TOKEN_ID;
  59. const DRK_TOKEN_SUPPLY: u64 = 1_000_000_000;
  60. // The tokens we want to send via the proposal
  61. const PROPOSAL_AMOUNT: u64 = 250_000_000;
  62. // Block height to verify against
  63. let current_block_height = 0;
  64. // DAO parameters
  65. let dao_keypair = th.holders.get(&Holder::Dao).unwrap().keypair;
  66. let dao = Dao {
  67. proposer_limit: 100_000_000,
  68. quorum: 199_999_999,
  69. approval_ratio_base: 2,
  70. approval_ratio_quot: 1,
  71. gov_token_id,
  72. public_key: dao_keypair.public,
  73. bulla_blind: pallas::Base::random(&mut OsRng),
  74. };
  75. // ====================
  76. // Dao::Mint
  77. // Create the DAO bulla
  78. // ====================
  79. info!("Stage 1. Creating DAO bulla");
  80. info!("[Dao] Building DAO mint tx");
  81. let (dao_mint_tx, dao_mint_params) = th.dao_mint(&dao, &dao_keypair)?;
  82. for holder in &HOLDERS {
  83. info!("[{holder:?}] Executing DAO Mint tx");
  84. th.execute_dao_mint_tx(holder, &dao_mint_tx, &dao_mint_params, current_block_height)
  85. .await?;
  86. }
  87. th.assert_trees(&HOLDERS);
  88. // =======================================
  89. // Airdrop some treasury tokens to the DAO
  90. // =======================================
  91. info!("Stage 2. Send Treasury token");
  92. info!("[Faucet] Building DAO airdrop tx");
  93. let (airdrop_tx, airdrop_params) = th.airdrop_native(
  94. DRK_TOKEN_SUPPLY,
  95. &Holder::Dao,
  96. Some(DAO_CONTRACT_ID.inner()), // spend_hook
  97. Some(dao_mint_params.dao_bulla.inner()), // user_data
  98. )?;
  99. for holder in &HOLDERS {
  100. info!("[{holder:?}] Executing DAO airdrop tx");
  101. th.execute_airdrop_native_tx(
  102. holder,
  103. &airdrop_tx,
  104. &airdrop_params,
  105. current_block_height,
  106. )
  107. .await?;
  108. }
  109. th.assert_trees(&HOLDERS);
  110. // Gather the DAO owncoin
  111. th.gather_owncoin(&Holder::Dao, &airdrop_params.outputs[0], None)?;
  112. // ======================================
  113. // Mint the governance token to 3 holders
  114. // ======================================
  115. info!("Stage 3. Minting governance token");
  116. info!("[Alice] Building governance token mint tx for Alice");
  117. let (a_token_mint_tx, a_token_mint_params) =
  118. th.token_mint(ALICE_GOV_SUPPLY, &Holder::Alice, &Holder::Alice, None, None)?;
  119. for holder in &HOLDERS {
  120. info!("[{holder:?}] Executing governance token mint tx for Alice");
  121. th.execute_token_mint_tx(
  122. holder,
  123. &a_token_mint_tx,
  124. &a_token_mint_params,
  125. current_block_height,
  126. )
  127. .await?;
  128. }
  129. th.assert_trees(&HOLDERS);
  130. // Gather owncoin
  131. th.gather_owncoin(&Holder::Alice, &a_token_mint_params.output, None)?;
  132. info!("[Alice] Building governance token mint tx for Bob");
  133. let (b_token_mint_tx, b_token_mint_params) =
  134. th.token_mint(BOB_GOV_SUPPLY, &Holder::Alice, &Holder::Bob, None, None)?;
  135. for holder in &HOLDERS {
  136. info!("[{holder:?}] Executing governance token mint tx for Bob");
  137. th.execute_token_mint_tx(
  138. holder,
  139. &b_token_mint_tx,
  140. &b_token_mint_params,
  141. current_block_height,
  142. )
  143. .await?;
  144. }
  145. th.assert_trees(&HOLDERS);
  146. // Gather owncoin
  147. th.gather_owncoin(&Holder::Bob, &b_token_mint_params.output, None)?;
  148. info!("[Alice] Building governance token mint tx for Charlie");
  149. let (c_token_mint_tx, c_token_mint_params) =
  150. th.token_mint(CHARLIE_GOV_SUPPLY, &Holder::Alice, &Holder::Charlie, None, None)?;
  151. for holder in &HOLDERS {
  152. info!("[{holder:?}] Executing governance token mint tx for Charlie");
  153. th.execute_token_mint_tx(
  154. holder,
  155. &c_token_mint_tx,
  156. &c_token_mint_params,
  157. current_block_height,
  158. )
  159. .await?;
  160. }
  161. th.assert_trees(&HOLDERS);
  162. // Gather owncoin
  163. th.gather_owncoin(&Holder::Charlie, &c_token_mint_params.output, None)?;
  164. // ================
  165. // Dao::Propose
  166. // Propose the vote
  167. // ================
  168. info!("Stage 4. Propose the vote");
  169. // TODO: look into proposal expiry once time for voting has finished
  170. // TODO: Is it possible for an invalid transfer() to be constructed on exec()?
  171. // Need to look into this.
  172. info!("[Alice] Building DAO proposal tx");
  173. // These coins are passed around to all DAO members who verify its validity
  174. // They also check hashing them equals the proposal_commit
  175. let proposal_coinattrs = vec![CoinAttributes {
  176. public_key: th.holders.get(&Holder::Rachel).unwrap().keypair.public,
  177. value: PROPOSAL_AMOUNT,
  178. token_id: drk_token_id,
  179. spend_hook: pallas::Base::ZERO,
  180. user_data: pallas::Base::ZERO,
  181. blind: pallas::Base::random(&mut OsRng),
  182. }];
  183. // We can add whatever we want in here, even arbitrary text
  184. // It's up to the auth module to decide what to do with it.
  185. let user_data = pallas::Base::ZERO;
  186. let (propose_tx, propose_params, propose_info) = th.dao_propose(
  187. &Holder::Alice,
  188. &proposal_coinattrs,
  189. user_data,
  190. &dao,
  191. &dao_mint_params.dao_bulla,
  192. current_block_height,
  193. )?;
  194. for holder in &HOLDERS {
  195. info!("[{holder:?}] Executing DAO proposal tx");
  196. th.execute_dao_propose_tx(holder, &propose_tx, &propose_params, current_block_height)
  197. .await?;
  198. }
  199. th.assert_trees(&HOLDERS);
  200. // =====================================
  201. // Dao::Vote
  202. // Proposal is accepted. Start the vote.
  203. // =====================================
  204. info!("Stage 5. Start voting");
  205. info!("[Alice] Building vote tx (yes)");
  206. let (alice_vote_tx, alice_vote_params) = th.dao_vote(
  207. &Holder::Alice,
  208. true,
  209. &dao,
  210. &dao_keypair,
  211. &propose_info,
  212. &propose_params.proposal_bulla,
  213. current_block_height,
  214. )?;
  215. info!("[Bob] Building vote tx (no)");
  216. let (bob_vote_tx, bob_vote_params) = th.dao_vote(
  217. &Holder::Bob,
  218. false,
  219. &dao,
  220. &dao_keypair,
  221. &propose_info,
  222. &propose_params.proposal_bulla,
  223. current_block_height,
  224. )?;
  225. info!("[Charlie] Building vote tx (yes)");
  226. let (charlie_vote_tx, charlie_vote_params) = th.dao_vote(
  227. &Holder::Charlie,
  228. true,
  229. &dao,
  230. &dao_keypair,
  231. &propose_info,
  232. &propose_params.proposal_bulla,
  233. current_block_height,
  234. )?;
  235. for holder in &HOLDERS {
  236. info!("[{holder:?}] Executing Alice vote tx");
  237. th.execute_dao_vote_tx(
  238. holder,
  239. &alice_vote_tx,
  240. &alice_vote_params,
  241. current_block_height,
  242. )
  243. .await?;
  244. info!("[{holder:?}] Executing Bob vote tx");
  245. th.execute_dao_vote_tx(holder, &bob_vote_tx, &bob_vote_params, current_block_height)
  246. .await?;
  247. info!("[{holder:?}] Executing Charlie vote tx");
  248. th.execute_dao_vote_tx(
  249. holder,
  250. &charlie_vote_tx,
  251. &charlie_vote_params,
  252. current_block_height,
  253. )
  254. .await?;
  255. }
  256. // Gather and decrypt all vote notes
  257. let vote_note_1 = alice_vote_params.note.decrypt(&dao_keypair.secret);
  258. let vote_note_2 = bob_vote_params.note.decrypt(&dao_keypair.secret);
  259. let vote_note_3 = charlie_vote_params.note.decrypt(&dao_keypair.secret);
  260. // Count the votes
  261. let mut total_yes_vote_value = 0;
  262. let mut total_all_vote_value = 0;
  263. let mut blind_total_vote = DaoBlindAggregateVote::default();
  264. let mut total_yes_vote_blind = pallas::Scalar::ZERO;
  265. let mut total_all_vote_blind = pallas::Scalar::ZERO;
  266. for (i, (note, params)) in [
  267. (vote_note_1, alice_vote_params),
  268. (vote_note_2, bob_vote_params),
  269. (vote_note_3, charlie_vote_params),
  270. ]
  271. .iter()
  272. .enumerate()
  273. {
  274. // Note format: [
  275. // vote_option,
  276. // yes_vote_blind,
  277. // all_vote_value_fp,
  278. // all_vote_blind,
  279. // ]
  280. let vote_option = fp_to_u64(note[0]).unwrap();
  281. let yes_vote_blind = fp_mod_fv(note[1]);
  282. let all_vote_value = fp_to_u64(note[2]).unwrap();
  283. let all_vote_blind = fp_mod_fv(note[3]);
  284. assert!(vote_option == 0 || vote_option == 1);
  285. total_yes_vote_blind += yes_vote_blind;
  286. total_all_vote_blind += all_vote_blind;
  287. // Update private values
  288. // vote_option is either 0 or 1
  289. let yes_vote_value = vote_option * all_vote_value;
  290. total_yes_vote_value += yes_vote_value;
  291. total_all_vote_value += all_vote_value;
  292. // Update public values
  293. let yes_vote_commit = params.yes_vote_commit;
  294. let all_vote_commit = params.inputs.iter().map(|i| i.vote_commit).sum();
  295. let blind_vote = DaoBlindAggregateVote { yes_vote_commit, all_vote_commit };
  296. blind_total_vote.aggregate(blind_vote);
  297. // Just for the debug
  298. let vote_result = match vote_option != 0 {
  299. true => "yes",
  300. false => "no",
  301. };
  302. info!("Voter {} voted {} with {} tokens", i, vote_result, all_vote_value);
  303. }
  304. info!("Outcome = {} / {}", total_yes_vote_value, total_all_vote_value);
  305. assert!(
  306. blind_total_vote.all_vote_commit ==
  307. pedersen_commitment_u64(total_all_vote_value, total_all_vote_blind)
  308. );
  309. assert!(
  310. blind_total_vote.yes_vote_commit ==
  311. pedersen_commitment_u64(total_yes_vote_value, total_yes_vote_blind)
  312. );
  313. // ================
  314. // Dao::Exec
  315. // Execute the vote
  316. // ================
  317. info!("Stage 6. Execute the vote");
  318. info!("[Dao] Building Dao::Exec tx");
  319. let (exec_tx, xfer_params, exec_params) = th.dao_exec(
  320. &dao,
  321. &dao_mint_params.dao_bulla,
  322. &propose_info,
  323. proposal_coinattrs,
  324. total_yes_vote_value,
  325. total_all_vote_value,
  326. total_yes_vote_blind,
  327. total_all_vote_blind,
  328. )?;
  329. for holder in &HOLDERS {
  330. info!("[{holder:?}] Executing Dao::Exec tx");
  331. th.execute_dao_exec_tx(
  332. holder,
  333. &exec_tx,
  334. &xfer_params,
  335. &exec_params,
  336. current_block_height,
  337. )
  338. .await?;
  339. }
  340. th.assert_trees(&HOLDERS);
  341. // Gather the coins
  342. th.gather_owncoin(&Holder::Rachel, &xfer_params.outputs[0], None)?;
  343. th.gather_owncoin(&Holder::Dao, &xfer_params.outputs[1], None)?;
  344. let rachel_wallet = th.holders.get(&Holder::Rachel).unwrap();
  345. assert!(rachel_wallet.unspent_money_coins[0].note.value == PROPOSAL_AMOUNT);
  346. assert!(rachel_wallet.unspent_money_coins[0].note.token_id == drk_token_id);
  347. // FIXME: The harness doesn't register that we spent the first coin on the proposal.
  348. let dao_wallet = th.holders.get(&Holder::Dao).unwrap();
  349. assert!(dao_wallet.unspent_money_coins[1].note.value == DRK_TOKEN_SUPPLY - PROPOSAL_AMOUNT);
  350. assert!(dao_wallet.unspent_money_coins[1].note.token_id == drk_token_id);
  351. // Stats
  352. th.statistics();
  353. // Thanks for reading
  354. Ok(())
  355. })
  356. }