diff --git a/Cargo.lock b/Cargo.lock
index 7cc7668fe..4aaadc98f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1641,6 +1641,7 @@ dependencies = [
"bs58",
"chacha20poly1305",
"darkfi",
+ "darkfi-contract-test-harness",
"darkfi-money-contract",
"darkfi-sdk",
"darkfi-serial",
diff --git a/src/contract/dao/Cargo.toml b/src/contract/dao/Cargo.toml
index 3400ca495..19f816995 100644
--- a/src/contract/dao/Cargo.toml
+++ b/src/contract/dao/Cargo.toml
@@ -27,10 +27,10 @@ rand = { version = "0.8.5", optional = true }
[dev-dependencies]
async-std = {version = "1.12.0", features = ["attributes"]}
darkfi = {path = "../../../", features = ["tx", "blockchain"]}
-darkfi-money-contract = { path = "../money", features = ["client", "no-entrypoint"] }
+darkfi-money-contract = {path = "../money", features = ["client", "no-entrypoint"]}
simplelog = "0.12.1"
sled = "0.34.7"
-#sqlx = {version = "0.6.3", features = ["runtime-async-std-rustls", "sqlite"]}
+darkfi-contract-test-harness = {path = "../test-harness"}
# We need to disable random using "custom" which makes the crate a noop
# so the wasm32-unknown-unknown target is enabled.
diff --git a/src/contract/dao/src/client/exec.rs b/src/contract/dao/src/client/exec.rs
index 39fb9e904..89da4ff44 100644
--- a/src/contract/dao/src/client/exec.rs
+++ b/src/contract/dao/src/client/exec.rs
@@ -32,7 +32,7 @@ use darkfi::{
};
use super::{DaoInfo, DaoProposalInfo};
-use crate::model::{DaoBlindAggregateVote, DaoExecParams};
+use crate::model::{DaoBlindAggregateVote, DaoExecParams, DaoProposalBulla};
pub struct DaoExecCall {
pub proposal: DaoProposalInfo,
@@ -85,14 +85,14 @@ impl DaoExecCall {
self.dao.bulla_blind,
]);
- let proposal_bulla = poseidon_hash::<6>([
+ let proposal_bulla = DaoProposalBulla::from(poseidon_hash::<6>([
proposal_dest_x,
proposal_dest_y,
proposal_amount,
self.proposal.token_id.inner(),
dao_bulla,
self.proposal.blind,
- ]);
+ ]));
let coin_0 = poseidon_hash::<7>([
proposal_dest_x,
@@ -157,7 +157,7 @@ impl DaoExecCall {
debug!(target: "dao", "proposal_bulla: {:?}", proposal_bulla);
let public_inputs = vec![
- proposal_bulla,
+ proposal_bulla.inner(),
coin_0,
coin_1,
*yes_vote_commit_coords.x(),
diff --git a/src/contract/dao/src/client/propose.rs b/src/contract/dao/src/client/propose.rs
index 18f4105d0..f48678526 100644
--- a/src/contract/dao/src/client/propose.rs
+++ b/src/contract/dao/src/client/propose.rs
@@ -34,7 +34,7 @@ use darkfi::{
Result,
};
-use crate::model::{DaoProposeParams, DaoProposeParamsInput};
+use crate::model::{DaoProposalBulla, DaoProposeParams, DaoProposeParamsInput};
use super::DaoInfo;
@@ -195,14 +195,14 @@ impl DaoProposeCall {
let dao_leaf_position: u64 = self.dao_leaf_position.into();
- let proposal_bulla = poseidon_hash::<6>([
+ let proposal_bulla = DaoProposalBulla::from(poseidon_hash::<6>([
proposal_dest_x,
proposal_dest_y,
proposal_amount,
self.proposal.token_id.inner(),
dao_bulla,
self.proposal.blind,
- ]);
+ ]));
let prover_witnesses = vec![
// Proposers total number of gov tokens
@@ -231,7 +231,7 @@ impl DaoProposeCall {
let public_inputs = vec![
token_commit,
self.dao_merkle_root.inner(),
- proposal_bulla,
+ proposal_bulla.inner(),
*total_funds_coords.x(),
*total_funds_coords.y(),
];
diff --git a/src/contract/dao/src/client/vote.rs b/src/contract/dao/src/client/vote.rs
index 5513b3188..cb4aa5aae 100644
--- a/src/contract/dao/src/client/vote.rs
+++ b/src/contract/dao/src/client/vote.rs
@@ -36,7 +36,7 @@ use darkfi::{
};
use super::{DaoInfo, DaoProposalInfo};
-use crate::model::{DaoVoteParams, DaoVoteParamsInput};
+use crate::model::{DaoProposalBulla, DaoVoteParams, DaoVoteParamsInput};
#[derive(SerialEncodable, SerialDecodable)]
pub struct DaoVoteNote {
@@ -196,14 +196,14 @@ impl DaoVoteCall {
self.dao.bulla_blind,
]);
- let proposal_bulla = poseidon_hash::<6>([
+ let proposal_bulla = DaoProposalBulla::from(poseidon_hash::<6>([
proposal_dest_x,
proposal_dest_y,
proposal_amount,
self.proposal.token_id.inner(),
dao_bulla,
self.proposal.blind,
- ]);
+ ]));
let vote_option = self.vote_option as u64;
assert!(vote_option == 0 || vote_option == 1);
@@ -243,7 +243,7 @@ impl DaoVoteCall {
let public_inputs = vec![
token_commit,
- proposal_bulla,
+ proposal_bulla.inner(),
// this should be a value commit??
*yes_vote_commit_coords.x(),
*yes_vote_commit_coords.y(),
diff --git a/src/contract/dao/src/entrypoint/exec.rs b/src/contract/dao/src/entrypoint/exec.rs
index e691b6587..21db13785 100644
--- a/src/contract/dao/src/entrypoint/exec.rs
+++ b/src/contract/dao/src/entrypoint/exec.rs
@@ -55,7 +55,7 @@ pub(crate) fn dao_exec_get_metadata(
zk_public_inputs.push((
DAO_CONTRACT_ZKAS_DAO_EXEC_NS.to_string(),
vec![
- params.proposal,
+ params.proposal.inner(),
params.coin_0.inner(),
params.coin_1.inner(),
*yes_vote_coords.x(),
@@ -110,8 +110,11 @@ pub(crate) fn dao_exec_process_instruction(
// Checks
// ======
// 1. Check coins in MoneyTransfer are the same as our coin 0 and coin 1
- if mt_params.outputs[0].coin != params.coin_0 ||
- mt_params.outputs[1].coin != params.coin_1 ||
+ // * outputs[0] is the change returned to DAO
+ // * outputs[1] is the value being sent to the recipient
+ // (This is how it's done in the client API of Money::Transfer)
+ if mt_params.outputs[0].coin != params.coin_1 ||
+ mt_params.outputs[1].coin != params.coin_0 ||
mt_params.outputs.len() != 2
{
msg!("[Dao::Exec] Error: Coin commitments mismatch");
diff --git a/src/contract/dao/src/entrypoint/propose.rs b/src/contract/dao/src/entrypoint/propose.rs
index 1530f79ab..65a25c276 100644
--- a/src/contract/dao/src/entrypoint/propose.rs
+++ b/src/contract/dao/src/entrypoint/propose.rs
@@ -85,7 +85,7 @@ pub(crate) fn dao_propose_get_metadata(
vec![
params.token_commit,
params.dao_merkle_root.inner(),
- params.proposal_bulla,
+ params.proposal_bulla.inner(),
*total_funds_coords.x(),
*total_funds_coords.y(),
],
diff --git a/src/contract/dao/src/entrypoint/vote.rs b/src/contract/dao/src/entrypoint/vote.rs
index ee35cb02c..d89c9589e 100644
--- a/src/contract/dao/src/entrypoint/vote.rs
+++ b/src/contract/dao/src/entrypoint/vote.rs
@@ -90,7 +90,7 @@ pub(crate) fn dao_vote_get_metadata(
DAO_CONTRACT_ZKAS_DAO_VOTE_MAIN_NS.to_string(),
vec![
params.token_commit,
- params.proposal_bulla,
+ params.proposal_bulla.inner(),
*yes_vote_commit_coords.x(),
*yes_vote_commit_coords.y(),
*all_vote_commit_coords.x(),
diff --git a/src/contract/dao/src/lib.rs b/src/contract/dao/src/lib.rs
index 8a0f639dd..ef0483bb2 100644
--- a/src/contract/dao/src/lib.rs
+++ b/src/contract/dao/src/lib.rs
@@ -57,12 +57,6 @@ pub mod entrypoint;
/// Client API for interaction with this smart contract
pub mod client;
-// TODO: Delete these and use the proper API
-#[cfg(feature = "client")]
-pub mod money_client;
-#[cfg(feature = "client")]
-pub mod wallet_cache;
-
// These are the different sled trees that will be created
pub const DAO_CONTRACT_DB_INFO_TREE: &str = "dao_info";
pub const DAO_CONTRACT_DB_DAO_BULLAS: &str = "dao_bullas";
diff --git a/src/contract/dao/src/model.rs b/src/contract/dao/src/model.rs
index 2d89ef503..57848ab93 100644
--- a/src/contract/dao/src/model.rs
+++ b/src/contract/dao/src/model.rs
@@ -16,6 +16,8 @@
* along with this program. If not, see .
*/
+use core::str::FromStr;
+
use darkfi_money_contract::model::Coin;
use darkfi_sdk::{
crypto::{note::AeadEncryptedNote, pasta_prelude::*, MerkleNode, Nullifier, PublicKey},
@@ -51,11 +53,53 @@ impl DaoBulla {
}
}
-use core::str::FromStr;
+impl std::hash::Hash for DaoBulla {
+ fn hash(&self, state: &mut H) {
+ state.write(&self.to_bytes());
+ }
+}
+
darkfi_sdk::fp_from_bs58!(DaoBulla);
darkfi_sdk::fp_to_bs58!(DaoBulla);
darkfi_sdk::ty_from_fp!(DaoBulla);
+/// A `DaoProposalBulla` represented in the state
+#[derive(Debug, Copy, Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)]
+pub struct DaoProposalBulla(pallas::Base);
+
+impl DaoProposalBulla {
+ /// Reference the raw inner base field element
+ pub fn inner(&self) -> pallas::Base {
+ self.0
+ }
+
+ /// Create a `DaoBulla` object from given bytes, erroring if the
+ /// input bytes are noncanonical.
+ pub fn from_bytes(x: [u8; 32]) -> Result {
+ match pallas::Base::from_repr(x).into() {
+ Some(v) => Ok(Self(v)),
+ None => Err(ContractError::IoError(
+ "Failed to instantiate DaoProposalBulla from bytes".to_string(),
+ )),
+ }
+ }
+
+ /// Convert the `DaoBulla` type into 32 raw bytes
+ pub fn to_bytes(&self) -> [u8; 32] {
+ self.0.to_repr()
+ }
+}
+
+impl std::hash::Hash for DaoProposalBulla {
+ fn hash(&self, state: &mut H) {
+ state.write(&self.to_bytes());
+ }
+}
+
+darkfi_sdk::fp_from_bs58!(DaoProposalBulla);
+darkfi_sdk::fp_to_bs58!(DaoProposalBulla);
+darkfi_sdk::ty_from_fp!(DaoProposalBulla);
+
/// Parameters for `Dao::Mint`
#[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
pub struct DaoMintParams {
@@ -80,7 +124,7 @@ pub struct DaoProposeParams {
/// Token ID commitment for the proposal
pub token_commit: pallas::Base,
/// Bulla of the DAO proposal
- pub proposal_bulla: pallas::Base,
+ pub proposal_bulla: DaoProposalBulla,
/// Encrypted note
pub note: AeadEncryptedNote,
/// Inputs for the proposal
@@ -102,7 +146,7 @@ pub struct DaoProposeParamsInput {
#[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
pub struct DaoProposeUpdate {
/// Minted proposal bulla
- pub proposal_bulla: pallas::Base,
+ pub proposal_bulla: DaoProposalBulla,
/// Snapshotted Merkle root in the Money state
pub snapshot_root: MerkleNode,
}
@@ -124,7 +168,7 @@ pub struct DaoVoteParams {
/// Token commitment for the vote inputs
pub token_commit: pallas::Base,
/// Proposal bulla being voted on
- pub proposal_bulla: pallas::Base,
+ pub proposal_bulla: DaoProposalBulla,
/// Commitment for yes votes
pub yes_vote_commit: pallas::Point,
/// Encrypted note
@@ -150,7 +194,7 @@ pub struct DaoVoteParamsInput {
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct DaoVoteUpdate {
/// The proposal bulla being voted on
- pub proposal_bulla: pallas::Base,
+ pub proposal_bulla: DaoProposalBulla,
/// The updated proposal metadata
pub proposal_metadata: DaoProposalMetadata,
/// Vote nullifiers,
@@ -188,7 +232,7 @@ impl Default for DaoBlindAggregateVote {
#[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
pub struct DaoExecParams {
/// The proposal bulla
- pub proposal: pallas::Base,
+ pub proposal: DaoProposalBulla,
/// The output coin for the proposal recipient
pub coin_0: Coin,
/// The output coin for the change returned to DAO
@@ -203,5 +247,5 @@ pub struct DaoExecParams {
#[derive(Debug, Copy, Clone, SerialEncodable, SerialDecodable)]
pub struct DaoExecUpdate {
/// The proposal bulla
- pub proposal: pallas::Base,
+ pub proposal: DaoProposalBulla,
}
diff --git a/src/contract/dao/src/money_client.rs b/src/contract/dao/src/money_client.rs
deleted file mode 100644
index f575ba8d3..000000000
--- a/src/contract/dao/src/money_client.rs
+++ /dev/null
@@ -1,227 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2023 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-//! TODO: This file should be deleted and the API from money::client
-//! should be used directly.
-
-use darkfi::{
- zk::{Proof, ProvingKey},
- zkas::ZkBinary,
- Result,
-};
-use darkfi_sdk::{
- bridgetree,
- crypto::{
- note::AeadEncryptedNote, pasta_prelude::*, MerkleNode, PublicKey, SecretKey, TokenId,
- ValueBlind,
- },
- pasta::pallas,
-};
-
-use rand::rngs::OsRng;
-
-use darkfi_money_contract::{
- client::{
- transfer_v1::{
- create_transfer_burn_proof, create_transfer_mint_proof, TransactionBuilderInputInfo,
- TransactionBuilderOutputInfo,
- },
- MoneyNote,
- },
- model::{ClearInput, Input, MoneyTransferParamsV1, Output},
-};
-
-pub struct TransferCall {
- pub clear_inputs: Vec,
- pub inputs: Vec,
- pub outputs: Vec,
-}
-
-pub struct TransferClearInput {
- pub value: u64,
- pub token_id: TokenId,
- pub signature_secret: SecretKey,
-}
-
-pub struct TransferInput {
- pub leaf_position: bridgetree::Position,
- pub merkle_path: Vec,
- pub secret: SecretKey,
- pub note: MoneyNote,
- pub user_data_blind: pallas::Base,
- pub value_blind: ValueBlind,
- pub signature_secret: SecretKey,
-}
-
-pub struct TransferOutput {
- pub value: u64,
- pub token_id: TokenId,
- pub public: PublicKey,
- pub serial: pallas::Base,
- pub spend_hook: pallas::Base,
- pub user_data: pallas::Base,
-}
-
-impl TransferCall {
- fn compute_remainder_blind(
- clear_inputs: &[ClearInput],
- input_blinds: &[ValueBlind],
- output_blinds: &[ValueBlind],
- ) -> ValueBlind {
- let mut total = ValueBlind::zero();
-
- for input in clear_inputs {
- total += input.value_blind;
- }
-
- for input_blind in input_blinds {
- total += input_blind;
- }
-
- for output_blind in output_blinds {
- total -= output_blind;
- }
-
- total
- }
-
- pub fn make(
- self,
- mint_zkbin: &ZkBinary,
- mint_pk: &ProvingKey,
- burn_zkbin: &ZkBinary,
- burn_pk: &ProvingKey,
- ) -> Result<(MoneyTransferParamsV1, Vec)> {
- assert!(self.clear_inputs.len() + self.inputs.len() > 0);
-
- let mut clear_inputs = vec![];
- let token_blind = ValueBlind::random(&mut OsRng);
- for input in &self.clear_inputs {
- let signature_public = PublicKey::from_secret(input.signature_secret);
- let value_blind = ValueBlind::random(&mut OsRng);
-
- let clear_input = ClearInput {
- value: input.value,
- token_id: input.token_id,
- value_blind,
- token_blind,
- signature_public,
- };
- clear_inputs.push(clear_input);
- }
-
- let mut proofs = vec![];
- let mut inputs = vec![];
- let mut input_blinds = vec![];
-
- for input in self.inputs {
- let value_blind = input.value_blind;
- input_blinds.push(value_blind);
-
- // FIXME: Just an API hack
- let _input = TransactionBuilderInputInfo {
- leaf_position: input.leaf_position,
- merkle_path: input.merkle_path,
- secret: input.secret,
- note: input.note,
- };
-
- let (proof, revealed) = create_transfer_burn_proof(
- burn_zkbin,
- burn_pk,
- &_input,
- value_blind,
- token_blind,
- input.user_data_blind,
- input.signature_secret,
- )?;
-
- proofs.push(proof);
-
- let input = Input {
- value_commit: revealed.value_commit,
- token_commit: revealed.token_commit,
- nullifier: revealed.nullifier,
- merkle_root: revealed.merkle_root,
- spend_hook: revealed.spend_hook,
- user_data_enc: revealed.user_data_enc,
- signature_public: revealed.signature_public,
- };
- inputs.push(input);
- }
-
- let mut outputs = vec![];
- let mut output_blinds = vec![];
- // This value_blind calc assumes there will always be at least a single output
- assert!(!self.outputs.is_empty());
-
- for (i, output) in self.outputs.iter().enumerate() {
- let value_blind = if i == self.outputs.len() - 1 {
- Self::compute_remainder_blind(&clear_inputs, &input_blinds, &output_blinds)
- } else {
- ValueBlind::random(&mut OsRng)
- };
- output_blinds.push(value_blind);
-
- let serial = output.serial;
-
- // FIXME: This is a hack between the two APIs
- let _output = TransactionBuilderOutputInfo {
- value: output.value,
- token_id: output.token_id,
- public_key: output.public,
- };
-
- let (proof, revealed) = create_transfer_mint_proof(
- mint_zkbin,
- mint_pk,
- &_output,
- value_blind,
- token_blind,
- serial,
- output.spend_hook,
- output.user_data,
- )?;
-
- proofs.push(proof);
-
- let note = MoneyNote {
- serial,
- value: output.value,
- token_id: output.token_id,
- spend_hook: output.spend_hook,
- user_data: output.user_data,
- value_blind,
- token_blind,
- memo: Vec::new(),
- };
-
- let encrypted_note = AeadEncryptedNote::encrypt(¬e, &output.public, &mut OsRng)?;
-
- let output = Output {
- value_commit: revealed.value_commit,
- token_commit: revealed.token_commit,
- coin: revealed.coin,
- note: encrypted_note,
- };
- outputs.push(output);
- }
-
- Ok((MoneyTransferParamsV1 { clear_inputs, inputs, outputs }, proofs))
- }
-}
diff --git a/src/contract/dao/src/wallet_cache.rs b/src/contract/dao/src/wallet_cache.rs
deleted file mode 100644
index 82582103d..000000000
--- a/src/contract/dao/src/wallet_cache.rs
+++ /dev/null
@@ -1,85 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2023 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-use darkfi_sdk::{
- bridgetree,
- crypto::{note::AeadEncryptedNote, pasta_prelude::Field, MerkleNode, MerkleTree, SecretKey},
- pasta::pallas,
-};
-
-use darkfi_money_contract::{client::MoneyNote, model::Coin};
-
-pub struct OwnCoin {
- pub coin: Coin,
- pub note: MoneyNote,
- pub leaf_position: bridgetree::Position,
-}
-
-pub struct WalletCache {
- // Normally this would be a HashMap, but SecretKey is not Hash-able
- // TODO: This can be HashableBase
- cache: Vec<(SecretKey, Vec)>,
- /// The entire Money Merkle tree state
- pub tree: MerkleTree,
-}
-
-impl Default for WalletCache {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl WalletCache {
- pub fn new() -> Self {
- let mut tree = MerkleTree::new(100);
- tree.append(MerkleNode::from(pallas::Base::ZERO));
- let _ = tree.mark().unwrap();
- Self { cache: Vec::new(), tree }
- }
-
- /// Must be called at the start to begin tracking received coins for this secret.
- pub fn track(&mut self, secret: SecretKey) {
- self.cache.push((secret, Vec::new()));
- }
-
- /// Get all coins received by this secret key
- /// track() must be called on this secret before calling this or the function will panic.
- pub fn get_received(&mut self, secret: &SecretKey) -> Vec {
- for (other_secret, own_coins) in self.cache.iter_mut() {
- if *secret == *other_secret {
- // clear own_coins vec, and return current contents
- return std::mem::take(own_coins)
- }
- }
- panic!("you forget to track() this secret!");
- }
-
- pub fn try_decrypt_note(&mut self, coin: Coin, ciphertext: &AeadEncryptedNote) {
- // Add the new coins to the Merkle tree
- self.tree.append(MerkleNode::from(coin.inner()));
-
- // Loop through all our secret keys...
- for (secret, own_coins) in self.cache.iter_mut() {
- // .. attempt to decrypt the note ...
- if let Ok(note) = ciphertext.decrypt(secret) {
- let leaf_position = self.tree.mark().expect("coin should be in tree");
- own_coins.push(OwnCoin { coin, note, leaf_position });
- }
- }
- }
-}
diff --git a/src/contract/dao/tests/integration.rs b/src/contract/dao/tests/integration.rs
index ce98d7506..dcb770e28 100644
--- a/src/contract/dao/tests/integration.rs
+++ b/src/contract/dao/tests/integration.rs
@@ -16,702 +16,385 @@
* along with this program. If not, see .
*/
-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()?;
+ 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![];
+ // 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");
+ // ====================
+ info!("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!("[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 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,
- )?;
-
- let contract_id = *MONEY_CONTRACT_ID;
-
- 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];
-
- dao_th.alice_validator.read().await.add_transactions(&[tx.clone()], current_slot, true).await?;
-
- // Wallet stuff
-
- // 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)?;
-
- for output in params.outputs {
- let coin = Coin::from(output.coin);
- cache.try_decrypt_note(coin, &output.note);
- }
- }
-
- 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)
+ info!("[Faucet] Executing DAO airdrop tx");
+ th.execute_airdrop_native_tx(Holder::Faucet, &airdrop_tx, &airdrop_params, current_slot)
.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);
- }
- }
+ info!("[Alice] Executing DAO airdrop tx");
+ th.execute_airdrop_native_tx(Holder::Alice, &airdrop_tx, &airdrop_params, current_slot).await?;
- 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;
+ info!("[Bob] Executing DAO airdrop tx");
+ th.execute_airdrop_native_tx(Holder::Bob, &airdrop_tx, &airdrop_params, current_slot).await?;
- 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));
+ info!("[Charlie] Executing DAO airdrop tx");
+ th.execute_airdrop_native_tx(Holder::Charlie, &airdrop_tx, &airdrop_params, current_slot)
+ .await?;
- 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());
+ info!("[Rachel] Executing DAO airdrop tx");
+ th.execute_airdrop_native_tx(Holder::Rachel, &airdrop_tx, &airdrop_params, current_slot)
+ .await?;
- debug!(target: "dao", "Holder{} received a coin worth {} gDRK", i, note.value);
+ info!("[Dao] Executing DAO airdrop tx");
+ th.execute_airdrop_native_tx(Holder::Dao, &airdrop_tx, &airdrop_params, current_slot).await?;
- 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();
+ th.assert_trees(&HOLDERS);
- // =======================================================
+ // Gather the DAO owncoin
+ th.gather_owncoin(Holder::Dao, airdrop_params.outputs[0].clone(), None)?;
+
+ // ======================================
+ // Mint the governance token to 3 holders
+ // ======================================
+ info!("Stage 3. Minting governance token");
+
+ 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)?;
+
+ 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?;
+
+ 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?;
+
+ 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?;
+
+ 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?;
+
+ 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?;
+
+ 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?;
+
+ th.assert_trees(&HOLDERS);
+
+ // Gather owncoin
+ th.gather_owncoin(Holder::Alice, a_token_mint_params.output, None)?;
+
+ 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)?;
+
+ 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?;
+
+ 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?;
+
+ 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?;
+
+ 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?;
+
+ 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?;
+
+ 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?;
+
+ th.assert_trees(&HOLDERS);
+
+ // Gather owncoin
+ th.gather_owncoin(Holder::Bob, b_token_mint_params.output, None)?;
+
+ 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)?;
+
+ 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?;
+
+ 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?;
+
+ 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?;
+
+ 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?;
+
+ 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?;
+
+ 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?;
+
+ th.assert_trees(&HOLDERS);
+
+ // Gather owncoin
+ th.gather_owncoin(Holder::Charlie, c_token_mint_params.output, 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");
-
+ // ================
+ info!("Stage 4. Propose the vote");
// TODO: look into proposal expiry once time for voting has finished
-
- let receiver_keypair = Keypair::random(&mut OsRng);
-
- 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)
- };
-
- // 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,
- };
-
- 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)
- };
-
- let proposal = client::DaoProposalInfo {
- dest: receiver_keypair.public,
- amount: 1000,
- token_id: xdrk_token_id,
- blind: pallas::Base::random(&mut OsRng),
- };
-
- 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,
+ // 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::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];
+ 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?;
- propose_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?;
- //// Wallet
+ info!("[Charlie] Executing DAO proposal tx");
+ th.execute_dao_propose_tx(Holder::Charlie, &propose_tx, &propose_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!("[Rachel] Executing DAO proposal tx");
+ th.execute_dao_propose_tx(Holder::Rachel, &propose_tx, &propose_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!("[Dao] Executing DAO proposal tx");
+ th.execute_dao_propose_tx(Holder::Dao, &propose_tx, &propose_params, current_slot).await?;
- // TODO: check it belongs to DAO bulla
+ th.assert_trees(&HOLDERS);
- // 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);
+ // =====================================
+ // Dao::Vote
+ // Proposal is accepted. Start the vote.
+ // =====================================
+ info!("Stage 5. Start voting");
- // =======================================================
- // 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)
- };
-
- 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,
- };
-
- let vote_option: bool = true;
- // assert!(vote_option || !vote_option); // wtf
-
- // 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);
-
- 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 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 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);
-
- // User 2: NO
-
- 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)
- };
-
- 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,
- };
-
- let vote_option: bool = false;
- // assert!(vote_option || !vote_option); // wtf
-
- // We create a new keypair to encrypt the vote.
- let vote_keypair_2 = Keypair::random(&mut OsRng);
-
- 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,
+ 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());
-
- 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);
-
- // User 3: YES
-
- 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)
- };
-
- 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,
- };
-
- let vote_option: bool = true;
- // assert!(vote_option || !vote_option); // wtf
-
- // We create a new keypair to encrypt the vote.
- let vote_keypair_3 = Keypair::random(&mut OsRng);
-
- 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!("[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 contract_id = *DAO_CONTRACT_ID;
+ 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 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 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?;
- 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 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?;
- // 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);
+ 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?;
- // 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!("[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 = model::DaoBlindAggregateVote::default();
-
- // 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);
+ 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);
-
- 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)
- };
-
- // 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!("[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 mut data = vec![MoneyFunction::TransferV1 as u8];
- xfer_params.encode(&mut data)?;
- let xfer_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
+ info!("[Faucet] Executing Dao::Exec tx");
+ th.execute_dao_exec_tx(Holder::Faucet, &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!("[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![DaoFunction::Exec as u8];
- exec_params.encode(&mut data)?;
- let exec_call = ContractCall { contract_id: *DAO_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 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!("[Charlie] Executing Dao::Exec tx");
+ th.execute_dao_exec_tx(Holder::Charlie, &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());
+ info!("[Rachel] Executing Dao::Exec tx");
+ th.execute_dao_exec_tx(Holder::Rachel, &exec_tx, &xfer_params, &exec_params, current_slot)
+ .await?;
- // Statistics
- let mint_avg = mint_verify_times.iter().sum::();
- let mint_avg = mint_avg / mint_verify_times.len() as u32;
- println!("Average Mint verification time: {:?}", mint_avg);
+ info!("[Dao] Executing Dao::Exec tx");
+ th.execute_dao_exec_tx(Holder::Dao, &exec_tx, &xfer_params, &exec_params, current_slot).await?;
- let propose_avg = propose_verify_times.iter().sum::();
- let propose_avg = propose_avg / propose_verify_times.len() as u32;
- println!("Average Propose verification time: {:?}", propose_avg);
+ th.assert_trees(&HOLDERS);
- let vote_avg = vote_verify_times.iter().sum::();
- let vote_avg = vote_avg / vote_verify_times.len() as u32;
- println!("Average Vote verification time: {:?}", vote_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 exec_avg = exec_verify_times.iter().sum::();
- let exec_avg = exec_avg / exec_verify_times.len() as u32;
- println!("Average Exec verification time: {:?}", exec_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);
+ // 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);
+
+ // Stats
+ th.statistics();
+
+ // Thanks for reading
Ok(())
}
diff --git a/src/contract/money/tests/verification_bench.rs b/src/contract/money/tests/verification_bench.rs
index 81be63c95..29fc6cb8a 100644
--- a/src/contract/money/tests/verification_bench.rs
+++ b/src/contract/money/tests/verification_bench.rs
@@ -53,7 +53,8 @@ async fn alice2alice_random_amounts() -> Result<()> {
info!(target: "money", "[Faucet] ========================");
info!(target: "money", "[Faucet] Building Alice's airdrop");
info!(target: "money", "[Faucet] ========================");
- let (airdrop_tx, airdrop_params) = th.airdrop_native(ALICE_AIRDROP, Holder::Alice)?;
+ let (airdrop_tx, airdrop_params) =
+ th.airdrop_native(ALICE_AIRDROP, Holder::Alice, None, None, None, None)?;
info!(target: "money", "[Faucet] ==========================");
info!(target: "money", "[Faucet] Executing Alice airdrop tx");
@@ -151,7 +152,8 @@ async fn alice2alice_multiplecoins_random_amounts() -> Result<()> {
info!(target: "money", "[Faucet] ===================================================");
info!(target: "money", "[Faucet] Building Money::Mint params for Alice's mint for token {} and amount {}", i, amount);
info!(target: "money", "[Faucet] ===================================================");
- let (mint_tx, mint_params) = th.token_mint(amount, Holder::Alice, Holder::Alice)?;
+ let (mint_tx, mint_params) =
+ th.token_mint(amount, Holder::Alice, Holder::Alice, None, None)?;
info!(target: "money", "[Faucet] =======================");
info!(target: "money", "[Faucet] Executing Alice mint tx");