Przeglądaj źródła

contract/consensus: UnstakeV1 functionality added

aggstam 3 lat temu
rodzic
commit
08190df5ae

+ 23 - 1
src/contract/consensus/src/entrypoint.rs

@@ -31,7 +31,10 @@ use darkfi_sdk::{
 };
 };
 use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
 
-use crate::{model::ConsensusStakeUpdateV1, ConsensusFunction};
+use crate::{
+    model::{ConsensusStakeUpdateV1, ConsensusUnstakeUpdateV1},
+    ConsensusFunction,
+};
 
 
 /// `Consensus::Stake` functions
 /// `Consensus::Stake` functions
 mod stake_v1;
 mod stake_v1;
@@ -40,6 +43,13 @@ use stake_v1::{
     consensus_stake_process_update_v1,
     consensus_stake_process_update_v1,
 };
 };
 
 
+/// `Consensus::Unstake` functions
+mod unstake_v1;
+use unstake_v1::{
+    consensus_unstake_get_metadata_v1, consensus_unstake_process_instruction_v1,
+    consensus_unstake_process_update_v1,
+};
+
 darkfi_sdk::define_contract!(
 darkfi_sdk::define_contract!(
     init: init_contract,
     init: init_contract,
     exec: process_instruction,
     exec: process_instruction,
@@ -147,6 +157,10 @@ fn get_metadata(cid: ContractId, ix: &[u8]) -> ContractResult {
             let metadata = consensus_stake_get_metadata_v1(cid, call_idx, calls)?;
             let metadata = consensus_stake_get_metadata_v1(cid, call_idx, calls)?;
             Ok(set_return_data(&metadata)?)
             Ok(set_return_data(&metadata)?)
         }
         }
+        ConsensusFunction::UnstakeV1 => {
+            let metadata = consensus_unstake_get_metadata_v1(cid, call_idx, calls)?;
+            Ok(set_return_data(&metadata)?)
+        }
     }
     }
 }
 }
 
 
@@ -170,6 +184,10 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
             let update_data = consensus_stake_process_instruction_v1(cid, call_idx, calls)?;
             let update_data = consensus_stake_process_instruction_v1(cid, call_idx, calls)?;
             Ok(set_return_data(&update_data)?)
             Ok(set_return_data(&update_data)?)
         }
         }
+        ConsensusFunction::UnstakeV1 => {
+            let update_data = consensus_unstake_process_instruction_v1(cid, call_idx, calls)?;
+            Ok(set_return_data(&update_data)?)
+        }
     }
     }
 }
 }
 
 
@@ -183,5 +201,9 @@ fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
             let update: ConsensusStakeUpdateV1 = deserialize(&update_data[1..])?;
             let update: ConsensusStakeUpdateV1 = deserialize(&update_data[1..])?;
             Ok(consensus_stake_process_update_v1(cid, update)?)
             Ok(consensus_stake_process_update_v1(cid, update)?)
         }
         }
+        ConsensusFunction::UnstakeV1 => {
+            let update: ConsensusUnstakeUpdateV1 = deserialize(&update_data[1..])?;
+            Ok(consensus_unstake_process_update_v1(cid, update)?)
+        }
     }
     }
 }
 }

+ 175 - 0
src/contract/consensus/src/entrypoint/unstake_v1.rs

@@ -0,0 +1,175 @@
+/* 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::{
+    error::MoneyError, CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_NULLIFIERS_TREE,
+    CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1,
+};
+use darkfi_sdk::{
+    crypto::{
+        pasta_prelude::*, pedersen_commitment_base, ContractId, PublicKey, DARK_TOKEN_ID,
+        MONEY_CONTRACT_ID,
+    },
+    db::{db_contains_key, db_lookup, db_set},
+    error::{ContractError, ContractResult},
+    msg,
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
+
+use crate::{
+    model::{ConsensusUnstakeParamsV1, ConsensusUnstakeUpdateV1},
+    ConsensusFunction,
+};
+
+/// `get_metadata` function for `Consensus::UnstakeV1`
+pub(crate) fn consensus_unstake_get_metadata_v1(
+    _cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: ConsensusUnstakeParamsV1 = deserialize(&self_.data[1..])?;
+
+    // Public inputs for the ZK proofs we have to verify
+    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 commitments and signature pubkeys from the
+    // anonymous input
+    let input = &params.input;
+    let value_coords = input.value_commit.to_affine().coordinates().unwrap();
+    let token_coords = input.token_commit.to_affine().coordinates().unwrap();
+    let (sig_x, sig_y) = input.signature_public.xy();
+
+    // It is very important that these are in the same order as the
+    // `constrain_instance` calls in the zkas code.
+    // Otherwise verification will fail.
+    zk_public_inputs.push((
+        CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1.to_string(),
+        vec![
+            input.nullifier.inner(),
+            *value_coords.x(),
+            *value_coords.y(),
+            *token_coords.x(),
+            *token_coords.y(),
+            input.merkle_root.inner(),
+            input.user_data_enc,
+            sig_x,
+            sig_y,
+        ],
+    ));
+
+    signature_pubkeys.push(input.signature_public);
+
+    // Serialize everything gathered and return it
+    let mut metadata = vec![];
+    zk_public_inputs.encode(&mut metadata)?;
+    signature_pubkeys.encode(&mut metadata)?;
+
+    Ok(metadata)
+}
+
+/// `process_instruction` function for `Consensus::UnstakeV1`
+pub(crate) fn consensus_unstake_process_instruction_v1(
+    cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: ConsensusUnstakeParamsV1 = deserialize(&self_.data[1..])?;
+
+    // Access the necessary databases where there is information to
+    // validate this state transition.
+    let nullifiers_db = db_lookup(cid, CONSENSUS_CONTRACT_NULLIFIERS_TREE)?;
+    let coin_roots_db = db_lookup(cid, CONSENSUS_CONTRACT_COIN_ROOTS_TREE)?;
+
+    // ===================================
+    // Perform the actual state transition
+    // ===================================
+
+    msg!("[ConsensusUnstakeV1] Validating anonymous input");
+    let input = &params.input;
+
+    // Only native token can be unstaked
+    if input.token_commit != pedersen_commitment_base(DARK_TOKEN_ID.inner(), params.token_blind) {
+        msg!("[ConsensusUnstakeV1] Error: Input used non-native token");
+        return Err(MoneyError::StakeInputNonNativeToken.into())
+    }
+
+    // The Merkle root is used to know whether this is a coin that
+    // existed in a previous state.
+    if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
+        msg!("[ConsensusUnstakeV1] Error: Merkle root not found in previous state");
+        return Err(MoneyError::TransferMerkleRootNotFound.into())
+    }
+
+    // The nullifiers should not already exist. It is the double-spend protection.
+    if db_contains_key(nullifiers_db, &serialize(&input.nullifier))? {
+        msg!("[ConsensusUnstakeV1] Error: Duplicate nullifier found");
+        return Err(MoneyError::DuplicateNullifier.into())
+    }
+
+    // Check if spend hook is set and its correctness
+    if input.spend_hook == pallas::Base::zero() {
+        msg!("[ConsensusUnstakeV1] Error: Missing spend hook");
+        return Err(MoneyError::StakeMissingSpendHook.into())
+    }
+
+    let next_call_idx = call_idx + 1;
+    if next_call_idx >= calls.len() as u32 {
+        msg!("[ConsensusUnstakeV1] 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() != input.spend_hook {
+        msg!("[ConsensusUnstakeV1] Error: Invoking contract call does not match spend hook");
+        return Err(MoneyError::SpendHookMismatch.into())
+    }
+
+    if input.spend_hook != MONEY_CONTRACT_ID.inner() {
+        msg!("[ConsensusUnstakeV1] Error: Spend hook is not money contract");
+        return Err(MoneyError::UnstakeSpendHookNotMoneyContract.into())
+    }
+
+    // At this point the state transition has passed, so we create a state update
+    let update = ConsensusUnstakeUpdateV1 { nullifier: input.nullifier };
+    let mut update_data = vec![];
+    update_data.write_u8(ConsensusFunction::UnstakeV1 as u8)?;
+    update.encode(&mut update_data)?;
+
+    // and return it
+    Ok(update_data)
+}
+
+/// `process_update` function for `Consensus::UnstakeV1`
+pub(crate) fn consensus_unstake_process_update_v1(
+    cid: ContractId,
+    update: ConsensusUnstakeUpdateV1,
+) -> ContractResult {
+    // Grab all necessary db handles for where we want to write
+    let nullifiers_db = db_lookup(cid, CONSENSUS_CONTRACT_NULLIFIERS_TREE)?;
+
+    msg!("[ConsensusUnstakeV1] Adding new nullifier to the set");
+    db_set(nullifiers_db, &serialize(&update.nullifier), &[])?;
+
+    Ok(())
+}

+ 2 - 5
src/contract/consensus/src/lib.rs

@@ -19,9 +19,6 @@
 //! Smart contract implementing staking, unstaking and evolving
 //! Smart contract implementing staking, unstaking and evolving
 //! of consensus tokens.
 //! of consensus tokens.
 
 
-//! Smart contract implementing money transfers, atomic swaps, token
-//! minting and freezing, and staking/unstaking of consensus tokens.
-
 use darkfi_sdk::error::ContractError;
 use darkfi_sdk::error::ContractError;
 
 
 /// Functions available in the contract
 /// Functions available in the contract
@@ -29,7 +26,7 @@ use darkfi_sdk::error::ContractError;
 pub enum ConsensusFunction {
 pub enum ConsensusFunction {
     StakeV1 = 0x00,
     StakeV1 = 0x00,
     //EvolveV1 = 0x01,
     //EvolveV1 = 0x01,
-    //UnstakeV1 = 0x02,
+    UnstakeV1 = 0x02,
 }
 }
 
 
 impl TryFrom<u8> for ConsensusFunction {
 impl TryFrom<u8> for ConsensusFunction {
@@ -39,7 +36,7 @@ impl TryFrom<u8> for ConsensusFunction {
         match b {
         match b {
             0x00 => Ok(Self::StakeV1),
             0x00 => Ok(Self::StakeV1),
             //0x01 => Ok(Self::EvolveV1),
             //0x01 => Ok(Self::EvolveV1),
-            //0x02 => Ok(Self::UnstakeV1),
+            0x02 => Ok(Self::UnstakeV1),
             _ => Err(ContractError::InvalidFunction),
             _ => Err(ContractError::InvalidFunction),
         }
         }
     }
     }

+ 21 - 2
src/contract/consensus/src/model.rs

@@ -16,8 +16,11 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
  */
 
 
-use darkfi_money_contract::model::{Output, StakeInput};
-use darkfi_sdk::crypto::Coin;
+use darkfi_money_contract::model::{Input, Output, StakeInput};
+use darkfi_sdk::{
+    crypto::{Coin, Nullifier},
+    pasta::pallas,
+};
 use darkfi_serial::{SerialDecodable, SerialEncodable};
 use darkfi_serial::{SerialDecodable, SerialEncodable};
 
 
 /// Parameters for `Consensus::Stake`
 /// Parameters for `Consensus::Stake`
@@ -35,3 +38,19 @@ pub struct ConsensusStakeUpdateV1 {
     /// The newly minted coin
     /// The newly minted coin
     pub coin: 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,
+}

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

@@ -145,7 +145,7 @@ pub(crate) fn money_stake_process_instruction_v1(
 
 
     if input.spend_hook != CONSENSUS_CONTRACT_ID.inner() {
     if input.spend_hook != CONSENSUS_CONTRACT_ID.inner() {
         msg!("[MoneyStakeV1] Error: Spend hook is not consensus contract");
         msg!("[MoneyStakeV1] Error: Spend hook is not consensus contract");
-        return Err(MoneyError::StakeSpendHookNonConsensusContract.into())
+        return Err(MoneyError::StakeSpendHookNotConsensusContract.into())
     }
     }
 
 
     // At this point the state transition has passed, so we create a state update
     // At this point the state transition has passed, so we create a state update

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

@@ -81,7 +81,10 @@ pub enum MoneyError {
     StakeMissingSpendHook,
     StakeMissingSpendHook,
 
 
     #[error("Spend hook is not consensus contract")]
     #[error("Spend hook is not consensus contract")]
-    StakeSpendHookNonConsensusContract,
+    StakeSpendHookNotConsensusContract,
+
+    #[error("Spend hook is not money contract")]
+    UnstakeSpendHookNotMoneyContract,
 }
 }
 
 
 impl From<MoneyError> for ContractError {
 impl From<MoneyError> for ContractError {
@@ -107,7 +110,8 @@ impl From<MoneyError> for ContractError {
             MoneyError::MintFrozen => Self::Custom(18),
             MoneyError::MintFrozen => Self::Custom(18),
             MoneyError::StakeInputNonNativeToken => Self::Custom(19),
             MoneyError::StakeInputNonNativeToken => Self::Custom(19),
             MoneyError::StakeMissingSpendHook => Self::Custom(20),
             MoneyError::StakeMissingSpendHook => Self::Custom(20),
-            MoneyError::StakeSpendHookNonConsensusContract => Self::Custom(21),
+            MoneyError::StakeSpendHookNotConsensusContract => Self::Custom(21),
+            MoneyError::UnstakeSpendHookNotMoneyContract => Self::Custom(21),
         }
         }
     }
     }
 }
 }