contract/dao: Rewrite integration test using the test harness.

This commit is contained in:
parazyd
2023-07-13 14:32:00 +02:00
parent 21d6978739
commit c359b02dd7
14 changed files with 437 additions and 1093 deletions

1
Cargo.lock generated
View File

@@ -1641,6 +1641,7 @@ dependencies = [
"bs58",
"chacha20poly1305",
"darkfi",
"darkfi-contract-test-harness",
"darkfi-money-contract",
"darkfi-sdk",
"darkfi-serial",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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<H: core::hash::Hasher>(&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<Self, ContractError> {
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<H: core::hash::Hasher>(&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,
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
//! 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<TransferClearInput>,
pub inputs: Vec<TransferInput>,
pub outputs: Vec<TransferOutput>,
}
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<MerkleNode>,
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<Proof>)> {
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(&note, &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))
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<OwnCoin>)>,
/// 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<OwnCoin> {
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 });
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

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