浏览代码

contract/money: UnstakeV1 fixed

aggstam 3 年之前
父节点
当前提交
f3fdea0664

+ 1 - 3
src/contract/consensus/src/client/stake_v1.rs

@@ -25,7 +25,7 @@ use darkfi::{
 };
 };
 use darkfi_money_contract::{
 use darkfi_money_contract::{
     client::{MoneyNote, OwnCoin},
     client::{MoneyNote, OwnCoin},
-    model::{Output, StakeInput},
+    model::{ConsensusStakeParamsV1, Output, StakeInput},
 };
 };
 use darkfi_sdk::{
 use darkfi_sdk::{
     crypto::{
     crypto::{
@@ -38,8 +38,6 @@ use darkfi_sdk::{
 use log::{debug, info};
 use log::{debug, info};
 use rand::rngs::OsRng;
 use rand::rngs::OsRng;
 
 
-use crate::model::ConsensusStakeParamsV1;
-
 pub struct ConsensusStakeCallDebris {
 pub struct ConsensusStakeCallDebris {
     pub params: ConsensusStakeParamsV1,
     pub params: ConsensusStakeParamsV1,
     pub proofs: Vec<Proof>,
     pub proofs: Vec<Proof>,

+ 1 - 3
src/contract/consensus/src/client/unstake_v1.rs

@@ -25,7 +25,7 @@ use darkfi::{
 };
 };
 use darkfi_money_contract::{
 use darkfi_money_contract::{
     client::{MoneyNote, OwnCoin},
     client::{MoneyNote, OwnCoin},
-    model::Input,
+    model::{ConsensusUnstakeParamsV1, Input},
 };
 };
 use darkfi_sdk::{
 use darkfi_sdk::{
     crypto::{
     crypto::{
@@ -38,8 +38,6 @@ use darkfi_sdk::{
 use log::{debug, info};
 use log::{debug, info};
 use rand::rngs::OsRng;
 use rand::rngs::OsRng;
 
 
-use crate::model::ConsensusUnstakeParamsV1;
-
 pub struct ConsensusUnstakeCallDebris {
 pub struct ConsensusUnstakeCallDebris {
     pub params: ConsensusUnstakeParamsV1,
     pub params: ConsensusUnstakeParamsV1,
     pub proofs: Vec<Proof>,
     pub proofs: Vec<Proof>,

+ 2 - 4
src/contract/consensus/src/entrypoint.rs

@@ -17,6 +17,7 @@
  */
  */
 
 
 use darkfi_money_contract::{
 use darkfi_money_contract::{
+    model::{ConsensusStakeUpdateV1, ConsensusUnstakeUpdateV1},
     CONSENSUS_CONTRACT_COINS_TREE, CONSENSUS_CONTRACT_COIN_MERKLE_TREE,
     CONSENSUS_CONTRACT_COINS_TREE, CONSENSUS_CONTRACT_COIN_MERKLE_TREE,
     CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_DB_VERSION,
     CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_DB_VERSION,
     CONSENSUS_CONTRACT_INFO_TREE, CONSENSUS_CONTRACT_NULLIFIERS_TREE,
     CONSENSUS_CONTRACT_INFO_TREE, CONSENSUS_CONTRACT_NULLIFIERS_TREE,
@@ -31,10 +32,7 @@ use darkfi_sdk::{
 };
 };
 use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
 
-use crate::{
-    model::{ConsensusStakeUpdateV1, ConsensusUnstakeUpdateV1},
-    ConsensusFunction,
-};
+use crate::ConsensusFunction;
 
 
 /// `Consensus::Stake` functions
 /// `Consensus::Stake` functions
 mod stake_v1;
 mod stake_v1;

+ 7 - 8
src/contract/consensus/src/entrypoint/stake_v1.rs

@@ -17,10 +17,12 @@
  */
  */
 
 
 use darkfi_money_contract::{
 use darkfi_money_contract::{
-    error::MoneyError, model::MoneyStakeParamsV1, CONSENSUS_CONTRACT_COINS_TREE,
-    CONSENSUS_CONTRACT_COIN_MERKLE_TREE, CONSENSUS_CONTRACT_COIN_ROOTS_TREE,
-    CONSENSUS_CONTRACT_INFO_TREE, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1,
-    MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_NULLIFIERS_TREE,
+    error::MoneyError,
+    model::{ConsensusStakeParamsV1, ConsensusStakeUpdateV1, MoneyStakeParamsV1},
+    CONSENSUS_CONTRACT_COINS_TREE, CONSENSUS_CONTRACT_COIN_MERKLE_TREE,
+    CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_INFO_TREE,
+    CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1, MONEY_CONTRACT_COIN_ROOTS_TREE,
+    MONEY_CONTRACT_NULLIFIERS_TREE,
 };
 };
 use darkfi_sdk::{
 use darkfi_sdk::{
     crypto::{
     crypto::{
@@ -35,10 +37,7 @@ use darkfi_sdk::{
 };
 };
 use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
 
-use crate::{
-    model::{ConsensusStakeParamsV1, ConsensusStakeUpdateV1},
-    ConsensusFunction,
-};
+use crate::ConsensusFunction;
 
 
 /// `get_metadata` function for `Consensus::StakeV1`
 /// `get_metadata` function for `Consensus::StakeV1`
 pub(crate) fn consensus_stake_get_metadata_v1(
 pub(crate) fn consensus_stake_get_metadata_v1(

+ 5 - 6
src/contract/consensus/src/entrypoint/unstake_v1.rs

@@ -17,8 +17,10 @@
  */
  */
 
 
 use darkfi_money_contract::{
 use darkfi_money_contract::{
-    error::MoneyError, model::MoneyUnstakeParamsV1, CONSENSUS_CONTRACT_COIN_ROOTS_TREE,
-    CONSENSUS_CONTRACT_NULLIFIERS_TREE, CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1,
+    error::MoneyError,
+    model::{ConsensusUnstakeParamsV1, ConsensusUnstakeUpdateV1, MoneyUnstakeParamsV1},
+    CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_NULLIFIERS_TREE,
+    CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1,
 };
 };
 use darkfi_sdk::{
 use darkfi_sdk::{
     crypto::{
     crypto::{
@@ -33,10 +35,7 @@ use darkfi_sdk::{
 };
 };
 use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
 
-use crate::{
-    model::{ConsensusUnstakeParamsV1, ConsensusUnstakeUpdateV1},
-    ConsensusFunction,
-};
+use crate::ConsensusFunction;
 
 
 /// `get_metadata` function for `Consensus::UnstakeV1`
 /// `get_metadata` function for `Consensus::UnstakeV1`
 pub(crate) fn consensus_unstake_get_metadata_v1(
 pub(crate) fn consensus_unstake_get_metadata_v1(

+ 0 - 3
src/contract/consensus/src/lib.rs

@@ -42,9 +42,6 @@ impl TryFrom<u8> for ConsensusFunction {
     }
     }
 }
 }
 
 
-/// Call parameters definitions
-pub mod model;
-
 #[cfg(not(feature = "no-entrypoint"))]
 #[cfg(not(feature = "no-entrypoint"))]
 /// WASM entrypoint functions
 /// WASM entrypoint functions
 pub mod entrypoint;
 pub mod entrypoint;

+ 0 - 56
src/contract/consensus/src/model.rs

@@ -1,56 +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_money_contract::model::{Input, Output, StakeInput};
-use darkfi_sdk::{
-    crypto::{Coin, Nullifier},
-    pasta::pallas,
-};
-use darkfi_serial::{SerialDecodable, SerialEncodable};
-
-/// Parameters for `Consensus::Stake`
-#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct ConsensusStakeParamsV1 {
-    /// Burnt token revealed info
-    pub input: StakeInput,
-    /// Anonymous output
-    pub output: Output,
-}
-
-/// State update for `Consensus::Stake`
-#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct ConsensusStakeUpdateV1 {
-    /// The newly minted coin
-    pub coin: Coin,
-}
-
-/// Parameters for `Consensus::Unstake`
-#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct ConsensusUnstakeParamsV1 {
-    /// Blinding factor for `token_id`
-    pub token_blind: pallas::Scalar,
-    /// Anonymous input
-    pub input: Input,
-}
-
-/// State update for `Consensus::Unstake`
-#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct ConsensusUnstakeUpdateV1 {
-    /// Revealed nullifier
-    pub nullifier: Nullifier,
-}

+ 5 - 2
src/contract/consensus/tests/stake_unstake.rs

@@ -244,8 +244,11 @@ async fn consensus_contract_stake_unstake() -> Result<()> {
     let calls = vec![consensus_call, money_call];
     let calls = vec![consensus_call, money_call];
     let proofs = vec![alice_consensus_unstake_proofs, alice_money_unstake_proofs];
     let proofs = vec![alice_consensus_unstake_proofs, alice_money_unstake_proofs];
     let mut alice_unstake_tx = Transaction { calls, proofs, signatures: vec![] };
     let mut alice_unstake_tx = Transaction { calls, proofs, signatures: vec![] };
-    let sigs = alice_unstake_tx.create_sigs(&mut OsRng, &[alice_consensus_unstake_secret_key])?;
-    alice_unstake_tx.signatures = vec![sigs];
+    let consensus_sigs =
+        alice_unstake_tx.create_sigs(&mut OsRng, &[alice_consensus_unstake_secret_key])?;
+    let money_sigs =
+        alice_unstake_tx.create_sigs(&mut OsRng, &[alice_consensus_unstake_secret_key])?;
+    alice_unstake_tx.signatures = vec![consensus_sigs, money_sigs];
 
 
     info!(target: "consensus", "[Faucet] ==========================");
     info!(target: "consensus", "[Faucet] ==========================");
     info!(target: "consensus", "[Faucet] Executing Alice unstake tx");
     info!(target: "consensus", "[Faucet] Executing Alice unstake tx");

+ 5 - 5
src/contract/money/src/client/unstake_v1.rs

@@ -110,8 +110,8 @@ impl MoneyUnstakeCallBuilder {
         debug!("Finished building output");
         debug!("Finished building output");
 
 
         let serial = pallas::Base::random(&mut OsRng);
         let serial = pallas::Base::random(&mut OsRng);
-        let spend_hook = DARK_TOKEN_ID.inner();
-        let user_data = pallas::Base::random(&mut OsRng);
+        let spend_hook = pallas::Base::zero();
+        let user_data_enc = pallas::Base::zero();
         let coin_blind = pallas::Base::random(&mut OsRng);
         let coin_blind = pallas::Base::random(&mut OsRng);
 
 
         info!("Creating unstake mint proof for output");
         info!("Creating unstake mint proof for output");
@@ -123,7 +123,7 @@ impl MoneyUnstakeCallBuilder {
             self.token_blind,
             self.token_blind,
             serial,
             serial,
             spend_hook,
             spend_hook,
-            user_data,
+            user_data_enc,
             coin_blind,
             coin_blind,
         )?;
         )?;
 
 
@@ -133,7 +133,7 @@ impl MoneyUnstakeCallBuilder {
             value: output.value,
             value: output.value,
             token_id: output.token_id,
             token_id: output.token_id,
             spend_hook,
             spend_hook,
-            user_data,
+            user_data: user_data_enc,
             coin_blind,
             coin_blind,
             value_blind: self.value_blind,
             value_blind: self.value_blind,
             token_blind: self.token_blind,
             token_blind: self.token_blind,
@@ -158,7 +158,7 @@ impl MoneyUnstakeCallBuilder {
         };
         };
 
 
         // We now fill this with necessary stuff
         // We now fill this with necessary stuff
-        let params = MoneyUnstakeParamsV1 { input, output };
+        let params = MoneyUnstakeParamsV1 { input, spend_hook, user_data_enc, output };
         let proofs = vec![proof];
         let proofs = vec![proof];
 
 
         // Now we should have all the params and zk proof.
         // Now we should have all the params and zk proof.

+ 2 - 4
src/contract/money/src/entrypoint/stake_v1.rs

@@ -31,7 +31,7 @@ use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
 
 use crate::{
 use crate::{
     error::MoneyError,
     error::MoneyError,
-    model::{MoneyStakeParamsV1, MoneyStakeUpdateV1, MoneyUnstakeParamsV1},
+    model::{ConsensusStakeParamsV1, MoneyStakeParamsV1, MoneyStakeUpdateV1},
     MoneyFunction, MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_NULLIFIERS_TREE,
     MoneyFunction, MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_NULLIFIERS_TREE,
     MONEY_CONTRACT_ZKAS_BURN_NS_V1,
     MONEY_CONTRACT_ZKAS_BURN_NS_V1,
 };
 };
@@ -151,9 +151,7 @@ pub(crate) fn money_stake_process_instruction_v1(
     }
     }
 
 
     // Verify next call StakeInput is the same as this calls input
     // Verify next call StakeInput is the same as this calls input
-    // Note: ConsensusStakeParamsV1 is the same as MoneyUnstakeParamsV1
-    // TODO: maybe create a common models src folder accessible by all contracts?
-    let next_params: MoneyUnstakeParamsV1 = deserialize(&next.data[1..])?;
+    let next_params: ConsensusStakeParamsV1 = deserialize(&next.data[1..])?;
     if input != &next_params.input {
     if input != &next_params.input {
         msg!("[MoneyStakeV1] Error: Next call input mismatch");
         msg!("[MoneyStakeV1] Error: Next call input mismatch");
         return Err(MoneyError::NextCallInputMissmatch.into())
         return Err(MoneyError::NextCallInputMissmatch.into())

+ 56 - 8
src/contract/money/src/entrypoint/unstake_v1.rs

@@ -18,7 +18,7 @@
 
 
 use darkfi_sdk::{
 use darkfi_sdk::{
     crypto::{
     crypto::{
-        pasta_prelude::*, pedersen_commitment_base, Coin, ContractId, MerkleNode,
+        pasta_prelude::*, pedersen_commitment_base, Coin, ContractId, MerkleNode, PublicKey,
         CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID,
         CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID,
     },
     },
     db::{db_contains_key, db_lookup, db_set},
     db::{db_contains_key, db_lookup, db_set},
@@ -31,7 +31,7 @@ use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
 
 use crate::{
 use crate::{
     error::MoneyError,
     error::MoneyError,
-    model::{MoneyUnstakeParamsV1, MoneyUnstakeUpdateV1},
+    model::{ConsensusUnstakeParamsV1, MoneyUnstakeParamsV1, MoneyUnstakeUpdateV1},
     MoneyFunction, CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_NULLIFIERS_TREE,
     MoneyFunction, CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_NULLIFIERS_TREE,
     MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE, MONEY_CONTRACT_COIN_ROOTS_TREE,
     MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE, MONEY_CONTRACT_COIN_ROOTS_TREE,
     MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
     MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
@@ -48,6 +48,8 @@ pub(crate) fn money_unstake_get_metadata_v1(
 
 
     // Public inputs for the ZK proofs we have to verify
     // Public inputs for the ZK proofs we have to verify
     let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
     let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
+    // Public keys for the transaction signatures we have to verify
+    let mut signature_pubkeys: Vec<PublicKey> = vec![];
 
 
     // Grab the pedersen commitment from the anonymous output
     // Grab the pedersen commitment from the anonymous output
     let output = &params.output;
     let output = &params.output;
@@ -65,9 +67,12 @@ pub(crate) fn money_unstake_get_metadata_v1(
         ],
         ],
     ));
     ));
 
 
+    signature_pubkeys.push(params.input.signature_public);
+
     // Serialize everything gathered and return it
     // Serialize everything gathered and return it
     let mut metadata = vec![];
     let mut metadata = vec![];
     zk_public_inputs.encode(&mut metadata)?;
     zk_public_inputs.encode(&mut metadata)?;
+    signature_pubkeys.encode(&mut metadata)?;
 
 
     Ok(metadata)
     Ok(metadata)
 }
 }
@@ -97,7 +102,7 @@ pub(crate) fn money_unstake_process_instruction_v1(
     let input = &params.input;
     let input = &params.input;
     let output = &params.output;
     let output = &params.output;
 
 
-    // Only native token can be staked
+    // Only native token can be unstaked
     if output.token_commit != pedersen_commitment_base(DARK_TOKEN_ID.inner(), input.token_blind) {
     if output.token_commit != pedersen_commitment_base(DARK_TOKEN_ID.inner(), input.token_blind) {
         msg!("[MoneyUnstakeV1] Error: Input used non-native token");
         msg!("[MoneyUnstakeV1] Error: Input used non-native token");
         return Err(MoneyError::StakeInputNonNativeToken.into())
         return Err(MoneyError::StakeInputNonNativeToken.into())
@@ -117,18 +122,61 @@ pub(crate) fn money_unstake_process_instruction_v1(
     }
     }
 
 
     // The nullifiers should already exist. It is the double-mint protection.
     // The nullifiers should already exist. It is the double-mint protection.
-    if !db_contains_key(consensus_nullifiers_db, &serialize(&input.nullifier))? {
+    if db_contains_key(consensus_nullifiers_db, &serialize(&input.nullifier))? {
         msg!("[MoneyUnstakeV1] Error: Duplicate nullifier found");
         msg!("[MoneyUnstakeV1] Error: Duplicate nullifier found");
         return Err(MoneyError::DuplicateNullifier.into())
         return Err(MoneyError::DuplicateNullifier.into())
     }
     }
 
 
-    // Check caller matches stake spend hook and its correctness
-    let caller = &calls[call_idx as usize];
-    if caller.contract_id.inner() != CONSENSUS_CONTRACT_ID.inner() {
-        msg!("[MoneyUnstakeV1] Error: Invoking contract call does not match spend hook");
+    // Check previous call is consensus contract
+    if call_idx == 0 {
+        msg!("[MoneyUnstakeV1] Error: previous_call_idx will be out of bounds");
+        return Err(MoneyError::SpendHookOutOfBounds.into())
+    }
+
+    let previous_call_idx = call_idx - 1;
+    let previous = &calls[previous_call_idx as usize];
+    if previous.contract_id.inner() != CONSENSUS_CONTRACT_ID.inner() {
+        msg!("[MoneyUnstakeV1] Error: Previous contract call is not consensus contract");
+        return Err(MoneyError::UnstakePreviousCallNotConsensusContract.into())
+    }
+
+    // Verify previous call corresponds to Consensus::UnstakeV1 (0x02)
+    if previous.data[0] != 0x02 {
+        msg!("[MoneyUnstakeV1] Error: Previous call function mismatch");
+        return Err(MoneyError::PreviousCallFunctionMissmatch.into())
+    }
+
+    // Verify previous call input is the same as this calls StakeInput
+    let previous_params: ConsensusUnstakeParamsV1 = deserialize(&previous.data[1..])?;
+    let previous_input = &previous_params.input;
+    if &previous_input != &input {
+        msg!("[MoneyUnstakeV1] Error: Previous call input mismatch");
+        return Err(MoneyError::PreviousCallInputMissmatch.into())
+    }
+
+    // Check spend hook correctness
+    if previous_input.spend_hook != CONSENSUS_CONTRACT_ID.inner() {
+        msg!("[MoneyUnstakeV1] Error: Invoking contract call does not match spend hook in input");
         return Err(MoneyError::SpendHookMismatch.into())
         return Err(MoneyError::SpendHookMismatch.into())
     }
     }
 
 
+    // If next spend hook is set, check its correctness
+    if params.spend_hook != pallas::Base::zero() {
+        let next_call_idx = call_idx + 1;
+        if next_call_idx >= calls.len() as u32 {
+            msg!("[MoneyUnstakeV1] Error: next_call_idx out of bounds");
+            return Err(MoneyError::SpendHookOutOfBounds.into())
+        }
+
+        let next = &calls[next_call_idx as usize];
+        if next.contract_id.inner() != params.spend_hook {
+            msg!(
+                "[MoneyUnstakeV1] Error: Invoking contract call does not match spend hook in input"
+            );
+            return Err(MoneyError::SpendHookMismatch.into())
+        }
+    }
+
     // Newly created coin for this call is in the output. Here we gather it,
     // Newly created coin for this call is in the output. Here we gather it,
     // and we also check that it hasn't existed before.
     // and we also check that it hasn't existed before.
     if db_contains_key(money_coins_db, &serialize(&output.coin))? {
     if db_contains_key(money_coins_db, &serialize(&output.coin))? {

+ 8 - 4
src/contract/money/src/error.rs

@@ -95,6 +95,9 @@ pub enum MoneyError {
     #[error("Next contract call is not money contract")]
     #[error("Next contract call is not money contract")]
     UnstakeNextCallNotMoneyContract,
     UnstakeNextCallNotMoneyContract,
 
 
+    #[error("Previous contract call is not consensus contract")]
+    UnstakePreviousCallNotConsensusContract,
+
     #[error("Next call function mismatch")]
     #[error("Next call function mismatch")]
     NextCallFunctionMissmatch,
     NextCallFunctionMissmatch,
 
 
@@ -136,10 +139,11 @@ impl From<MoneyError> for ContractError {
             MoneyError::StakePreviousCallNotMoneyContract => Self::Custom(23),
             MoneyError::StakePreviousCallNotMoneyContract => Self::Custom(23),
             MoneyError::UnstakeSpendHookNotConsensusContract => Self::Custom(24),
             MoneyError::UnstakeSpendHookNotConsensusContract => Self::Custom(24),
             MoneyError::UnstakeNextCallNotMoneyContract => Self::Custom(25),
             MoneyError::UnstakeNextCallNotMoneyContract => Self::Custom(25),
-            MoneyError::NextCallFunctionMissmatch => Self::Custom(26),
-            MoneyError::NextCallInputMissmatch => Self::Custom(27),
-            MoneyError::PreviousCallFunctionMissmatch => Self::Custom(28),
-            MoneyError::PreviousCallInputMissmatch => Self::Custom(29),
+            MoneyError::UnstakePreviousCallNotConsensusContract => Self::Custom(26),
+            MoneyError::NextCallFunctionMissmatch => Self::Custom(27),
+            MoneyError::NextCallInputMissmatch => Self::Custom(28),
+            MoneyError::PreviousCallFunctionMissmatch => Self::Custom(29),
+            MoneyError::PreviousCallInputMissmatch => Self::Custom(30),
         }
         }
     }
     }
 }
 }

+ 40 - 0
src/contract/money/src/model.rs

@@ -169,6 +169,14 @@ pub struct MoneyStakeUpdateV1 {
 pub struct MoneyUnstakeParamsV1 {
 pub struct MoneyUnstakeParamsV1 {
     /// Burnt token revealed info
     /// Burnt token revealed info
     pub input: StakeInput,
     pub input: StakeInput,
+    /// 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,
+    /// Encrypted user data field. An encrypted commitment to arbitrary data.
+    /// When spend hook is set (it is nonzero), then this field may be user
+    /// to pass data to the invoked contract.
+    pub user_data_enc: pallas::Base,
     /// Anonymous output
     /// Anonymous output
     pub output: Output,
     pub output: Output,
 }
 }
@@ -179,3 +187,35 @@ pub struct MoneyUnstakeUpdateV1 {
     /// The newly minted coin
     /// The newly minted coin
     pub coin: Coin,
     pub coin: Coin,
 }
 }
+
+/// Parameters for `Consensus::Stake`
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct ConsensusStakeParamsV1 {
+    /// Burnt token revealed info
+    pub input: StakeInput,
+    /// Anonymous output
+    pub output: Output,
+}
+
+/// State update for `Consensus::Stake`
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct ConsensusStakeUpdateV1 {
+    /// The newly minted coin
+    pub coin: Coin,
+}
+
+/// Parameters for `Consensus::Unstake`
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct ConsensusUnstakeParamsV1 {
+    /// Blinding factor for `token_id`
+    pub token_blind: pallas::Scalar,
+    /// Anonymous input
+    pub input: Input,
+}
+
+/// State update for `Consensus::Unstake`
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct ConsensusUnstakeUpdateV1 {
+    /// Revealed nullifier
+    pub nullifier: Nullifier,
+}