浏览代码

dao: add missing checks on call structure

x 2 年之前
父节点
当前提交
c50f9f6ac4

+ 24 - 1
src/contract/dao/src/entrypoint.rs

@@ -29,6 +29,7 @@ use darkfi_sdk::{
 use darkfi_serial::{deserialize, serialize, Decodable, Encodable, WriteExt};
 use darkfi_serial::{deserialize, serialize, Decodable, Encodable, WriteExt};
 
 
 use crate::{
 use crate::{
+    error::DaoError,
     model::{DaoExecUpdate, DaoMintUpdate, DaoProposeUpdate, DaoVoteUpdate},
     model::{DaoExecUpdate, DaoMintUpdate, DaoProposeUpdate, DaoVoteUpdate},
     DaoFunction, DAO_CONTRACT_DB_DAO_BULLAS, DAO_CONTRACT_DB_DAO_MERKLE_ROOTS,
     DaoFunction, DAO_CONTRACT_DB_DAO_BULLAS, DAO_CONTRACT_DB_DAO_MERKLE_ROOTS,
     DAO_CONTRACT_DB_INFO_TREE, DAO_CONTRACT_DB_PROPOSAL_BULLAS, DAO_CONTRACT_DB_VOTE_NULLIFIERS,
     DAO_CONTRACT_DB_INFO_TREE, DAO_CONTRACT_DB_PROPOSAL_BULLAS, DAO_CONTRACT_DB_VOTE_NULLIFIERS,
@@ -179,7 +180,29 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
         return Err(ContractError::Internal)
         return Err(ContractError::Internal)
     }
     }
 
 
-    match DaoFunction::try_from(calls[call_idx as usize].data[0])? {
+    let self_ = &calls[call_idx as usize];
+    let func = DaoFunction::try_from(self_.data[0])?;
+
+    if calls.len() != 1 {
+        // Enforce a strict structure for our tx
+        if calls.len() != 2 || call_idx != 1 {
+            msg!("[Dao] Error: No more than 2 calls allowed, and DAO call must be last");
+            return Err(DaoError::InvalidCalls.into())
+        }
+
+        // We can unpack user_data and check the function call is correct.
+        // But in this contract, only DAO::exec() can be invoked by other ones.
+        // So just check the function call is correct.
+
+        // NOTE: we may wish to improve this since it cripples user composability.
+
+        if func != DaoFunction::Exec {
+            msg!("[Dao] Error: Only DAO::exec() can be invoked");
+            return Err(DaoError::InvalidCalls.into())
+        }
+    }
+
+    match func {
         DaoFunction::Mint => {
         DaoFunction::Mint => {
             let update_data = dao_mint_process_instruction(cid, call_idx, calls)?;
             let update_data = dao_mint_process_instruction(cid, call_idx, calls)?;
             Ok(set_return_data(&update_data)?)
             Ok(set_return_data(&update_data)?)

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

@@ -20,6 +20,9 @@ use darkfi_sdk::error::ContractError;
 
 
 #[derive(Debug, Clone, thiserror::Error)]
 #[derive(Debug, Clone, thiserror::Error)]
 pub enum DaoError {
 pub enum DaoError {
+    #[error("Invalid calls")]
+    InvalidCalls,
+
     #[error("DAO already exists")]
     #[error("DAO already exists")]
     DaoAlreadyExists,
     DaoAlreadyExists,
 
 
@@ -66,20 +69,21 @@ pub enum DaoError {
 impl From<DaoError> for ContractError {
 impl From<DaoError> for ContractError {
     fn from(e: DaoError) -> Self {
     fn from(e: DaoError) -> Self {
         match e {
         match e {
-            DaoError::DaoAlreadyExists => Self::Custom(1),
-            DaoError::ProposalInputsEmpty => Self::Custom(2),
-            DaoError::InvalidInputMerkleRoot => Self::Custom(3),
-            DaoError::InvalidDaoMerkleRoot => Self::Custom(4),
-            DaoError::ProposalAlreadyExists => Self::Custom(5),
-            DaoError::VoteInputsEmpty => Self::Custom(6),
-            DaoError::ProposalNonexistent => Self::Custom(7),
-            DaoError::ProposalEnded => Self::Custom(8),
-            DaoError::CoinAlreadySpent => Self::Custom(9),
-            DaoError::DoubleVote => Self::Custom(10),
-            DaoError::ExecCallInvalidFormat => Self::Custom(11),
-            DaoError::ExecCallOutputsMismatch => Self::Custom(12),
-            DaoError::ExecCallValueMismatch => Self::Custom(13),
-            DaoError::VoteCommitMismatch => Self::Custom(14),
+            DaoError::InvalidCalls => Self::Custom(1),
+            DaoError::DaoAlreadyExists => Self::Custom(2),
+            DaoError::ProposalInputsEmpty => Self::Custom(3),
+            DaoError::InvalidInputMerkleRoot => Self::Custom(4),
+            DaoError::InvalidDaoMerkleRoot => Self::Custom(5),
+            DaoError::ProposalAlreadyExists => Self::Custom(6),
+            DaoError::VoteInputsEmpty => Self::Custom(7),
+            DaoError::ProposalNonexistent => Self::Custom(8),
+            DaoError::ProposalEnded => Self::Custom(9),
+            DaoError::CoinAlreadySpent => Self::Custom(10),
+            DaoError::DoubleVote => Self::Custom(11),
+            DaoError::ExecCallInvalidFormat => Self::Custom(12),
+            DaoError::ExecCallOutputsMismatch => Self::Custom(13),
+            DaoError::ExecCallValueMismatch => Self::Custom(14),
+            DaoError::VoteCommitMismatch => Self::Custom(15),
         }
         }
     }
     }
 }
 }

+ 1 - 0
src/contract/dao/src/lib.rs

@@ -22,6 +22,7 @@ use darkfi_sdk::error::ContractError;
 
 
 /// Functions available in the contract
 /// Functions available in the contract
 #[repr(u8)]
 #[repr(u8)]
+#[derive(PartialEq, Debug)]
 pub enum DaoFunction {
 pub enum DaoFunction {
     Mint = 0x00,
     Mint = 0x00,
     Propose = 0x01,
     Propose = 0x01,

+ 1 - 1
src/contract/money/src/entrypoint/transfer_v1.rs

@@ -195,7 +195,7 @@ pub(crate) fn money_transfer_process_instruction_v1(
 
 
             let next = &calls[next_call_idx as usize];
             let next = &calls[next_call_idx as usize];
             if next.contract_id.inner() != input.spend_hook {
             if next.contract_id.inner() != input.spend_hook {
-                msg!("[TransferV1] Error: Invoking contract call does not match spend hook in input {}", i);
+                msg!("[TransferV1] Error: Invoked contract call does not match spend hook in input {}", i);
                 return Err(MoneyError::SpendHookMismatch.into())
                 return Err(MoneyError::SpendHookMismatch.into())
             }
             }
         }
         }

+ 1 - 1
src/contract/money/src/model.rs

@@ -92,7 +92,7 @@ pub struct Input {
     /// must have this value as its ID.
     /// must have this value as its ID.
     pub spend_hook: pallas::Base,
     pub spend_hook: pallas::Base,
     /// Encrypted user data field. An encrypted commitment to arbitrary data.
     /// Encrypted user data field. An encrypted commitment to arbitrary data.
-    /// When spend hook is set (it is nonzero), then this field may be user
+    /// When spend hook is set (it is nonzero), then this field may be used
     /// to pass data to the invoked contract.
     /// to pass data to the invoked contract.
     pub user_data_enc: pallas::Base,
     pub user_data_enc: pallas::Base,
     /// Public key for the signature
     /// Public key for the signature