From 47b5edc65e045936858c93a373a18c963c05a1ef Mon Sep 17 00:00:00 2001 From: skoupidi Date: Thu, 19 Dec 2024 22:32:47 +0200 Subject: [PATCH] contract/dao: allow exec call only after voting period has passed --- bin/drk/src/dao.rs | 7 ++++ doc/src/testnet/dao.md | 16 ++++---- src/contract/dao/proof/exec.zk | 8 ++++ src/contract/dao/proof/vote-main.zk | 2 +- src/contract/dao/src/client/exec.rs | 12 ++++-- src/contract/dao/src/client/vote.rs | 7 ++-- src/contract/dao/src/entrypoint/exec.rs | 5 +++ src/contract/dao/tests/integration.rs | 41 ++++++++++++++++++-- src/contract/test-harness/src/dao_exec.rs | 9 +++++ src/contract/test-harness/src/dao_propose.rs | 6 ++- src/contract/test-harness/src/vks.rs | 4 +- 11 files changed, 94 insertions(+), 23 deletions(-) diff --git a/bin/drk/src/dao.rs b/bin/drk/src/dao.rs index a83b386ba217..9a6bb680c1ef 100644 --- a/bin/drk/src/dao.rs +++ b/bin/drk/src/dao.rs @@ -2269,6 +2269,12 @@ impl Drk { // Fetch our money Merkle tree let tree = self.get_money_tree().await?; + // Retrieve next block height and current block time target, + // to compute their window. + let next_block_height = self.get_next_block_height().await?; + let block_target = self.get_block_target().await?; + let current_blockwindow = blockwindow(next_block_height, block_target); + // Now we can create the transfer call parameters let input_user_data_blind = Blind::random(&mut OsRng); let mut inputs = vec![]; @@ -2320,6 +2326,7 @@ impl Drk { yes_vote_blind, all_vote_blind, signature_secret: exec_signature_secret, + current_blockwindow, }; let (exec_params, exec_proofs) = exec_builder.make(&dao_exec_zkbin, &dao_exec_pk)?; diff --git a/doc/src/testnet/dao.md b/doc/src/testnet/dao.md index 2c21bbe1d741..bc50e06bf3ed 100644 --- a/doc/src/testnet/dao.md +++ b/doc/src/testnet/dao.md @@ -77,11 +77,11 @@ $ ./drk dao balance MiladyMakerDAO Now that the DAO has something in its treasury, we can generate a transfer proposal to send it somewhere, that will be up to vote -for 30 block periods. Let's propose to send 5 of the 10 tokens to +for 1 block period. Let's propose to send 5 of the 10 tokens to our address (we can find that with `drk wallet --address`): ``` -$ ./drk dao propose-transfer MiladyMakerDAO 30 5 WCKD {YOUR_ADDRESS} +$ ./drk dao propose-transfer MiladyMakerDAO 1 5 WCKD {YOUR_ADDRESS} ``` After command was executed, it will output the generated proposal @@ -146,10 +146,10 @@ current status when running `dao proposal {PROPOSAL_BULLA}`. ## Executing the proposal -Once enough votes have been cast that meet the required minimum (quorum) -and assuming the yes:no votes ratio is bigger than the approval ratio, -then we are ready to confirm the vote. Any DAO member can perform this -action. +Once the block period has passed and enough votes have been cast that +meet the required minimum (quorum), and assuming the yes:no votes ratio +ratio is bigger than the approval ratio, then we are ready to confirm +the vote. Any DAO member can perform this action. Since in our tutorial the `MLDY` governance tokens we used surpass the quorum, we can execute the proposal right away: @@ -203,7 +203,7 @@ from the DAO treasury to the new DAO we created: ``` $ ./drk dao list WickedDAO -$ ./drk dao propose-transfer MiladyMakerDAO 30 6.9 MLDY {WICKED_DAO_PUBLIC_KEY} \ +$ ./drk dao propose-transfer MiladyMakerDAO 1 6.9 MLDY {WICKED_DAO_PUBLIC_KEY} \ {DAO_CONTRACT_SPEND_HOOK} {WICKED_DAO_BULLA} $ ./drk dao proposal {PROPOSAL_BULLA} --mint-proposal > dao_mldy_transfer_proposal_wckd_mint_tx $ ./drk broadcast < dao_mldy_transfer_proposal_wckd_mint_tx @@ -216,7 +216,7 @@ $ ./drk dao vote {PROPOSAL_BULLA} 1 > dao_mldy_transfer_proposal_wckd_vote_tx $ ./drk broadcast < dao_mldy_transfer_proposal_wckd_vote_tx ``` -And execute it: +And execute it, after the vote period(1 block period) has passed: ``` $ ./drk dao exec {PROPOSAL_BULLA} > dao_mldy_transfer_proposal_wckd_exec_tx diff --git a/src/contract/dao/proof/exec.zk b/src/contract/dao/proof/exec.zk index f3e8a7814543..ceb68e47d642 100644 --- a/src/contract/dao/proof/exec.zk +++ b/src/contract/dao/proof/exec.zk @@ -31,6 +31,9 @@ witness "Exec" { Scalar yes_vote_blind, Scalar all_vote_blind, + # Check whether the proposal has expired or not + Base current_blockwindow, + # Signature secret Base signature_secret, } @@ -61,6 +64,11 @@ circuit "Exec" { constrain_instance(proposal_bulla); constrain_instance(proposal_auth_calls_commit); + # Enforce that the proposal has expired + end_time = base_add(proposal_creation_blockwindow, proposal_duration_blockwindows); + less_than_strict(end_time, current_blockwindow); + constrain_instance(current_blockwindow); + # Create Pedersen commitments for win_votes and total_votes, and # constrain the commitments' coordinates. yes_vote_value_c = ec_mul_short(yes_vote_value, VALUE_COMMIT_VALUE); diff --git a/src/contract/dao/proof/vote-main.zk b/src/contract/dao/proof/vote-main.zk index 1948e2b0155e..44a3478af860 100644 --- a/src/contract/dao/proof/vote-main.zk +++ b/src/contract/dao/proof/vote-main.zk @@ -46,7 +46,7 @@ circuit "VoteMain" { token_commit = poseidon_hash(dao_gov_token_id, gov_token_blind); constrain_instance(token_commit); - # cast to EcPoint + # Cast to EcPoint # (otherwise zkas refuses to compile) ONE = witness_base(1); dao_pubkey = ec_mul_var_base(ONE, dao_public_key); diff --git a/src/contract/dao/src/client/exec.rs b/src/contract/dao/src/client/exec.rs index cb7d377bda1e..dfb312bee96c 100644 --- a/src/contract/dao/src/client/exec.rs +++ b/src/contract/dao/src/client/exec.rs @@ -43,6 +43,7 @@ pub struct DaoExecCall { pub yes_vote_blind: ScalarBlind, pub all_vote_blind: ScalarBlind, pub signature_secret: SecretKey, + pub current_blockwindow: u64, } impl DaoExecCall { @@ -77,8 +78,10 @@ impl DaoExecCall { let signature_public = PublicKey::from_secret(self.signature_secret); + let current_blockwindow = pallas::Base::from(self.current_blockwindow); + let prover_witnesses = vec![ - // proposal params + // Proposal params Witness::Base(Value::known(proposal_auth_calls_commit)), Witness::Base(Value::known(pallas::Base::from(self.proposal.creation_blockwindow))), Witness::Base(Value::known(pallas::Base::from(self.proposal.duration_blockwindows))), @@ -93,12 +96,14 @@ impl DaoExecCall { Witness::Base(Value::known(dao_pub_x)), Witness::Base(Value::known(dao_pub_y)), Witness::Base(Value::known(self.dao.bulla_blind.inner())), - // votes + // Votes Witness::Base(Value::known(pallas::Base::from(self.yes_vote_value))), Witness::Base(Value::known(pallas::Base::from(self.all_vote_value))), Witness::Scalar(Value::known(self.yes_vote_blind.inner())), Witness::Scalar(Value::known(self.all_vote_blind.inner())), - // signature secret + // Time checks + Witness::Base(Value::known(current_blockwindow)), + // Signature secret Witness::Base(Value::known(self.signature_secret.inner())), ]; @@ -106,6 +111,7 @@ impl DaoExecCall { let public_inputs = vec![ proposal_bulla.inner(), proposal_auth_calls_commit, + current_blockwindow, *yes_vote_commit_coords.x(), *yes_vote_commit_coords.y(), *all_vote_commit_coords.x(), diff --git a/src/contract/dao/src/client/vote.rs b/src/contract/dao/src/client/vote.rs index 8a94a26459b9..e56612d090b2 100644 --- a/src/contract/dao/src/client/vote.rs +++ b/src/contract/dao/src/client/vote.rs @@ -251,8 +251,9 @@ impl> DaoVoteCall<'_, T> { let (ephem_x, ephem_y) = ephem_pubkey.xy(); let current_blockwindow = pallas::Base::from(self.current_blockwindow); + let prover_witnesses = vec![ - // proposal params + // Proposal params Witness::Base(Value::known(self.proposal.auth_calls.commit())), Witness::Base(Value::known(pallas::Base::from(self.proposal.creation_blockwindow))), Witness::Base(Value::known(pallas::Base::from(self.proposal.duration_blockwindows))), @@ -272,9 +273,9 @@ impl> DaoVoteCall<'_, T> { // Total number of gov tokens allocated Witness::Base(Value::known(all_vote_value_fp)), Witness::Base(Value::known(all_vote_blind.inner())), - // gov token + // Gov token Witness::Base(Value::known(gov_token_blind)), - // time checks + // Time checks Witness::Base(Value::known(current_blockwindow)), // verifiable encryption Witness::Base(Value::known(ephem_secret.inner())), diff --git a/src/contract/dao/src/entrypoint/exec.rs b/src/contract/dao/src/entrypoint/exec.rs index 6083113ed897..0ceefa46d1a7 100644 --- a/src/contract/dao/src/entrypoint/exec.rs +++ b/src/contract/dao/src/entrypoint/exec.rs @@ -27,6 +27,7 @@ use darkfi_sdk::{ use darkfi_serial::{deserialize, serialize, Encodable, WriteExt}; use crate::{ + blockwindow, error::DaoError, model::{DaoExecParams, DaoExecUpdate, DaoProposalMetadata, VecAuthCallCommit}, DaoFunction, DAO_CONTRACT_DB_PROPOSAL_BULLAS, DAO_CONTRACT_ZKAS_DAO_EXEC_NS, @@ -46,6 +47,9 @@ pub(crate) fn dao_exec_get_metadata( // Public keys for the transaction signatures we have to verify let signature_pubkeys: Vec = vec![params.signature_public]; + let current_blockwindow = + blockwindow(wasm::util::get_verifying_block_height()?, wasm::util::get_block_target()?); + let blind_vote = params.blind_total_vote; let yes_vote_coords = blind_vote.yes_vote_commit.to_affine().coordinates().unwrap(); let all_vote_coords = blind_vote.all_vote_commit.to_affine().coordinates().unwrap(); @@ -55,6 +59,7 @@ pub(crate) fn dao_exec_get_metadata( vec![ params.proposal_bulla.inner(), params.proposal_auth_calls.commit(), + pallas::Base::from(current_blockwindow), *yes_vote_coords.x(), *yes_vote_coords.y(), *all_vote_coords.x(), diff --git a/src/contract/dao/tests/integration.rs b/src/contract/dao/tests/integration.rs index ae569b9d286c..9613d8681af9 100644 --- a/src/contract/dao/tests/integration.rs +++ b/src/contract/dao/tests/integration.rs @@ -19,6 +19,7 @@ use darkfi::Result; use darkfi_contract_test_harness::{init_logger, Holder, TestHarness}; use darkfi_dao_contract::{ + blockwindow, model::{Dao, DaoBlindAggregateVote, DaoVoteParams}, DaoFunction, }; @@ -57,6 +58,7 @@ const PROPOSER_LIMIT: u64 = 100_000_000; const QUORUM: u64 = 200_000_000; const APPROVAL_RATIO_BASE: u64 = 2; const APPROVAL_RATIO_QUOT: u64 = 1; +const PROPOSAL_DURATION_BLOCKWINDOW: u64 = 1; // The tokens we want to send via the transfer proposal const TRANSFER_PROPOSAL_AMOUNT: u64 = 250_000_000; @@ -320,6 +322,11 @@ async fn execute_transfer_proposal( blind: Blind::random(&mut OsRng), }]; + // Grab creation blockwindow + let block_target = + th.holders.get_mut(&Holder::Dao).unwrap().validator.consensus.module.read().await.target; + let creation_blockwindow = blockwindow(*current_block_height, block_target); + let (tx, params, fee_params, proposal_info) = th .dao_propose_transfer( &Holder::Alice, @@ -327,6 +334,7 @@ async fn execute_transfer_proposal( user_data, dao, *current_block_height, + PROPOSAL_DURATION_BLOCKWINDOW, ) .await?; @@ -396,7 +404,6 @@ async fn execute_transfer_proposal( .await?; } th.assert_trees(&HOLDERS); - *current_block_height += 1; // Gather and decrypt all generic vote notes let vote_note_1 = alice_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap(); @@ -411,6 +418,13 @@ async fn execute_transfer_proposal( (vote_note_3, charlie_vote_params), ]); + // Wait until proposal has expired + let mut current_blockwindow = creation_blockwindow; + while current_blockwindow <= creation_blockwindow + PROPOSAL_DURATION_BLOCKWINDOW + 1 { + *current_block_height += 1; + current_blockwindow = blockwindow(*current_block_height, block_target); + } + // ================ // Dao::Exec // Execute the vote @@ -472,8 +486,21 @@ async fn execute_generic_proposal( // Propose the vote // ================ info!("[Alice] Building DAO generic proposal tx"); - let (tx, params, fee_params, proposal_info) = - th.dao_propose_generic(&Holder::Alice, user_data, dao, *current_block_height).await?; + + // Grab creation blockwindow + let block_target = + th.holders.get_mut(&Holder::Dao).unwrap().validator.consensus.module.read().await.target; + let creation_blockwindow = blockwindow(*current_block_height, block_target); + + let (tx, params, fee_params, proposal_info) = th + .dao_propose_generic( + &Holder::Alice, + user_data, + dao, + *current_block_height, + PROPOSAL_DURATION_BLOCKWINDOW, + ) + .await?; for holder in &HOLDERS { info!("[{holder:?}] Executing DAO generic proposal tx"); @@ -541,7 +568,6 @@ async fn execute_generic_proposal( .await?; } th.assert_trees(&HOLDERS); - *current_block_height += 1; // Gather and decrypt all generic vote notes let vote_note_1 = alice_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap(); @@ -556,6 +582,13 @@ async fn execute_generic_proposal( (vote_note_3, charlie_vote_params), ]); + // Wait until proposal has expired + let mut current_blockwindow = creation_blockwindow; + while current_blockwindow <= creation_blockwindow + PROPOSAL_DURATION_BLOCKWINDOW + 1 { + *current_block_height += 1; + current_blockwindow = blockwindow(*current_block_height, block_target); + } + // ================ // Dao::Exec // Execute the vote diff --git a/src/contract/test-harness/src/dao_exec.rs b/src/contract/test-harness/src/dao_exec.rs index 92ef9f52347e..9bbed8956c50 100644 --- a/src/contract/test-harness/src/dao_exec.rs +++ b/src/contract/test-harness/src/dao_exec.rs @@ -21,6 +21,7 @@ use darkfi::{ Result, }; use darkfi_dao_contract::{ + blockwindow, client::{DaoAuthMoneyTransferCall, DaoExecCall}, model::{Dao, DaoProposal}, DaoFunction, DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS, @@ -146,6 +147,8 @@ impl TestHarness { xfer_params.inputs.iter().map(|input| input.value_commit).sum() ); + let block_target = dao_wallet.validator.consensus.module.read().await.target; + let current_blockwindow = blockwindow(block_height, block_target); let exec_builder = DaoExecCall { proposal: proposal.clone(), dao: dao.clone(), @@ -154,6 +157,7 @@ impl TestHarness { yes_vote_blind, all_vote_blind, signature_secret: exec_signature_secret, + current_blockwindow, }; let (exec_params, exec_proofs) = exec_builder.make(dao_exec_zkbin, dao_exec_pk)?; @@ -251,11 +255,15 @@ impl TestHarness { all_vote_blind: ScalarBlind, block_height: u32, ) -> Result<(Transaction, Option)> { + let wallet = self.holders.get_mut(holder).unwrap(); + let (dao_exec_pk, dao_exec_zkbin) = self.proving_keys.get(DAO_CONTRACT_ZKAS_DAO_EXEC_NS).unwrap(); // Create the exec call let exec_signature_secret = SecretKey::random(&mut OsRng); + let block_target = wallet.validator.consensus.module.read().await.target; + let current_blockwindow = blockwindow(block_height, block_target); let exec_builder = DaoExecCall { proposal: proposal.clone(), dao: dao.clone(), @@ -264,6 +272,7 @@ impl TestHarness { yes_vote_blind, all_vote_blind, signature_secret: exec_signature_secret, + current_blockwindow, }; let (exec_params, exec_proofs) = exec_builder.make(dao_exec_zkbin, dao_exec_pk)?; diff --git a/src/contract/test-harness/src/dao_propose.rs b/src/contract/test-harness/src/dao_propose.rs index 745f4a89461c..12761d5b2948 100644 --- a/src/contract/test-harness/src/dao_propose.rs +++ b/src/contract/test-harness/src/dao_propose.rs @@ -54,6 +54,7 @@ impl TestHarness { user_data: pallas::Base, dao: &Dao, block_height: u32, + duration_blockwindows: u64, ) -> Result<(Transaction, DaoProposeParams, Option, DaoProposal)> { let wallet = self.holders.get(proposer).unwrap(); @@ -121,7 +122,7 @@ impl TestHarness { let proposal = DaoProposal { auth_calls, creation_blockwindow, - duration_blockwindows: 30, + duration_blockwindows, user_data, dao_bulla: dao.to_bulla(), blind: Blind::random(&mut OsRng), @@ -193,6 +194,7 @@ impl TestHarness { user_data: pallas::Base, dao: &Dao, block_height: u32, + duration_blockwindows: u64, ) -> Result<(Transaction, DaoProposeParams, Option, DaoProposal)> { let wallet = self.holders.get(proposer).unwrap(); @@ -238,7 +240,7 @@ impl TestHarness { let proposal = DaoProposal { auth_calls: vec![], creation_blockwindow, - duration_blockwindows: 30, + duration_blockwindows, user_data, dao_bulla: dao.to_bulla(), blind: Blind::random(&mut OsRng), diff --git a/src/contract/test-harness/src/vks.rs b/src/contract/test-harness/src/vks.rs index 7ed616da8bfe..8ea05811df38 100644 --- a/src/contract/test-harness/src/vks.rs +++ b/src/contract/test-harness/src/vks.rs @@ -49,8 +49,8 @@ use sled_overlay::sled; /// Update these if any circuits are changed. /// Delete the existing cachefiles, and enable debug logging, you will see the new hashes. -const PKS_HASH: &str = "e8de97d286a4a31606f96dfd13bb5a6e9dfa49322573b8cd1fe936aee7e33e58"; -const VKS_HASH: &str = "aa59b5e53c10c994c127beb443d6b1b4c21ee7417ce1a4f717c82431b7b8c8d9"; +const PKS_HASH: &str = "55d9535b390a819026bb3554f5de501997b831935bbc910951639fddfec44b14"; +const VKS_HASH: &str = "cbcb356ccacd0ad4ac953417f07bf24297927e61ba770f5aeddaa9e72f159272"; /// Build a `PathBuf` to a cachefile fn cache_path(typ: &str) -> Result {