Skip to content

Commit

Permalink
contract/dao: allow exec call only after voting period has passed
Browse files Browse the repository at this point in the history
  • Loading branch information
skoupidi committed Dec 19, 2024
1 parent f67b175 commit 47b5edc
Show file tree
Hide file tree
Showing 11 changed files with 94 additions and 23 deletions.
7 changes: 7 additions & 0 deletions bin/drk/src/dao.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![];
Expand Down Expand Up @@ -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)?;

Expand Down
16 changes: 8 additions & 8 deletions doc/src/testnet/dao.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/contract/dao/proof/exec.zk
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/contract/dao/proof/vote-main.zk
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 9 additions & 3 deletions src/contract/dao/src/client/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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))),
Expand All @@ -93,19 +96,22 @@ 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())),
];

debug!(target: "contract::dao::client::exec", "proposal_bulla: {:?}", proposal_bulla);
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(),
Expand Down
7 changes: 4 additions & 3 deletions src/contract/dao/src/client/vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))),
Expand All @@ -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())),
Expand Down
5 changes: 5 additions & 0 deletions src/contract/dao/src/entrypoint/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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();
Expand All @@ -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(),
Expand Down
41 changes: 37 additions & 4 deletions src/contract/dao/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -320,13 +322,19 @@ 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,
&proposal_coinattrs,
user_data,
dao,
*current_block_height,
PROPOSAL_DURATION_BLOCKWINDOW,
)
.await?;

Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand Down
9 changes: 9 additions & 0 deletions src/contract/test-harness/src/dao_exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand All @@ -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)?;
Expand Down Expand Up @@ -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(),
Expand All @@ -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)?;

Expand Down
6 changes: 4 additions & 2 deletions src/contract/test-harness/src/dao_propose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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),
Expand Down
4 changes: 2 additions & 2 deletions src/contract/test-harness/src/vks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down

0 comments on commit 47b5edc

Please sign in to comment.