Просмотр исходного кода

contract/dao: allow exec call only after voting period has passed

skoupidi 1 год назад
Родитель
Сommit
47b5edc65e

+ 7 - 0
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)?;
 

+ 8 - 8
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

+ 8 - 0
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);

+ 1 - 1
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);

+ 9 - 3
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(),

+ 4 - 3
src/contract/dao/src/client/vote.rs

@@ -251,8 +251,9 @@ impl<T: StorageAdapter<Value = pallas::Base>> 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<T: StorageAdapter<Value = pallas::Base>> 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())),

+ 5 - 0
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<PublicKey> = 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(),

+ 37 - 4
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

+ 9 - 0
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<MoneyFeeParamsV1>)> {
+        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)?;
 

+ 4 - 2
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<MoneyFeeParamsV1>, 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<MoneyFeeParamsV1>, 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),

+ 2 - 2
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<PathBuf> {