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

This commit is contained in:
skoupidi
2024-12-19 22:32:47 +02:00
parent f67b17508c
commit 47b5edc65e
11 changed files with 94 additions and 23 deletions

View File

@@ -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)?;

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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(),

View File

@@ -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())),

View File

@@ -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(),

View File

@@ -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

View File

@@ -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)?;

View File

@@ -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),

View File

@@ -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> {