|
|
@@ -16,702 +16,385 @@
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
*/
|
|
|
|
|
|
-use std::time::{Duration, Instant};
|
|
|
-
|
|
|
-use darkfi::{tx::Transaction, Result};
|
|
|
+use darkfi::Result;
|
|
|
+use darkfi_contract_test_harness::{init_logger, Holder, TestHarness};
|
|
|
+use darkfi_dao_contract::{
|
|
|
+ client::{DaoInfo, DaoVoteNote},
|
|
|
+ model::DaoBlindAggregateVote,
|
|
|
+};
|
|
|
use darkfi_sdk::{
|
|
|
- crypto::{
|
|
|
- pasta_prelude::*, pedersen_commitment_u64, poseidon_hash, Keypair, MerkleNode, MerkleTree,
|
|
|
- SecretKey, TokenId, DAO_CONTRACT_ID, DARK_TOKEN_ID, MONEY_CONTRACT_ID,
|
|
|
- },
|
|
|
+ crypto::{pasta_prelude::Field, pedersen_commitment_u64, DAO_CONTRACT_ID, DARK_TOKEN_ID},
|
|
|
pasta::pallas,
|
|
|
- ContractCall,
|
|
|
};
|
|
|
-use darkfi_serial::{Decodable, Encodable};
|
|
|
-use log::debug;
|
|
|
+use log::info;
|
|
|
use rand::rngs::OsRng;
|
|
|
|
|
|
-use darkfi_dao_contract::{client, model, money_client, wallet_cache::WalletCache, DaoFunction};
|
|
|
-
|
|
|
-use darkfi_money_contract::{
|
|
|
- client::token_mint_v1::TokenMintCallBuilder,
|
|
|
- model::{Coin, MoneyTokenMintParamsV1, MoneyTransferParamsV1},
|
|
|
- MoneyFunction,
|
|
|
-};
|
|
|
-
|
|
|
-mod harness;
|
|
|
-use harness::{init_logger, DaoTestHarness};
|
|
|
-
|
|
|
-// TODO: Anonymity leaks in this proof of concept:
|
|
|
-//
|
|
|
-// * Vote updates are linked to the proposal_bulla
|
|
|
-// * Nullifier of vote will link vote with the coin when it's spent
|
|
|
-
|
|
|
-// TODO: strategize and cleanup Result/Error usage
|
|
|
-// TODO: fix up code doc
|
|
|
-// TODO: db_* errors returned from runtime should be more specific.
|
|
|
-// TODO: db_* functions should be consistently ordered
|
|
|
-// TODO: migrate rest of func calls below to make() format and cleanup
|
|
|
-// TODO: Migrate to test-harness
|
|
|
-
|
|
|
#[async_std::test]
|
|
|
async fn integration_test() -> Result<()> {
|
|
|
- init_logger()?;
|
|
|
-
|
|
|
- // Some benchmark averages
|
|
|
- let mut mint_verify_times = vec![];
|
|
|
- let mut propose_verify_times = vec![];
|
|
|
- let mut vote_verify_times = vec![];
|
|
|
- let mut exec_verify_times = vec![];
|
|
|
+ init_logger();
|
|
|
+
|
|
|
+ // Holders this test will use:
|
|
|
+ // * Faucet airdrops DRK
|
|
|
+ // * Alice, Bob, and Charlie are members of the DAO.
|
|
|
+ // * Rachel is the proposal recipient.
|
|
|
+ // * Dao is the DAO wallet
|
|
|
+ const HOLDERS: [Holder; 6] =
|
|
|
+ [Holder::Faucet, Holder::Alice, Holder::Bob, Holder::Charlie, Holder::Rachel, Holder::Dao];
|
|
|
+
|
|
|
+ // Initialize harness
|
|
|
+ let mut th = TestHarness::new(&["money".to_string(), "dao".to_string()]).await?;
|
|
|
+
|
|
|
+ // We'll use the ALICE token as the DAO governance token
|
|
|
+ let gov_token_id = th.token_id(&Holder::Alice);
|
|
|
+ const ALICE_GOV_SUPPLY: u64 = 100_000_000;
|
|
|
+ const BOB_GOV_SUPPLY: u64 = 100_000_000;
|
|
|
+ const CHARLIE_GOV_SUPPLY: u64 = 100_000_000;
|
|
|
+ // And the DRK token as the treasury token
|
|
|
+ let drk_token_id = *DARK_TOKEN_ID;
|
|
|
+ const DRK_TOKEN_SUPPLY: u64 = 1_000_000_000;
|
|
|
+ // The tokens we want to send via the proposal
|
|
|
+ const PROPOSAL_AMOUNT: u64 = 250_000_000;
|
|
|
|
|
|
// Slot to verify against
|
|
|
let current_slot = 0;
|
|
|
|
|
|
- let dao_th = DaoTestHarness::new().await?;
|
|
|
-
|
|
|
- // Money parameters
|
|
|
- let xdrk_supply = 1_000_000;
|
|
|
- let xdrk_token_id = *DARK_TOKEN_ID;
|
|
|
-
|
|
|
- // Governance token parameters
|
|
|
- let gdrk_mint_auth = Keypair::random(&mut OsRng);
|
|
|
- let gdrk_supply = 1_000_000;
|
|
|
- let gdrk_token_id = TokenId::derive(gdrk_mint_auth.secret);
|
|
|
-
|
|
|
// DAO parameters
|
|
|
- let dao = client::DaoInfo {
|
|
|
- proposer_limit: 110,
|
|
|
- quorum: 110,
|
|
|
+ let dao_keypair = th.holders.get(&Holder::Dao).unwrap().keypair;
|
|
|
+ let dao = DaoInfo {
|
|
|
+ proposer_limit: 100_000_000,
|
|
|
+ quorum: 199_999_999,
|
|
|
approval_ratio_base: 2,
|
|
|
approval_ratio_quot: 1,
|
|
|
- gov_token_id: gdrk_token_id,
|
|
|
- public_key: dao_th.dao_kp.public,
|
|
|
+ gov_token_id,
|
|
|
+ public_key: dao_keypair.public,
|
|
|
bulla_blind: pallas::Base::random(&mut OsRng),
|
|
|
};
|
|
|
|
|
|
- // We use this to receive coins
|
|
|
- let mut cache = WalletCache::new();
|
|
|
-
|
|
|
- // =======================================================
|
|
|
+ // ====================
|
|
|
// Dao::Mint
|
|
|
- //
|
|
|
// Create the DAO bulla
|
|
|
- // =======================================================
|
|
|
- debug!(target: "dao", "Stage 1. Creating DAO bulla");
|
|
|
-
|
|
|
- let (params, proofs) = client::make_mint_call(
|
|
|
- &dao,
|
|
|
- &dao_th.dao_kp.secret,
|
|
|
- &dao_th.dao_mint_zkbin,
|
|
|
- &dao_th.dao_mint_pk,
|
|
|
- )?;
|
|
|
+ // ====================
|
|
|
+ info!("Stage 1. Creating DAO bulla");
|
|
|
|
|
|
- let mut data = vec![DaoFunction::Mint as u8];
|
|
|
- params.encode(&mut data)?;
|
|
|
- let calls = vec![ContractCall { contract_id: dao_th.dao_contract_id, data }];
|
|
|
- let proofs = vec![proofs];
|
|
|
- let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
|
|
- let sigs = tx.create_sigs(&mut OsRng, &[dao_th.dao_kp.secret])?;
|
|
|
- tx.signatures = vec![sigs];
|
|
|
-
|
|
|
- let timer = Instant::now();
|
|
|
- dao_th.alice_validator.read().await.add_transactions(&[tx.clone()], current_slot, true).await?;
|
|
|
- mint_verify_times.push(timer.elapsed());
|
|
|
- // TODO: Witness and add to wallet merkle tree?
|
|
|
-
|
|
|
- let mut dao_tree = MerkleTree::new(100);
|
|
|
- let dao_leaf_position = {
|
|
|
- let node = MerkleNode::from(params.dao_bulla.inner());
|
|
|
- dao_tree.append(node);
|
|
|
- dao_tree.mark().unwrap()
|
|
|
- };
|
|
|
- let dao_bulla = params.dao_bulla;
|
|
|
- debug!(target: "dao", "Created DAO bulla: {:?}", dao_bulla.inner());
|
|
|
-
|
|
|
- // =======================================================
|
|
|
- // Money::Transfer
|
|
|
- //
|
|
|
- // Mint the initial supply of treasury token
|
|
|
- // and send it all to the DAO directly
|
|
|
- // =======================================================
|
|
|
- debug!(target: "dao", "Stage 2. Minting treasury token");
|
|
|
-
|
|
|
- cache.track(dao_th.dao_kp.secret);
|
|
|
-
|
|
|
- // Address of deployed contract in our example is dao::exec::FUNC_ID
|
|
|
- // This field is public, you can see it's being sent to a DAO
|
|
|
- // but nothing else is visible.
|
|
|
- //
|
|
|
- // In the python code we wrote:
|
|
|
- //
|
|
|
- // spend_hook = b"0xdao_ruleset"
|
|
|
- //
|
|
|
- // TODO: this should be the contract/func ID
|
|
|
- let spend_hook = DAO_CONTRACT_ID.inner();
|
|
|
- // The user_data can be a simple hash of the items passed into the ZK proof
|
|
|
- // up to corresponding linked ZK proof to interpret however they need.
|
|
|
- // In out case, it's the bulla for the DAO
|
|
|
- let user_data = dao_bulla.inner();
|
|
|
-
|
|
|
- let call = money_client::TransferCall {
|
|
|
- clear_inputs: vec![money_client::TransferClearInput {
|
|
|
- value: xdrk_supply,
|
|
|
- token_id: xdrk_token_id,
|
|
|
- signature_secret: dao_th.faucet_kp.secret,
|
|
|
- }],
|
|
|
- inputs: vec![],
|
|
|
- outputs: vec![money_client::TransferOutput {
|
|
|
- value: xdrk_supply,
|
|
|
- token_id: xdrk_token_id,
|
|
|
- public: dao_th.dao_kp.public,
|
|
|
- serial: pallas::Base::random(&mut OsRng),
|
|
|
- spend_hook,
|
|
|
- user_data,
|
|
|
- }],
|
|
|
- };
|
|
|
- let (params, proofs) = call.make(
|
|
|
- &dao_th.money_mint_zkbin,
|
|
|
- &dao_th.money_mint_pk,
|
|
|
- &dao_th.money_burn_zkbin,
|
|
|
- &dao_th.money_burn_pk,
|
|
|
+ info!("[Dao] Building DAO mint tx");
|
|
|
+ let (dao_mint_tx, dao_mint_params) = th.dao_mint(&dao, &dao_keypair)?;
|
|
|
+
|
|
|
+ info!("[Faucet] Executing DAO Mint tx");
|
|
|
+ th.execute_dao_mint_tx(Holder::Faucet, &dao_mint_tx, &dao_mint_params, current_slot).await?;
|
|
|
+
|
|
|
+ info!("[Alice] Executing DAO Mint tx");
|
|
|
+ th.execute_dao_mint_tx(Holder::Alice, &dao_mint_tx, &dao_mint_params, current_slot).await?;
|
|
|
+
|
|
|
+ info!("[Bob] Executing DAO Mint tx");
|
|
|
+ th.execute_dao_mint_tx(Holder::Bob, &dao_mint_tx, &dao_mint_params, current_slot).await?;
|
|
|
+
|
|
|
+ info!("[Charlie] Executing DAO Mint tx");
|
|
|
+ th.execute_dao_mint_tx(Holder::Charlie, &dao_mint_tx, &dao_mint_params, current_slot).await?;
|
|
|
+
|
|
|
+ info!("[Rachel] Executing DAO Mint tx");
|
|
|
+ th.execute_dao_mint_tx(Holder::Rachel, &dao_mint_tx, &dao_mint_params, current_slot).await?;
|
|
|
+
|
|
|
+ info!("[Dao] Executing DAO Mint tx");
|
|
|
+ th.execute_dao_mint_tx(Holder::Dao, &dao_mint_tx, &dao_mint_params, current_slot).await?;
|
|
|
+
|
|
|
+ // TODO: assert_trees
|
|
|
+
|
|
|
+ // =======================================
|
|
|
+ // Airdrop some treasury tokens to the DAO
|
|
|
+ // =======================================
|
|
|
+ info!("Stage 2. Send Treasury token");
|
|
|
+
|
|
|
+ info!("[Faucet] Building DAO airdrop tx");
|
|
|
+ let (airdrop_tx, airdrop_params) = th.airdrop_native(
|
|
|
+ DRK_TOKEN_SUPPLY,
|
|
|
+ Holder::Dao,
|
|
|
+ Some(DAO_CONTRACT_ID.inner()), // spend_hook
|
|
|
+ Some(dao_mint_params.dao_bulla.inner()), // user_data
|
|
|
+ None,
|
|
|
+ None,
|
|
|
)?;
|
|
|
|
|
|
- let contract_id = *MONEY_CONTRACT_ID;
|
|
|
+ info!("[Faucet] Executing DAO airdrop tx");
|
|
|
+ th.execute_airdrop_native_tx(Holder::Faucet, &airdrop_tx, &airdrop_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let mut data = vec![MoneyFunction::TransferV1 as u8];
|
|
|
- params.encode(&mut data)?;
|
|
|
- let calls = vec![ContractCall { contract_id, data }];
|
|
|
- let proofs = vec![proofs];
|
|
|
- let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
|
|
- let sigs = tx.create_sigs(&mut OsRng, &vec![dao_th.faucet_kp.secret])?;
|
|
|
- tx.signatures = vec![sigs];
|
|
|
+ info!("[Alice] Executing DAO airdrop tx");
|
|
|
+ th.execute_airdrop_native_tx(Holder::Alice, &airdrop_tx, &airdrop_params, current_slot).await?;
|
|
|
|
|
|
- dao_th.alice_validator.read().await.add_transactions(&[tx.clone()], current_slot, true).await?;
|
|
|
+ info!("[Bob] Executing DAO airdrop tx");
|
|
|
+ th.execute_airdrop_native_tx(Holder::Bob, &airdrop_tx, &airdrop_params, current_slot).await?;
|
|
|
|
|
|
- // Wallet stuff
|
|
|
+ info!("[Charlie] Executing DAO airdrop tx");
|
|
|
+ th.execute_airdrop_native_tx(Holder::Charlie, &airdrop_tx, &airdrop_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- // DAO reads the money received from the encrypted note
|
|
|
- {
|
|
|
- assert_eq!(tx.calls.len(), 1);
|
|
|
- let calldata = &tx.calls[0].data;
|
|
|
- let params_data = &calldata[1..];
|
|
|
- let params: MoneyTransferParamsV1 = Decodable::decode(params_data)?;
|
|
|
+ info!("[Rachel] Executing DAO airdrop tx");
|
|
|
+ th.execute_airdrop_native_tx(Holder::Rachel, &airdrop_tx, &airdrop_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- for output in params.outputs {
|
|
|
- let coin = Coin::from(output.coin);
|
|
|
- cache.try_decrypt_note(coin, &output.note);
|
|
|
- }
|
|
|
- }
|
|
|
+ info!("[Dao] Executing DAO airdrop tx");
|
|
|
+ th.execute_airdrop_native_tx(Holder::Dao, &airdrop_tx, &airdrop_params, current_slot).await?;
|
|
|
|
|
|
- let mut recv_coins = cache.get_received(&dao_th.dao_kp.secret);
|
|
|
- assert_eq!(recv_coins.len(), 1);
|
|
|
- let dao_recv_coin = recv_coins.pop().unwrap();
|
|
|
- let treasury_note = dao_recv_coin.note;
|
|
|
-
|
|
|
- // Check the actual coin received is valid before accepting it
|
|
|
-
|
|
|
- let coords = dao_th.dao_kp.public.inner().to_affine().coordinates().unwrap();
|
|
|
- let coin = poseidon_hash::<7>([
|
|
|
- *coords.x(),
|
|
|
- *coords.y(),
|
|
|
- pallas::Base::from(treasury_note.value),
|
|
|
- treasury_note.token_id.inner(),
|
|
|
- treasury_note.serial,
|
|
|
- treasury_note.spend_hook,
|
|
|
- treasury_note.user_data,
|
|
|
- ]);
|
|
|
- assert_eq!(coin, dao_recv_coin.coin.inner());
|
|
|
-
|
|
|
- assert_eq!(treasury_note.spend_hook, spend_hook);
|
|
|
- assert_eq!(treasury_note.user_data, dao_bulla.inner());
|
|
|
-
|
|
|
- debug!(target: "dao", "DAO received a coin worth {} xDRK", treasury_note.value);
|
|
|
-
|
|
|
- // =======================================================
|
|
|
- // Money::Transfer
|
|
|
- //
|
|
|
- // Mint the governance token
|
|
|
- // Send it to three hodlers
|
|
|
- // =======================================================
|
|
|
- debug!(target: "dao", "Stage 3. Minting governance token");
|
|
|
-
|
|
|
- cache.track(dao_th.alice_kp.secret);
|
|
|
- cache.track(dao_th.bob_kp.secret);
|
|
|
- cache.track(dao_th.charlie_kp.secret);
|
|
|
-
|
|
|
- // TODO: Clean this whole test up
|
|
|
- let token_mint_zkbin = include_bytes!("../../money/proof/token_mint_v1.zk.bin");
|
|
|
- let token_mint_zkbin = darkfi::zkas::ZkBinary::decode(token_mint_zkbin)?;
|
|
|
- let token_mint_empty_wit = darkfi::zk::empty_witnesses(&token_mint_zkbin);
|
|
|
- let token_mint_circuit =
|
|
|
- darkfi::zk::ZkCircuit::new(token_mint_empty_wit, token_mint_zkbin.clone());
|
|
|
- let token_mint_pk = darkfi::zk::ProvingKey::build(13, &token_mint_circuit);
|
|
|
-
|
|
|
- // Spend hook and user data disabled
|
|
|
- let spend_hook = pallas::Base::from(0);
|
|
|
- let user_data = pallas::Base::from(0);
|
|
|
-
|
|
|
- let mut builder = TokenMintCallBuilder {
|
|
|
- mint_authority: gdrk_mint_auth,
|
|
|
- recipient: dao_th.alice_kp.public,
|
|
|
- amount: 400000,
|
|
|
- spend_hook,
|
|
|
- user_data,
|
|
|
- token_mint_zkbin,
|
|
|
- token_mint_pk,
|
|
|
- };
|
|
|
- let debris1 = builder.build()?;
|
|
|
-
|
|
|
- builder.recipient = dao_th.bob_kp.public;
|
|
|
- let debris2 = builder.build()?;
|
|
|
-
|
|
|
- builder.amount = 200000;
|
|
|
- builder.recipient = dao_th.charlie_kp.public;
|
|
|
- let debris3 = builder.build()?;
|
|
|
-
|
|
|
- assert!(2 * 400000 + 200000 == gdrk_supply);
|
|
|
-
|
|
|
- // This should actually be 3 calls in a single tx, but w/e.
|
|
|
- let mut data = vec![MoneyFunction::TokenMintV1 as u8];
|
|
|
- debris1.params.encode(&mut data)?;
|
|
|
- let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }];
|
|
|
- let proofs = vec![debris1.proofs];
|
|
|
- let mut tx1 = Transaction { calls, proofs, signatures: vec![] };
|
|
|
- let sigs = tx1.create_sigs(&mut OsRng, &[gdrk_mint_auth.secret])?;
|
|
|
- tx1.signatures = vec![sigs];
|
|
|
-
|
|
|
- let mut data = vec![MoneyFunction::TokenMintV1 as u8];
|
|
|
- debris2.params.encode(&mut data)?;
|
|
|
- let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }];
|
|
|
- let proofs = vec![debris2.proofs];
|
|
|
- let mut tx2 = Transaction { calls, proofs, signatures: vec![] };
|
|
|
- let sigs = tx2.create_sigs(&mut OsRng, &[gdrk_mint_auth.secret])?;
|
|
|
- tx2.signatures = vec![sigs];
|
|
|
-
|
|
|
- let mut data = vec![MoneyFunction::TokenMintV1 as u8];
|
|
|
- debris3.params.encode(&mut data)?;
|
|
|
- let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }];
|
|
|
- let proofs = vec![debris3.proofs];
|
|
|
- let mut tx3 = Transaction { calls, proofs, signatures: vec![] };
|
|
|
- let sigs = tx3.create_sigs(&mut OsRng, &[gdrk_mint_auth.secret])?;
|
|
|
- tx3.signatures = vec![sigs];
|
|
|
-
|
|
|
- dao_th
|
|
|
- .alice_validator
|
|
|
- .read()
|
|
|
- .await
|
|
|
- .add_transactions(&[tx1.clone(), tx2.clone(), tx3.clone()], current_slot, true)
|
|
|
- .await?;
|
|
|
-
|
|
|
- // Wallet
|
|
|
- {
|
|
|
- for tx in [tx1, tx2, tx3] {
|
|
|
- assert_eq!(tx.calls.len(), 1);
|
|
|
- let calldata = &tx.calls[0].data;
|
|
|
- let params_data = &calldata[1..];
|
|
|
- let params: MoneyTokenMintParamsV1 = Decodable::decode(params_data)?;
|
|
|
- cache.try_decrypt_note(params.output.coin, ¶ms.output.note);
|
|
|
- }
|
|
|
- }
|
|
|
+ th.assert_trees(&HOLDERS);
|
|
|
|
|
|
- let gov_keypairs = vec![dao_th.alice_kp, dao_th.bob_kp, dao_th.charlie_kp];
|
|
|
- let mut gov_recv = vec![None, None, None];
|
|
|
- // Check that each person received one coin
|
|
|
- for (i, key) in gov_keypairs.iter().enumerate() {
|
|
|
- let gov_recv_coin = {
|
|
|
- let mut recv_coins = cache.get_received(&key.secret);
|
|
|
- assert_eq!(recv_coins.len(), 1);
|
|
|
- let recv_coin = recv_coins.pop().unwrap();
|
|
|
- let note = &recv_coin.note;
|
|
|
-
|
|
|
- assert_eq!(note.token_id, gdrk_token_id);
|
|
|
- // Normal payment
|
|
|
- assert_eq!(note.spend_hook, pallas::Base::from(0));
|
|
|
- assert_eq!(note.user_data, pallas::Base::from(0));
|
|
|
-
|
|
|
- let (pub_x, pub_y) = key.public.xy();
|
|
|
- let coin = poseidon_hash::<7>([
|
|
|
- pub_x,
|
|
|
- pub_y,
|
|
|
- pallas::Base::from(note.value),
|
|
|
- note.token_id.inner(),
|
|
|
- note.serial,
|
|
|
- note.spend_hook,
|
|
|
- note.user_data,
|
|
|
- ]);
|
|
|
- assert_eq!(coin, recv_coin.coin.inner());
|
|
|
-
|
|
|
- debug!(target: "dao", "Holder{} received a coin worth {} gDRK", i, note.value);
|
|
|
-
|
|
|
- recv_coin
|
|
|
- };
|
|
|
- gov_recv[i] = Some(gov_recv_coin);
|
|
|
- }
|
|
|
- // unwrap them for this demo
|
|
|
- let gov_recv: Vec<_> = gov_recv.into_iter().map(|r| r.unwrap()).collect();
|
|
|
+ // Gather the DAO owncoin
|
|
|
+ th.gather_owncoin(Holder::Dao, airdrop_params.outputs[0].clone(), None)?;
|
|
|
|
|
|
- // =======================================================
|
|
|
- // Dao::Propose
|
|
|
- //
|
|
|
- // Propose the vote
|
|
|
- // In order to make a valid vote, first the proposer must
|
|
|
- // meet a criteria for a minimum number of gov tokens
|
|
|
- //
|
|
|
- // DAO rules:
|
|
|
- // 1. gov token IDs must match on all inputs
|
|
|
- // 2. proposals must be submitted by minimum amount
|
|
|
- // 3. all votes >= quorum
|
|
|
- // 4. outcome > approval_ratio
|
|
|
- // 5. structure of outputs
|
|
|
- // output 0: value and address
|
|
|
- // output 1: change address
|
|
|
- // =======================================================
|
|
|
- debug!(target: "dao", "Stage 4. Propose the vote");
|
|
|
+ // ======================================
|
|
|
+ // Mint the governance token to 3 holders
|
|
|
+ // ======================================
|
|
|
+ info!("Stage 3. Minting governance token");
|
|
|
|
|
|
- // TODO: look into proposal expiry once time for voting has finished
|
|
|
+ info!("[Alice] Building governance token mint tx for Alice");
|
|
|
+ let (a_token_mint_tx, a_token_mint_params) =
|
|
|
+ th.token_mint(ALICE_GOV_SUPPLY, Holder::Alice, Holder::Alice, None, None)?;
|
|
|
|
|
|
- let receiver_keypair = Keypair::random(&mut OsRng);
|
|
|
+ info!("[Faucet] Executing governance token mint tx for Alice");
|
|
|
+ th.execute_token_mint_tx(Holder::Faucet, &a_token_mint_tx, &a_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let (money_leaf_position, money_merkle_path) = {
|
|
|
- let tree = &cache.tree;
|
|
|
- let leaf_position = gov_recv[0].leaf_position;
|
|
|
- let merkle_path = tree.witness(leaf_position, 0).unwrap();
|
|
|
- (leaf_position, merkle_path)
|
|
|
- };
|
|
|
+ info!("[Alice] Executing governance token mint tx for Alice");
|
|
|
+ th.execute_token_mint_tx(Holder::Alice, &a_token_mint_tx, &a_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- // TODO: is it possible for an invalid transfer() to be constructed on exec()?
|
|
|
- // need to look into this
|
|
|
- let signature_secret = SecretKey::random(&mut OsRng);
|
|
|
- let input = client::DaoProposeStakeInput {
|
|
|
- secret: dao_th.alice_kp.secret,
|
|
|
- note: gov_recv[0].note.clone(),
|
|
|
- leaf_position: money_leaf_position,
|
|
|
- merkle_path: money_merkle_path,
|
|
|
- signature_secret,
|
|
|
- };
|
|
|
+ info!("[Bob] Executing governance token mint tx for Alice");
|
|
|
+ th.execute_token_mint_tx(Holder::Bob, &a_token_mint_tx, &a_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let (dao_merkle_path, dao_merkle_root) = {
|
|
|
- let tree = &dao_tree;
|
|
|
- let root = tree.root(0).unwrap();
|
|
|
- let merkle_path = tree.witness(dao_leaf_position, 0).unwrap();
|
|
|
- (merkle_path, root)
|
|
|
- };
|
|
|
+ info!("[Charlie] Executing governance token mint tx for Alice");
|
|
|
+ th.execute_token_mint_tx(Holder::Charlie, &a_token_mint_tx, &a_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let proposal = client::DaoProposalInfo {
|
|
|
- dest: receiver_keypair.public,
|
|
|
- amount: 1000,
|
|
|
- token_id: xdrk_token_id,
|
|
|
- blind: pallas::Base::random(&mut OsRng),
|
|
|
- };
|
|
|
+ info!("[Rachel] Executing governance token mint tx for Alice");
|
|
|
+ th.execute_token_mint_tx(Holder::Rachel, &a_token_mint_tx, &a_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let call = client::DaoProposeCall {
|
|
|
- inputs: vec![input],
|
|
|
- proposal,
|
|
|
- dao: dao.clone(),
|
|
|
- dao_leaf_position,
|
|
|
- dao_merkle_path,
|
|
|
- dao_merkle_root,
|
|
|
- };
|
|
|
- let (params, proofs) = call.make(
|
|
|
- &dao_th.dao_propose_burn_zkbin,
|
|
|
- &dao_th.dao_propose_burn_pk,
|
|
|
- &dao_th.dao_propose_main_zkbin,
|
|
|
- &dao_th.dao_propose_main_pk,
|
|
|
- )?;
|
|
|
+ info!("[Dao] Executing governance token mint tx for Alice");
|
|
|
+ th.execute_token_mint_tx(Holder::Dao, &a_token_mint_tx, &a_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let contract_id = *DAO_CONTRACT_ID;
|
|
|
+ th.assert_trees(&HOLDERS);
|
|
|
|
|
|
- let mut data = vec![DaoFunction::Propose as u8];
|
|
|
- params.encode(&mut data)?;
|
|
|
- let calls = vec![ContractCall { contract_id, data }];
|
|
|
- let proofs = vec![proofs];
|
|
|
- let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
|
|
- let sigs = tx.create_sigs(&mut OsRng, &vec![signature_secret])?;
|
|
|
- tx.signatures = vec![sigs];
|
|
|
+ // Gather owncoin
|
|
|
+ th.gather_owncoin(Holder::Alice, a_token_mint_params.output, None)?;
|
|
|
|
|
|
- let timer = Instant::now();
|
|
|
- dao_th.alice_validator.read().await.add_transactions(&[tx.clone()], current_slot, true).await?;
|
|
|
- propose_verify_times.push(timer.elapsed());
|
|
|
+ info!("[Alice] Building governance token mint tx for Bob");
|
|
|
+ let (b_token_mint_tx, b_token_mint_params) =
|
|
|
+ th.token_mint(BOB_GOV_SUPPLY, Holder::Alice, Holder::Bob, None, None)?;
|
|
|
|
|
|
- //// Wallet
|
|
|
+ info!("[Faucet] Executing governance token mint tx for Bob");
|
|
|
+ th.execute_token_mint_tx(Holder::Faucet, &b_token_mint_tx, &b_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- // HACK: Here we clone the tree so we can reproduce the root for voting.
|
|
|
- // This should be done in a nicer way
|
|
|
- let tree_at_proposal = cache.tree.clone();
|
|
|
+ info!("[Alice] Executing governance token mint tx for Bob");
|
|
|
+ th.execute_token_mint_tx(Holder::Alice, &b_token_mint_tx, &b_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- // Read received proposal
|
|
|
- let (proposal, proposal_bulla) = {
|
|
|
- let note: client::DaoProposeNote = params.note.decrypt(&dao_th.dao_kp.secret).unwrap();
|
|
|
+ info!("[Bob] Executing governance token mint tx for Bob");
|
|
|
+ th.execute_token_mint_tx(Holder::Bob, &b_token_mint_tx, &b_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- // TODO: check it belongs to DAO bulla
|
|
|
+ info!("[Charlie] Executing governance token mint tx for Bob");
|
|
|
+ th.execute_token_mint_tx(Holder::Charlie, &b_token_mint_tx, &b_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- // Return the proposal info
|
|
|
- (note.proposal, params.proposal_bulla)
|
|
|
- };
|
|
|
- debug!(target: "dao", "Proposal now active!");
|
|
|
- debug!(target: "dao", " destination: {:?}", proposal.dest);
|
|
|
- debug!(target: "dao", " amount: {}", proposal.amount);
|
|
|
- debug!(target: "dao", " token_id: {:?}", proposal.token_id);
|
|
|
- debug!(target: "dao", " dao_bulla: {:?}", dao_bulla.inner());
|
|
|
- debug!(target: "dao", "Proposal bulla: {:?}", proposal_bulla);
|
|
|
-
|
|
|
- // =======================================================
|
|
|
- // Proposal is accepted!
|
|
|
- // Start the voting
|
|
|
- // =======================================================
|
|
|
-
|
|
|
- // Copying these schizo comments from python code:
|
|
|
- // Lets the voting begin
|
|
|
- // Voters have access to the proposal and dao data
|
|
|
- // vote_state = VoteState()
|
|
|
- // We don't need to copy nullifier set because it is checked from gov_state
|
|
|
- // in vote_state_transition() anyway
|
|
|
- //
|
|
|
- // TODO: what happens if voters don't unblind their vote
|
|
|
- // Answer:
|
|
|
- // 1. there is a time limit
|
|
|
- // 2. both the MPC or users can unblind
|
|
|
- //
|
|
|
- // TODO: bug if I vote then send money, then we can double vote
|
|
|
- // TODO: all timestamps missing
|
|
|
- // - timelock (future voting starts in 2 days)
|
|
|
- // Fix: use nullifiers from money gov state only from
|
|
|
- // beginning of gov period
|
|
|
- // Cannot use nullifiers from before voting period
|
|
|
-
|
|
|
- debug!(target: "dao", "Stage 5. Start voting");
|
|
|
-
|
|
|
- // We were previously saving updates here for testing
|
|
|
- // let mut updates = vec![];
|
|
|
-
|
|
|
- // User 1: YES
|
|
|
-
|
|
|
- let (money_leaf_position, money_merkle_path) = {
|
|
|
- let tree = &tree_at_proposal;
|
|
|
- let leaf_position = gov_recv[0].leaf_position;
|
|
|
- let merkle_path = tree.witness(leaf_position, 0).unwrap();
|
|
|
- (leaf_position, merkle_path)
|
|
|
- };
|
|
|
+ info!("[Rachel] Executing governance token mint tx for Bob");
|
|
|
+ th.execute_token_mint_tx(Holder::Rachel, &b_token_mint_tx, &b_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let signature_secret = SecretKey::random(&mut OsRng);
|
|
|
- let input = client::DaoVoteInput {
|
|
|
- secret: dao_th.alice_kp.secret,
|
|
|
- note: gov_recv[0].note.clone(),
|
|
|
- leaf_position: money_leaf_position,
|
|
|
- merkle_path: money_merkle_path,
|
|
|
- signature_secret,
|
|
|
- };
|
|
|
+ info!("[Dao] Executing governance token mint tx for Bob");
|
|
|
+ th.execute_token_mint_tx(Holder::Dao, &b_token_mint_tx, &b_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let vote_option: bool = true;
|
|
|
- // assert!(vote_option || !vote_option); // wtf
|
|
|
+ th.assert_trees(&HOLDERS);
|
|
|
|
|
|
- // We create a new keypair to encrypt the vote.
|
|
|
- // For the demo MVP, you can just use the dao_keypair secret
|
|
|
- let vote_keypair_1 = Keypair::random(&mut OsRng);
|
|
|
+ // Gather owncoin
|
|
|
+ th.gather_owncoin(Holder::Bob, b_token_mint_params.output, None)?;
|
|
|
|
|
|
- let call = client::DaoVoteCall {
|
|
|
- inputs: vec![input],
|
|
|
- vote_option,
|
|
|
- yes_vote_blind: pallas::Scalar::random(&mut OsRng),
|
|
|
- vote_keypair: vote_keypair_1,
|
|
|
- proposal: proposal.clone(),
|
|
|
- dao: dao.clone(),
|
|
|
- };
|
|
|
- let (params, proofs) = call.make(
|
|
|
- &dao_th.dao_vote_burn_zkbin,
|
|
|
- &dao_th.dao_vote_burn_pk,
|
|
|
- &dao_th.dao_vote_main_zkbin,
|
|
|
- &dao_th.dao_vote_main_pk,
|
|
|
- )?;
|
|
|
+ info!("[Alice] Building governance token mint tx for Charlie");
|
|
|
+ let (c_token_mint_tx, c_token_mint_params) =
|
|
|
+ th.token_mint(CHARLIE_GOV_SUPPLY, Holder::Alice, Holder::Charlie, None, None)?;
|
|
|
|
|
|
- let contract_id = *DAO_CONTRACT_ID;
|
|
|
-
|
|
|
- let mut data = vec![DaoFunction::Vote as u8];
|
|
|
- params.encode(&mut data)?;
|
|
|
- let calls = vec![ContractCall { contract_id, data }];
|
|
|
- let proofs = vec![proofs];
|
|
|
- let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
|
|
- let sigs = tx.create_sigs(&mut OsRng, &vec![signature_secret])?;
|
|
|
- tx.signatures = vec![sigs];
|
|
|
-
|
|
|
- let timer = Instant::now();
|
|
|
- dao_th.alice_validator.read().await.add_transactions(&[tx.clone()], current_slot, true).await?;
|
|
|
- vote_verify_times.push(timer.elapsed());
|
|
|
-
|
|
|
- // Secret vote info. Needs to be revealed at some point.
|
|
|
- // TODO: look into verifiable encryption for notes
|
|
|
- // TODO: look into timelock puzzle as a possibility
|
|
|
- let vote_note_1 = {
|
|
|
- let note: client::DaoVoteNote = params.note.decrypt(&vote_keypair_1.secret).unwrap();
|
|
|
- note
|
|
|
- };
|
|
|
- debug!(target: "dao", "User 1 voted!");
|
|
|
- debug!(target: "dao", " vote_option: {}", vote_note_1.vote_option);
|
|
|
- debug!(target: "dao", " value: {}", vote_note_1.all_vote_value);
|
|
|
+ info!("[Faucet] Executing governance token mint tx for Charlie");
|
|
|
+ th.execute_token_mint_tx(Holder::Faucet, &c_token_mint_tx, &c_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- // User 2: NO
|
|
|
+ info!("[Alice] Executing governance token mint tx for Charlie");
|
|
|
+ th.execute_token_mint_tx(Holder::Alice, &c_token_mint_tx, &c_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let (money_leaf_position, money_merkle_path) = {
|
|
|
- let tree = &tree_at_proposal;
|
|
|
- let leaf_position = gov_recv[1].leaf_position;
|
|
|
- let merkle_path = tree.witness(leaf_position, 0).unwrap();
|
|
|
- (leaf_position, merkle_path)
|
|
|
- };
|
|
|
+ info!("[Bob] Executing governance token mint tx for Charlie");
|
|
|
+ th.execute_token_mint_tx(Holder::Bob, &c_token_mint_tx, &c_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let signature_secret = SecretKey::random(&mut OsRng);
|
|
|
- let input = client::DaoVoteInput {
|
|
|
- //secret: gov_keypair_2.secret,
|
|
|
- secret: dao_th.bob_kp.secret,
|
|
|
- note: gov_recv[1].note.clone(),
|
|
|
- leaf_position: money_leaf_position,
|
|
|
- merkle_path: money_merkle_path,
|
|
|
- signature_secret,
|
|
|
- };
|
|
|
+ info!("[Charlie] Executing governance token mint tx for Charlie");
|
|
|
+ th.execute_token_mint_tx(Holder::Charlie, &c_token_mint_tx, &c_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let vote_option: bool = false;
|
|
|
- // assert!(vote_option || !vote_option); // wtf
|
|
|
+ info!("[Rachel] Executing governance token mint tx for Charlie");
|
|
|
+ th.execute_token_mint_tx(Holder::Rachel, &c_token_mint_tx, &c_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- // We create a new keypair to encrypt the vote.
|
|
|
- let vote_keypair_2 = Keypair::random(&mut OsRng);
|
|
|
+ info!("[Dao] Executing governance token mint tx for Charlie");
|
|
|
+ th.execute_token_mint_tx(Holder::Dao, &c_token_mint_tx, &c_token_mint_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let call = client::DaoVoteCall {
|
|
|
- inputs: vec![input],
|
|
|
- vote_option,
|
|
|
- yes_vote_blind: pallas::Scalar::random(&mut OsRng),
|
|
|
- vote_keypair: vote_keypair_2,
|
|
|
- proposal: proposal.clone(),
|
|
|
- dao: dao.clone(),
|
|
|
- };
|
|
|
- let (params, proofs) = call.make(
|
|
|
- &dao_th.dao_vote_burn_zkbin,
|
|
|
- &dao_th.dao_vote_burn_pk,
|
|
|
- &dao_th.dao_vote_main_zkbin,
|
|
|
- &dao_th.dao_vote_main_pk,
|
|
|
+ th.assert_trees(&HOLDERS);
|
|
|
+
|
|
|
+ // Gather owncoin
|
|
|
+ th.gather_owncoin(Holder::Charlie, c_token_mint_params.output, None)?;
|
|
|
+
|
|
|
+ // ================
|
|
|
+ // Dao::Propose
|
|
|
+ // Propose the vote
|
|
|
+ // ================
|
|
|
+ info!("Stage 4. Propose the vote");
|
|
|
+ // TODO: look into proposal expiry once time for voting has finished
|
|
|
+ // TODO: Is it possible for an invalid transfer() to be constructed on exec()?
|
|
|
+ // Need to look into this.
|
|
|
+ info!("[Alice] Building DAO proposal tx");
|
|
|
+ let (propose_tx, propose_params, propose_info) = th.dao_propose(
|
|
|
+ Holder::Alice,
|
|
|
+ Holder::Rachel,
|
|
|
+ PROPOSAL_AMOUNT,
|
|
|
+ drk_token_id,
|
|
|
+ dao.clone(),
|
|
|
+ dao_mint_params.dao_bulla,
|
|
|
)?;
|
|
|
|
|
|
- let contract_id = *DAO_CONTRACT_ID;
|
|
|
+ info!("[Faucet] Executing DAO proposal tx");
|
|
|
+ th.execute_dao_propose_tx(Holder::Faucet, &propose_tx, &propose_params, current_slot).await?;
|
|
|
|
|
|
- let mut data = vec![DaoFunction::Vote as u8];
|
|
|
- params.encode(&mut data)?;
|
|
|
- let calls = vec![ContractCall { contract_id, data }];
|
|
|
- let proofs = vec![proofs];
|
|
|
- let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
|
|
- let sigs = tx.create_sigs(&mut OsRng, &vec![signature_secret])?;
|
|
|
- tx.signatures = vec![sigs];
|
|
|
+ info!("[Alice] Executing DAO proposal tx");
|
|
|
+ th.execute_dao_propose_tx(Holder::Alice, &propose_tx, &propose_params, current_slot).await?;
|
|
|
|
|
|
- let timer = Instant::now();
|
|
|
- dao_th.alice_validator.read().await.add_transactions(&[tx.clone()], current_slot, true).await?;
|
|
|
- vote_verify_times.push(timer.elapsed());
|
|
|
+ info!("[Bob] Executing DAO proposal tx");
|
|
|
+ th.execute_dao_propose_tx(Holder::Bob, &propose_tx, &propose_params, current_slot).await?;
|
|
|
|
|
|
- let vote_note_2 = {
|
|
|
- let note: client::DaoVoteNote = params.note.decrypt(&vote_keypair_2.secret).unwrap();
|
|
|
- note
|
|
|
- };
|
|
|
- debug!(target: "dao", "User 2 voted!");
|
|
|
- debug!(target: "dao", " vote_option: {}", vote_note_2.vote_option);
|
|
|
- debug!(target: "dao", " value: {}", vote_note_2.all_vote_value);
|
|
|
+ info!("[Charlie] Executing DAO proposal tx");
|
|
|
+ th.execute_dao_propose_tx(Holder::Charlie, &propose_tx, &propose_params, current_slot).await?;
|
|
|
|
|
|
- // User 3: YES
|
|
|
+ info!("[Rachel] Executing DAO proposal tx");
|
|
|
+ th.execute_dao_propose_tx(Holder::Rachel, &propose_tx, &propose_params, current_slot).await?;
|
|
|
|
|
|
- let (money_leaf_position, money_merkle_path) = {
|
|
|
- let tree = &tree_at_proposal;
|
|
|
- let leaf_position = gov_recv[2].leaf_position;
|
|
|
- let merkle_path = tree.witness(leaf_position, 0).unwrap();
|
|
|
- (leaf_position, merkle_path)
|
|
|
- };
|
|
|
+ info!("[Dao] Executing DAO proposal tx");
|
|
|
+ th.execute_dao_propose_tx(Holder::Dao, &propose_tx, &propose_params, current_slot).await?;
|
|
|
|
|
|
- let signature_secret = SecretKey::random(&mut OsRng);
|
|
|
- let input = client::DaoVoteInput {
|
|
|
- //secret: gov_keypair_3.secret,
|
|
|
- secret: dao_th.charlie_kp.secret,
|
|
|
- note: gov_recv[2].note.clone(),
|
|
|
- leaf_position: money_leaf_position,
|
|
|
- merkle_path: money_merkle_path,
|
|
|
- signature_secret,
|
|
|
- };
|
|
|
+ th.assert_trees(&HOLDERS);
|
|
|
|
|
|
- let vote_option: bool = true;
|
|
|
- // assert!(vote_option || !vote_option); // wtf
|
|
|
+ // =====================================
|
|
|
+ // Dao::Vote
|
|
|
+ // Proposal is accepted. Start the vote.
|
|
|
+ // =====================================
|
|
|
+ info!("Stage 5. Start voting");
|
|
|
|
|
|
- // We create a new keypair to encrypt the vote.
|
|
|
- let vote_keypair_3 = Keypair::random(&mut OsRng);
|
|
|
+ info!("[Alice] Building vote tx (yes)");
|
|
|
+ let (alice_vote_tx, alice_vote_params) = th.dao_vote(
|
|
|
+ Holder::Alice,
|
|
|
+ &dao_keypair,
|
|
|
+ true,
|
|
|
+ dao.clone(),
|
|
|
+ propose_info.clone(),
|
|
|
+ propose_params.proposal_bulla,
|
|
|
+ )?;
|
|
|
|
|
|
- let call = client::DaoVoteCall {
|
|
|
- inputs: vec![input],
|
|
|
- vote_option,
|
|
|
- yes_vote_blind: pallas::Scalar::random(&mut OsRng),
|
|
|
- vote_keypair: vote_keypair_3,
|
|
|
- proposal: proposal.clone(),
|
|
|
- dao: dao.clone(),
|
|
|
- };
|
|
|
- let (params, proofs) = call.make(
|
|
|
- &dao_th.dao_vote_burn_zkbin,
|
|
|
- &dao_th.dao_vote_burn_pk,
|
|
|
- &dao_th.dao_vote_main_zkbin,
|
|
|
- &dao_th.dao_vote_main_pk,
|
|
|
+ info!("[Bob] Building vote tx (no)");
|
|
|
+ let (bob_vote_tx, bob_vote_params) = th.dao_vote(
|
|
|
+ Holder::Bob,
|
|
|
+ &dao_keypair,
|
|
|
+ false,
|
|
|
+ dao.clone(),
|
|
|
+ propose_info.clone(),
|
|
|
+ propose_params.proposal_bulla,
|
|
|
)?;
|
|
|
|
|
|
- let contract_id = *DAO_CONTRACT_ID;
|
|
|
-
|
|
|
- let mut data = vec![DaoFunction::Vote as u8];
|
|
|
- params.encode(&mut data)?;
|
|
|
- let calls = vec![ContractCall { contract_id, data }];
|
|
|
- let proofs = vec![proofs];
|
|
|
- let mut tx = Transaction { calls, proofs, signatures: vec![] };
|
|
|
- let sigs = tx.create_sigs(&mut OsRng, &vec![signature_secret])?;
|
|
|
- tx.signatures = vec![sigs];
|
|
|
-
|
|
|
- let timer = Instant::now();
|
|
|
- dao_th.alice_validator.read().await.add_transactions(&[tx.clone()], current_slot, true).await?;
|
|
|
- vote_verify_times.push(timer.elapsed());
|
|
|
-
|
|
|
- // Secret vote info. Needs to be revealed at some point.
|
|
|
- // TODO: look into verifiable encryption for notes
|
|
|
- // TODO: look into timelock puzzle as a possibility
|
|
|
- let vote_note_3 = {
|
|
|
- let note: client::DaoVoteNote = params.note.decrypt(&vote_keypair_3.secret).unwrap();
|
|
|
- note
|
|
|
- };
|
|
|
- debug!(target: "dao", "User 3 voted!");
|
|
|
- debug!(target: "dao", " vote_option: {}", vote_note_3.vote_option);
|
|
|
- debug!(target: "dao", " value: {}", vote_note_3.all_vote_value);
|
|
|
-
|
|
|
- // Every votes produces a semi-homomorphic encryption of their vote.
|
|
|
- // Which is either yes or no
|
|
|
- // We copy the state tree for the governance token so coins can be used
|
|
|
- // to vote on other proposals at the same time.
|
|
|
- // With their vote, they produce a ZK proof + nullifier
|
|
|
- // The votes are unblinded by MPC to a selected party at the end of the
|
|
|
- // voting period.
|
|
|
- // (that's if we want votes to be hidden during voting)
|
|
|
+ info!("[Charlie] Building vote tx (yes)");
|
|
|
+ let (charlie_vote_tx, charlie_vote_params) = th.dao_vote(
|
|
|
+ Holder::Charlie,
|
|
|
+ &dao_keypair,
|
|
|
+ true,
|
|
|
+ dao.clone(),
|
|
|
+ propose_info.clone(),
|
|
|
+ propose_params.proposal_bulla,
|
|
|
+ )?;
|
|
|
|
|
|
- let mut total_yes_vote_value = 0;
|
|
|
- let mut total_all_vote_value = 0;
|
|
|
+ info!("[Faucet] Executing Alice vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Faucet, &alice_vote_tx, &alice_vote_params, current_slot)
|
|
|
+ .await?;
|
|
|
+ info!("[Faucet] Executing Bob vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Faucet, &bob_vote_tx, &bob_vote_params, current_slot).await?;
|
|
|
+ info!("[Faucet] Executing Charlie vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Faucet, &charlie_vote_tx, &charlie_vote_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let mut blind_total_vote = model::DaoBlindAggregateVote::default();
|
|
|
+ info!("[Alice] Executing Alice vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Alice, &alice_vote_tx, &alice_vote_params, current_slot).await?;
|
|
|
+ info!("[Alice] Executing Bob vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Alice, &bob_vote_tx, &bob_vote_params, current_slot).await?;
|
|
|
+ info!("[Alice] Executing Charlie vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Alice, &charlie_vote_tx, &charlie_vote_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- // Just keep track of these for the assert statements after the for loop
|
|
|
- // but they aren't needed otherwise.
|
|
|
- let mut total_yes_vote_blind = pallas::Scalar::from(0);
|
|
|
- let mut total_all_vote_blind = pallas::Scalar::from(0);
|
|
|
+ info!("[Bob] Executing Alice vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Bob, &alice_vote_tx, &alice_vote_params, current_slot).await?;
|
|
|
+ info!("[Bob] Executing Bob vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Bob, &bob_vote_tx, &bob_vote_params, current_slot).await?;
|
|
|
+ info!("[Bob] Executing Charlie vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Bob, &charlie_vote_tx, &charlie_vote_params, current_slot)
|
|
|
+ .await?;
|
|
|
+
|
|
|
+ info!("[Charlie] Executing Alice vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Charlie, &alice_vote_tx, &alice_vote_params, current_slot)
|
|
|
+ .await?;
|
|
|
+ info!("[Charlie] Executing Bob vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Charlie, &bob_vote_tx, &bob_vote_params, current_slot).await?;
|
|
|
+ info!("[Charlie] Executing Charlie vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Charlie, &charlie_vote_tx, &charlie_vote_params, current_slot)
|
|
|
+ .await?;
|
|
|
+
|
|
|
+ info!("[Rachel] Executing Alice vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Rachel, &alice_vote_tx, &alice_vote_params, current_slot)
|
|
|
+ .await?;
|
|
|
+ info!("[Rachel] Executing Bob vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Rachel, &bob_vote_tx, &bob_vote_params, current_slot).await?;
|
|
|
+ info!("[Rachel] Executing Charlie vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Rachel, &charlie_vote_tx, &charlie_vote_params, current_slot)
|
|
|
+ .await?;
|
|
|
+
|
|
|
+ info!("[Dao] Executing Alice vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Dao, &alice_vote_tx, &alice_vote_params, current_slot).await?;
|
|
|
+ info!("[Dao] Executing Bob vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Dao, &bob_vote_tx, &bob_vote_params, current_slot).await?;
|
|
|
+ info!("[Dao] Executing Charlie vote tx");
|
|
|
+ th.execute_dao_vote_tx(Holder::Dao, &charlie_vote_tx, &charlie_vote_params, current_slot)
|
|
|
+ .await?;
|
|
|
+
|
|
|
+ // Gather and decrypt all vote notes
|
|
|
+ let vote_note_1: DaoVoteNote = alice_vote_params.note.decrypt(&dao_keypair.secret).unwrap();
|
|
|
+ let vote_note_2: DaoVoteNote = bob_vote_params.note.decrypt(&dao_keypair.secret).unwrap();
|
|
|
+ let vote_note_3: DaoVoteNote = charlie_vote_params.note.decrypt(&dao_keypair.secret).unwrap();
|
|
|
+
|
|
|
+ // Count the votes
|
|
|
+ let mut total_yes_vote_value = 0;
|
|
|
+ let mut total_all_vote_value = 0;
|
|
|
+ let mut blind_total_vote = DaoBlindAggregateVote::default();
|
|
|
+ let mut total_yes_vote_blind = pallas::Scalar::ZERO;
|
|
|
+ let mut total_all_vote_blind = pallas::Scalar::ZERO;
|
|
|
|
|
|
for (i, note) in [vote_note_1, vote_note_2, vote_note_3].iter().enumerate() {
|
|
|
total_yes_vote_blind += note.yes_vote_blind;
|
|
|
total_all_vote_blind += note.all_vote_blind;
|
|
|
|
|
|
// Update private values
|
|
|
-
|
|
|
// vote_option is either 0 or 1
|
|
|
let yes_vote_value = note.vote_option as u64 * note.all_vote_value;
|
|
|
total_yes_vote_value += yes_vote_value;
|
|
|
total_all_vote_value += note.all_vote_value;
|
|
|
|
|
|
// Update public values
|
|
|
-
|
|
|
let yes_vote_commit = pedersen_commitment_u64(yes_vote_value, note.yes_vote_blind);
|
|
|
let all_vote_commit = pedersen_commitment_u64(note.all_vote_value, note.all_vote_blind);
|
|
|
-
|
|
|
- let blind_vote = model::DaoBlindAggregateVote { yes_vote_commit, all_vote_commit };
|
|
|
+ let blind_vote = DaoBlindAggregateVote { yes_vote_commit, all_vote_commit };
|
|
|
blind_total_vote.aggregate(blind_vote);
|
|
|
|
|
|
// Just for the debug
|
|
|
@@ -719,150 +402,79 @@ async fn integration_test() -> Result<()> {
|
|
|
true => "yes",
|
|
|
false => "no",
|
|
|
};
|
|
|
- debug!(
|
|
|
- target: "dao",
|
|
|
- "Voter {} voted {} with {} gDRK",
|
|
|
- i,
|
|
|
- vote_result,
|
|
|
- note.all_vote_value,
|
|
|
- );
|
|
|
+
|
|
|
+ info!("Voter {} voted {} with {} tokens", i, vote_result, note.all_vote_value);
|
|
|
}
|
|
|
|
|
|
- debug!(target: "dao", "Outcome = {} / {}", total_yes_vote_value, total_all_vote_value);
|
|
|
+ info!("Outcome = {} / {}", total_yes_vote_value, total_all_vote_value);
|
|
|
|
|
|
assert!(
|
|
|
blind_total_vote.all_vote_commit ==
|
|
|
- pedersen_commitment_u64(total_all_vote_value, total_all_vote_blind),
|
|
|
+ pedersen_commitment_u64(total_all_vote_value, total_all_vote_blind)
|
|
|
);
|
|
|
+
|
|
|
assert!(
|
|
|
blind_total_vote.yes_vote_commit ==
|
|
|
- pedersen_commitment_u64(total_yes_vote_value, total_yes_vote_blind),
|
|
|
+ pedersen_commitment_u64(total_yes_vote_value, total_yes_vote_blind)
|
|
|
);
|
|
|
|
|
|
- // =======================================================
|
|
|
+ // ================
|
|
|
+ // Dao::Exec
|
|
|
// Execute the vote
|
|
|
- // =======================================================
|
|
|
+ // ================
|
|
|
+ info!("Stage 6. Execute the vote");
|
|
|
|
|
|
- debug!(target: "dao", "Stage 6. Execute vote");
|
|
|
-
|
|
|
- // Used to export user_data from this coin so it can be accessed by DAO::exec()
|
|
|
- let user_data_blind = pallas::Base::random(&mut OsRng);
|
|
|
-
|
|
|
- let user_serial = pallas::Base::random(&mut OsRng);
|
|
|
- let dao_serial = pallas::Base::random(&mut OsRng);
|
|
|
- let input_value = treasury_note.value;
|
|
|
- let input_value_blind = pallas::Scalar::random(&mut OsRng);
|
|
|
- let xfer_signature_secret = SecretKey::random(&mut OsRng);
|
|
|
- let exec_signature_secret = SecretKey::random(&mut OsRng);
|
|
|
+ info!("[Dao] Building Dao::Exec tx");
|
|
|
+ let (exec_tx, xfer_params, exec_params) = th.dao_exec(
|
|
|
+ dao,
|
|
|
+ dao_mint_params.dao_bulla,
|
|
|
+ propose_info,
|
|
|
+ total_yes_vote_value,
|
|
|
+ total_all_vote_value,
|
|
|
+ total_yes_vote_blind,
|
|
|
+ total_all_vote_blind,
|
|
|
+ )?;
|
|
|
|
|
|
- let (treasury_leaf_position, treasury_merkle_path) = {
|
|
|
- let tree = &cache.tree;
|
|
|
- let leaf_position = dao_recv_coin.leaf_position;
|
|
|
- let merkle_path = tree.witness(leaf_position, 0).unwrap();
|
|
|
- (leaf_position, merkle_path)
|
|
|
- };
|
|
|
+ info!("[Faucet] Executing Dao::Exec tx");
|
|
|
+ th.execute_dao_exec_tx(Holder::Faucet, &exec_tx, &xfer_params, &exec_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- // TODO: this should be the contract/func ID
|
|
|
- //let spend_hook = pallas::Base::from(110);
|
|
|
- let spend_hook = DAO_CONTRACT_ID.inner();
|
|
|
- // The user_data can be a simple hash of the items passed into the ZK proof
|
|
|
- // up to corresponding linked ZK proof to interpret however they need.
|
|
|
- // In out case, it's the bulla for the DAO
|
|
|
- let user_data = dao_bulla.inner();
|
|
|
-
|
|
|
- let xfer_call = money_client::TransferCall {
|
|
|
- clear_inputs: vec![],
|
|
|
- inputs: vec![money_client::TransferInput {
|
|
|
- leaf_position: treasury_leaf_position,
|
|
|
- merkle_path: treasury_merkle_path,
|
|
|
- secret: dao_th.dao_kp.secret,
|
|
|
- note: treasury_note,
|
|
|
- user_data_blind,
|
|
|
- value_blind: input_value_blind,
|
|
|
- signature_secret: xfer_signature_secret,
|
|
|
- }],
|
|
|
- outputs: vec![
|
|
|
- // Sending money
|
|
|
- money_client::TransferOutput {
|
|
|
- value: 1000,
|
|
|
- token_id: xdrk_token_id,
|
|
|
- //public: user_keypair.public,
|
|
|
- public: receiver_keypair.public,
|
|
|
- serial: user_serial,
|
|
|
- spend_hook: pallas::Base::from(0),
|
|
|
- user_data: pallas::Base::from(0),
|
|
|
- },
|
|
|
- // Change back to DAO
|
|
|
- money_client::TransferOutput {
|
|
|
- value: xdrk_supply - 1000,
|
|
|
- token_id: xdrk_token_id,
|
|
|
- public: dao_th.dao_kp.public,
|
|
|
- serial: dao_serial,
|
|
|
- spend_hook,
|
|
|
- user_data,
|
|
|
- },
|
|
|
- ],
|
|
|
- };
|
|
|
- let (xfer_params, xfer_proofs) = xfer_call.make(
|
|
|
- &dao_th.money_mint_zkbin,
|
|
|
- &dao_th.money_mint_pk,
|
|
|
- &dao_th.money_burn_zkbin,
|
|
|
- &dao_th.money_burn_pk,
|
|
|
- )?;
|
|
|
+ info!("[Alice] Executing Dao::Exec tx");
|
|
|
+ th.execute_dao_exec_tx(Holder::Alice, &exec_tx, &xfer_params, &exec_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let mut data = vec![MoneyFunction::TransferV1 as u8];
|
|
|
- xfer_params.encode(&mut data)?;
|
|
|
- let xfer_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
|
|
|
+ info!("[Bob] Executing Dao::Exec tx");
|
|
|
+ th.execute_dao_exec_tx(Holder::Bob, &exec_tx, &xfer_params, &exec_params, current_slot).await?;
|
|
|
|
|
|
- let call = client::DaoExecCall {
|
|
|
- proposal,
|
|
|
- dao,
|
|
|
- yes_vote_value: total_yes_vote_value,
|
|
|
- all_vote_value: total_all_vote_value,
|
|
|
- yes_vote_blind: total_yes_vote_blind,
|
|
|
- all_vote_blind: total_all_vote_blind,
|
|
|
- user_serial,
|
|
|
- dao_serial,
|
|
|
- input_value,
|
|
|
- input_value_blind,
|
|
|
- hook_dao_exec: spend_hook,
|
|
|
- signature_secret: exec_signature_secret,
|
|
|
- };
|
|
|
- let (exec_params, exec_proofs) = call.make(&dao_th.dao_exec_zkbin, &dao_th.dao_exec_pk)?;
|
|
|
+ info!("[Charlie] Executing Dao::Exec tx");
|
|
|
+ th.execute_dao_exec_tx(Holder::Charlie, &exec_tx, &xfer_params, &exec_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let mut data = vec![DaoFunction::Exec as u8];
|
|
|
- exec_params.encode(&mut data)?;
|
|
|
- let exec_call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
|
|
|
+ info!("[Rachel] Executing Dao::Exec tx");
|
|
|
+ th.execute_dao_exec_tx(Holder::Rachel, &exec_tx, &xfer_params, &exec_params, current_slot)
|
|
|
+ .await?;
|
|
|
|
|
|
- let mut tx = Transaction {
|
|
|
- calls: vec![xfer_call, exec_call],
|
|
|
- proofs: vec![xfer_proofs, exec_proofs],
|
|
|
- signatures: vec![],
|
|
|
- };
|
|
|
- let xfer_sigs = tx.create_sigs(&mut OsRng, &vec![xfer_signature_secret])?;
|
|
|
- let exec_sigs = tx.create_sigs(&mut OsRng, &vec![exec_signature_secret])?;
|
|
|
- tx.signatures = vec![xfer_sigs, exec_sigs];
|
|
|
+ info!("[Dao] Executing Dao::Exec tx");
|
|
|
+ th.execute_dao_exec_tx(Holder::Dao, &exec_tx, &xfer_params, &exec_params, current_slot).await?;
|
|
|
|
|
|
- let timer = Instant::now();
|
|
|
- dao_th.alice_validator.read().await.add_transactions(&[tx.clone()], current_slot, true).await?;
|
|
|
- exec_verify_times.push(timer.elapsed());
|
|
|
+ th.assert_trees(&HOLDERS);
|
|
|
|
|
|
- // Statistics
|
|
|
- let mint_avg = mint_verify_times.iter().sum::<Duration>();
|
|
|
- let mint_avg = mint_avg / mint_verify_times.len() as u32;
|
|
|
- println!("Average Mint verification time: {:?}", mint_avg);
|
|
|
+ // Gather the coins
|
|
|
+ th.gather_owncoin(Holder::Dao, xfer_params.outputs[0].clone(), None)?;
|
|
|
+ th.gather_owncoin(Holder::Rachel, xfer_params.outputs[1].clone(), None)?;
|
|
|
|
|
|
- let propose_avg = propose_verify_times.iter().sum::<Duration>();
|
|
|
- let propose_avg = propose_avg / propose_verify_times.len() as u32;
|
|
|
- println!("Average Propose verification time: {:?}", propose_avg);
|
|
|
+ let rachel_wallet = th.holders.get(&Holder::Rachel).unwrap();
|
|
|
+ assert!(rachel_wallet.unspent_money_coins[0].note.value == PROPOSAL_AMOUNT);
|
|
|
+ assert!(rachel_wallet.unspent_money_coins[0].note.token_id == drk_token_id);
|
|
|
|
|
|
- let vote_avg = vote_verify_times.iter().sum::<Duration>();
|
|
|
- let vote_avg = vote_avg / vote_verify_times.len() as u32;
|
|
|
- println!("Average Vote verification time: {:?}", vote_avg);
|
|
|
+ // FIXME: The harness doesn't register that we spent the first coin on the proposal.
|
|
|
+ let dao_wallet = th.holders.get(&Holder::Dao).unwrap();
|
|
|
+ assert!(dao_wallet.unspent_money_coins[1].note.value == DRK_TOKEN_SUPPLY - PROPOSAL_AMOUNT);
|
|
|
+ assert!(dao_wallet.unspent_money_coins[1].note.token_id == drk_token_id);
|
|
|
|
|
|
- let exec_avg = exec_verify_times.iter().sum::<Duration>();
|
|
|
- let exec_avg = exec_avg / exec_verify_times.len() as u32;
|
|
|
- println!("Average Exec verification time: {:?}", exec_avg);
|
|
|
+ // Stats
|
|
|
+ th.statistics();
|
|
|
|
|
|
+ // Thanks for reading
|
|
|
Ok(())
|
|
|
}
|