Просмотр исходного кода

auth_xfer: grab data from DAO::exec auth spec, do some verification on sibling call.

x 2 лет назад
Родитель
Сommit
ff76f6e834

+ 71 - 2
src/contract/dao/src/entrypoint/auth_xfer.rs

@@ -29,8 +29,9 @@ use darkfi_sdk::{
 use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
 use crate::{
-    error::DaoError, model::DaoAuthMoneyTransferParams, DaoFunction,
-    DAO_CONTRACT_DB_PROPOSAL_BULLAS, DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
+    error::DaoError,
+    model::{DaoAuthCall, DaoAuthMoneyTransferParams, DaoExecParams},
+    DaoFunction, DAO_CONTRACT_DB_PROPOSAL_BULLAS, DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
 };
 
 /// `get_metdata` function for `Dao::Exec`
@@ -49,6 +50,21 @@ pub(crate) fn dao_authxfer_get_metadata(
     Ok(metadata)
 }
 
+fn find_auth_in_parent(
+    exec_callnode: &DarkLeaf<ContractCall>,
+    proposal_auth_calls: Vec<DaoAuthCall>,
+    self_call_idx: u32,
+) -> Option<DaoAuthCall> {
+    for (auth_call, child_idx) in
+        proposal_auth_calls.into_iter().zip(exec_callnode.children_indexes.iter())
+    {
+        if *child_idx == self_call_idx as usize {
+            return Some(auth_call);
+        }
+    }
+    return None;
+}
+
 /// `process_instruction` function for `Dao::Exec`
 pub(crate) fn dao_authxfer_process_instruction(
     cid: ContractId,
@@ -58,6 +74,10 @@ pub(crate) fn dao_authxfer_process_instruction(
     let sibling_idx = call_idx + 1;
     let xfer_call = &calls[sibling_idx as usize].data;
 
+    ///////////////////////////////////////////////////
+    // 1. Next call should be money transfer
+    ///////////////////////////////////////////////////
+
     if xfer_call.contract_id != *MONEY_CONTRACT_ID {
         return Err(DaoError::AuthXferSiblingWrongContractId.into())
     }
@@ -67,6 +87,55 @@ pub(crate) fn dao_authxfer_process_instruction(
         return Err(DaoError::AuthXferSiblingWrongFunctionCode.into())
     }
 
+    ///////////////////////////////////////////////////
+    // 2. money::transfer() inputs should all have the same user_data
+    ///////////////////////////////////////////////////
+
+    let xfer_params: MoneyTransferParamsV1 = deserialize(&xfer_call.data[1..])?;
+    assert!(xfer_params.inputs.len() > 0);
+    // We need the last output to be the change
+    assert!(xfer_params.outputs.len() > 1);
+
+    // MoneyTransfer should all have the same user_data set.
+    // We check this by ensuring that user_data_enc is also the same for all inputs.
+    // This means using the same blinding factor for all input's user_data.
+    let user_data_enc = xfer_params.inputs[0].user_data_enc;
+    for input in &xfer_params.inputs[1..] {
+        if input.user_data_enc != user_data_enc {
+            msg!("[Dao::Exec] Error: Money inputs unmatched user_data_enc");
+            return Err(DaoError::AuthXferNonMatchingEncInputUserData.into())
+        }
+    }
+
+    ///////////////////////////////////////////////////
+    // 3. Check the coins on transfer outputs match
+    ///////////////////////////////////////////////////
+
+    // Find this auth_call in the parent DAO::exec()
+    let parent_idx = calls[call_idx as usize].parent_index.unwrap();
+    let exec_callnode = &calls[parent_idx];
+    let exec_params: DaoExecParams = deserialize(&exec_callnode.data.data[1..])?;
+
+    let mut auth_call =
+        find_auth_in_parent(&exec_callnode, exec_params.proposal_auth_calls, call_idx);
+    if auth_call.is_none() {
+        return Err(DaoError::AuthXferCallNotFoundInParent.into())
+    }
+
+    // Read the proposal_data which should be Vec<CoinParams>
+    // Deserialize the proposal_data
+    // Check all the outputs except the last match
+
+    ///////////////////////////////////////////////////
+    // 4. Change belongs to the DAO
+    ///////////////////////////////////////////////////
+
+    // The last output is sent back to the DAO. This is verified inside ZK.
+    // Also the public_key should match.
+
+    // We do not need to check the amounts, since sum(input values) == sum(output values)
+    // otherwise the tx is invalid.
+
     let mut update_data = vec![];
     update_data.write_u8(DaoFunction::AuthMoneyTransfer as u8)?;
     Ok(update_data)

+ 25 - 33
src/contract/dao/src/entrypoint/exec.rs

@@ -93,45 +93,39 @@ pub(crate) fn dao_exec_process_instruction(
     let self_ = &calls[call_idx as usize];
     let params: DaoExecParams = deserialize(&self_.data.data[1..])?;
 
+    ///////////////////////////////////////////////////
+    // 1. Verify the correct calling formats match the proposal
+    ///////////////////////////////////////////////////
+
     // Check children of DAO exec match the specified calls
-    for auth_call in &params.proposal_auth_calls {
-        let child_idx = self_.children_indexes[auth_call.index];
-        let child = &calls[child_idx];
-        let call = &child.data;
+    if params.proposal_auth_calls.len() != self_.children_indexes.len() {
+        return Err(DaoError::ExecCallWrongChildCallsLen.into())
+    }
+    for (auth_call, child_idx) in
+        params.proposal_auth_calls.iter().zip(self_.children_indexes.iter())
+    {
+        let child_call = &calls[*child_idx].data;
+
+        // We are allowing 2nd tier child calls here since it
+        // should be allowed to make recursive calls.
+        // Auth modules should check the direct parent is DAO::exec().
+        // Doing anything else is potentially risky.
 
-        let contract_id = call.contract_id.inner();
-        let function_code = call.data[0];
+        let contract_id = child_call.contract_id.inner();
+        let function_code = child_call.data[0];
 
+        // Check they match the auth call spec
         if contract_id != auth_call.contract_id || function_code != auth_call.function_code {
             msg!("[Dao::Exec] Error: wrong child call");
             return Err(DaoError::ExecCallWrongChildCall.into())
         }
     }
 
-    /*
-    // MoneyTransfer should all have the same user_data set.
-    // We check this by ensuring that user_data_enc is also the same for all inputs.
-    // This means using the same blinding factor for all input's user_data.
-    assert!(mt_params.inputs.len() > 0);
-    let user_data_enc = mt_params.inputs[0].user_data_enc;
-    for input in &mt_params.inputs[1..] {
-        if input.user_data_enc != user_data_enc {
-            msg!("[Dao::Exec] Error: Money inputs unmatched user_data_enc");
-            return Err(DaoError::ExecCallInvalidFormat.into())
-        }
-    }
+    ///////////////////////////////////////////////////
+    // 2. Verify the correct voting
+    ///////////////////////////////////////////////////
 
-    // ======
-    // Checks
-    // ======
-    // MoneyTransfer should have exactly 2 outputs
-    if mt_params.outputs.len() != 2 {
-        msg!("[Dao::Exec] Error: Money outputs != 2");
-        return Err(DaoError::ExecCallOutputsLenNot2.into())
-    }
-    */
-
-    // 2. Get the ProposalVote from DAO state
+    // Get the ProposalVote from DAO state
     let proposal_db = db_lookup(cid, DAO_CONTRACT_DB_PROPOSAL_BULLAS)?;
     let Some(data) = db_get(proposal_db, &serialize(&params.proposal))? else {
         msg!("[Dao::Exec] Error: Proposal {:?} not found", params.proposal);
@@ -139,7 +133,7 @@ pub(crate) fn dao_exec_process_instruction(
     };
     let proposal: DaoProposalMetadata = deserialize(&data)?;
 
-    // 3. Check yes_vote commit and all_vote_commit are the same as in BlindAggregateVote
+    // Check yes_vote commit and all_vote_commit are the same as in BlindAggregateVote
     if proposal.vote_aggregate.yes_vote_commit != params.blind_total_vote.yes_vote_commit ||
         proposal.vote_aggregate.all_vote_commit != params.blind_total_vote.all_vote_commit
     {
@@ -156,10 +150,8 @@ pub(crate) fn dao_exec_process_instruction(
 
 /// `process_update` function for `Dao::Exec`
 pub(crate) fn dao_exec_process_update(cid: ContractId, update: DaoExecUpdate) -> ContractResult {
-    // Grab all db handles we want to work on
-    let proposal_vote_db = db_lookup(cid, DAO_CONTRACT_DB_PROPOSAL_BULLAS)?;
-
     // Remove proposal from db
+    let proposal_vote_db = db_lookup(cid, DAO_CONTRACT_DB_PROPOSAL_BULLAS)?;
     db_del(proposal_vote_db, &serialize(&update.proposal))?;
 
     Ok(())

+ 14 - 6
src/contract/dao/src/error.rs

@@ -53,15 +53,15 @@ pub enum DaoError {
     #[error("Attempted double vote")]
     DoubleVote,
 
+    #[error("Exec calls len does not match auth spec")]
+    ExecCallWrongChildCallsLen,
+
     #[error("Child of exec call does not match proposal")]
     ExecCallWrongChildCall,
 
     #[error("Exec call has invalid tx format")]
     ExecCallInvalidFormat,
 
-    #[error("Exec call outputs.len() should be 2")]
-    ExecCallOutputsLenNot2,
-
     #[error("Exec call value commitment mismatch")]
     ExecCallValueMismatch,
 
@@ -73,6 +73,12 @@ pub enum DaoError {
 
     #[error("Sibling function code is not money::transfer()")]
     AuthXferSiblingWrongFunctionCode,
+
+    #[error("Inputs with non-matching encrypted input user data")]
+    AuthXferNonMatchingEncInputUserData,
+
+    #[error("Auth call not found in parent")]
+    AuthXferCallNotFoundInParent,
 }
 
 impl From<DaoError> for ContractError {
@@ -89,13 +95,15 @@ impl From<DaoError> for ContractError {
             DaoError::ProposalEnded => Self::Custom(9),
             DaoError::CoinAlreadySpent => Self::Custom(10),
             DaoError::DoubleVote => Self::Custom(11),
-            DaoError::ExecCallWrongChildCall => Self::Custom(12),
-            DaoError::ExecCallInvalidFormat => Self::Custom(13),
-            DaoError::ExecCallOutputsLenNot2 => Self::Custom(14),
+            DaoError::ExecCallWrongChildCallsLen => Self::Custom(12),
+            DaoError::ExecCallWrongChildCall => Self::Custom(13),
+            DaoError::ExecCallInvalidFormat => Self::Custom(14),
             DaoError::ExecCallValueMismatch => Self::Custom(15),
             DaoError::VoteCommitMismatch => Self::Custom(16),
             DaoError::AuthXferSiblingWrongContractId => Self::Custom(17),
             DaoError::AuthXferSiblingWrongFunctionCode => Self::Custom(18),
+            DaoError::AuthXferNonMatchingEncInputUserData => Self::Custom(19),
+            DaoError::AuthXferCallNotFoundInParent => Self::Custom(20),
         }
     }
 }

+ 1 - 2
src/contract/dao/src/model.rs

@@ -117,10 +117,9 @@ impl TryInto<DaoBulla> for ShareAddress {
 
 #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
 pub struct DaoAuthCall {
-    pub index: usize,
     pub contract_id: pallas::Base,
     pub function_code: u8,
-    pub proposal_data: Vec<u8>,
+    pub auth_data: Vec<u8>,
 }
 
 pub trait VecAuthCallCommit {

+ 16 - 8
src/contract/test-harness/src/dao_propose.rs

@@ -27,9 +27,11 @@ use darkfi_dao_contract::{
     model::{Dao, DaoAuthCall, DaoBulla, DaoProposal, DaoProposeParams},
     DaoFunction, DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
 };
-use darkfi_money_contract::{client::OwnCoin, model::CoinParams};
+use darkfi_money_contract::{client::OwnCoin, model::CoinParams, MoneyFunction};
 use darkfi_sdk::{
-    crypto::{pasta_prelude::Field, MerkleNode, SecretKey, TokenId, DAO_CONTRACT_ID},
+    crypto::{
+        pasta_prelude::Field, MerkleNode, SecretKey, TokenId, DAO_CONTRACT_ID, MONEY_CONTRACT_ID,
+    },
     pasta::pallas,
     ContractCall,
 };
@@ -79,12 +81,18 @@ impl TestHarness {
         let mut proposal_data = vec![];
         proposal_coins.encode(&mut proposal_data).unwrap();
 
-        let auth_calls = vec![DaoAuthCall {
-            index: 0,
-            contract_id: DAO_CONTRACT_ID.inner(),
-            function_code: DaoFunction::AuthMoneyTransfer as u8,
-            proposal_data,
-        }];
+        let auth_calls = vec![
+            DaoAuthCall {
+                contract_id: DAO_CONTRACT_ID.inner(),
+                function_code: DaoFunction::AuthMoneyTransfer as u8,
+                auth_data: proposal_data,
+            },
+            DaoAuthCall {
+                contract_id: MONEY_CONTRACT_ID.inner(),
+                function_code: MoneyFunction::TransferV1 as u8,
+                auth_data: vec![],
+            },
+        ];
 
         let proposal = DaoProposal {
             auth_calls,