Browse Source

add spend_hook to money contracts, and checks for DAO::exec()

x 3 năm trước cách đây
mục cha
commit
0c292e524e

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

@@ -148,8 +148,23 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
     assert!(call_idx < call.len() as u32);
 
     let self_ = &call[call_idx as usize];
+    let func = DaoFunction::try_from(self_.data[0])?;
 
-    match DaoFunction::try_from(self_.data[0])? {
+    if call.len() != 1 {
+        // Enforce a strict structure for our tx
+        assert_eq!(call.len(), 2);
+        assert_eq!(call_idx, 1);
+
+        // 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.
+
+        assert_eq!(func, DaoFunction::Exec);
+    }
+
+    match func {
         DaoFunction::Mint => {
             let params: MintCallParams = deserialize(&self_.data[1..])?;
             let dao_bulla = params.dao_bulla.inner();

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

@@ -47,6 +47,7 @@ pub const DAO_CONTRACT_ZKAS_DAO_PROPOSE_BURN_NS: &str = "DaoProposeInput";
 pub const DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS: &str = "DaoProposeMain";
 
 #[repr(u8)]
+#[derive(PartialEq, Debug)]
 pub enum DaoFunction {
     Mint = 0x00,
     Propose = 0x01,

+ 3 - 3
src/contract/dao/tests/integration.rs

@@ -755,7 +755,7 @@ async fn integration_test() -> Result<()> {
     let dao_coin_blind = pallas::Base::random(&mut OsRng);
     let input_value = treasury_note.value;
     let input_value_blind = pallas::Scalar::random(&mut OsRng);
-    let tx_signature_secret = SecretKey::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) = {
@@ -783,7 +783,7 @@ async fn integration_test() -> Result<()> {
             note: treasury_note,
             user_data_blind,
             value_blind: input_value_blind,
-            signature_secret: tx_signature_secret,
+            signature_secret: xfer_signature_secret,
         }],
         outputs: vec![
             // Sending money
@@ -847,7 +847,7 @@ async fn integration_test() -> Result<()> {
         proofs: vec![xfer_proofs, exec_proofs],
         signatures: vec![],
     };
-    let xfer_sigs = tx.create_sigs(&mut OsRng, &vec![tx_signature_secret])?;
+    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];
 

+ 28 - 2
src/contract/money/src/lib.rs

@@ -426,6 +426,32 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
                     return Err(ContractError::Custom(22))
                 }
 
+                msg!("XXXX: spend_hook = {:?}", input.spend_hook);
+
+                // Check the invoked contract if spend hook is set
+                if !bool::from(input.spend_hook.is_zero()) {
+                    let next_call_idx = call_idx + 1;
+                    if next_call_idx >= call.len() as u32 {
+                        msg!(
+                            "[Transfer] Error: next_call_idx = {} but len(calls) = {} in input {}",
+                            next_call_idx,
+                            call.len(),
+                            i
+                        );
+                        return Err(ContractError::Custom(23))
+                    }
+
+                    let next = &call[next_call_idx as usize];
+                    if next.contract_id.inner() != input.spend_hook {
+                        msg!(
+                            "[Transfer] Error: invoking contract call does not match spend hook\
+                             in input {}",
+                            i
+                        );
+                        return Err(ContractError::Custom(24))
+                    }
+                }
+
                 new_nullifiers.push(input.nullifier);
                 valcom_total += input.value_commit;
             }
@@ -436,7 +462,7 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
                 // TODO: Should we have coins in a sled tree too to check dupes?
                 if new_coins.contains(&Coin::from(output.coin)) {
                     msg!("[Transfer] Error: Duplicate coin found in output {}", i);
-                    return Err(ContractError::Custom(23))
+                    return Err(ContractError::Custom(25))
                 }
 
                 // FIXME: Needs some work on types and their place within all these libraries
@@ -447,7 +473,7 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
             // If the accumulator is not back in its initial state, there's a value mismatch.
             if valcom_total != pallas::Point::identity() {
                 msg!("[Transfer] Error: Value commitments do not result in identity");
-                return Err(ContractError::Custom(24))
+                return Err(ContractError::Custom(26))
             }
 
             // Verify that the token commitments are all for the same token

+ 6 - 2
src/contract/money/src/state.rs

@@ -126,9 +126,13 @@ pub struct Input {
     pub nullifier: Nullifier,
     /// Revealed Merkle root
     pub merkle_root: MerkleNode,
-    /// spend hook (TODO: document)
+    /// Spend hook used to invoke other contracts.
+    /// If this value is nonzero then the subsequent contract call in the tx
+    /// must have this value as its ID.
     pub spend_hook: pallas::Base,
-    /// user data enc (TODO: document)
+    /// Encrypted user data field. An encrypted commitment to arbitrary data.
+    /// When spend hook is set (it is nonzero), then this field may be used
+    /// to pass data to the invoked contract.
     pub user_data_enc: pallas::Base,
     /// Public key for the signature
     pub signature_public: PublicKey,