Przeglądaj źródła

contract/money: UnstakeV1 functionality added

aggstam 3 lat temu
rodzic
commit
06c447098e

+ 2 - 2
src/contract/consensus/src/client/stake_v1.rs

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

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

@@ -16,7 +16,13 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use darkfi_money_contract::{MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1};
+use darkfi_money_contract::{
+    CONSENSUS_CONTRACT_COINS_TREE, CONSENSUS_CONTRACT_COIN_MERKLE_TREE,
+    CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_DB_VERSION,
+    CONSENSUS_CONTRACT_INFO_TREE, CONSENSUS_CONTRACT_NULLIFIERS_TREE,
+    CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1,
+    MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+};
 use darkfi_sdk::{
     crypto::{ContractId, MerkleTree},
     db::{db_init, db_lookup, db_set, set_return_data, SMART_CONTRACT_ZKAS_DB_NAME},
@@ -25,13 +31,7 @@ use darkfi_sdk::{
 };
 use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
-use crate::{
-    model::ConsensusStakeUpdateV1, ConsensusFunction, CONSENSUS_CONTRACT_COINS_TREE,
-    CONSENSUS_CONTRACT_COIN_MERKLE_TREE, CONSENSUS_CONTRACT_COIN_ROOTS_TREE,
-    CONSENSUS_CONTRACT_DB_VERSION, CONSENSUS_CONTRACT_INFO_TREE,
-    CONSENSUS_CONTRACT_NULLIFIERS_TREE, CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1,
-    CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1,
-};
+use crate::{model::ConsensusStakeUpdateV1, ConsensusFunction};
 
 /// `Consensus::Stake` functions
 mod stake_v1;

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

@@ -17,12 +17,15 @@
  */
 
 use darkfi_money_contract::{
-    error::MoneyError, MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_NULLIFIERS_TREE,
+    error::MoneyError, 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::{
     crypto::{
-        pasta_prelude::*, pedersen_commitment_base, Coin, ContractId, MerkleNode,
-        CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID, MONEY_CONTRACT_ID,
+        pasta_prelude::*, pedersen_commitment_base, Coin, ContractId, MerkleNode, DARK_TOKEN_ID,
+        MONEY_CONTRACT_ID,
     },
     db::{db_contains_key, db_lookup, db_set},
     error::{ContractError, ContractResult},
@@ -34,9 +37,7 @@ use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
 use crate::{
     model::{ConsensusStakeParamsV1, ConsensusStakeUpdateV1},
-    ConsensusFunction, CONSENSUS_CONTRACT_COINS_TREE, CONSENSUS_CONTRACT_COIN_MERKLE_TREE,
-    CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_INFO_TREE,
-    CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1,
+    ConsensusFunction,
 };
 
 /// `get_metadata` function for `Consensus::StakeV1`
@@ -124,7 +125,7 @@ pub(crate) fn consensus_stake_process_instruction_v1(
 
     // 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() {
+    if caller.contract_id.inner() != MONEY_CONTRACT_ID.inner() {
         msg!("[ConsensusStakeV1] Error: Invoking contract call does not match spend hook");
         return Err(MoneyError::SpendHookMismatch.into())
     }

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

@@ -55,17 +55,3 @@ pub mod entrypoint;
 #[cfg(feature = "client")]
 /// Client API for interaction with this smart contract
 pub mod client;
-
-// These are the different sled trees that will be created
-pub const CONSENSUS_CONTRACT_INFO_TREE: &str = "info";
-pub const CONSENSUS_CONTRACT_COINS_TREE: &str = "coins";
-pub const CONSENSUS_CONTRACT_COIN_ROOTS_TREE: &str = "coin_roots";
-pub const CONSENSUS_CONTRACT_NULLIFIERS_TREE: &str = "nullifiers";
-
-// These are keys inside the info tree
-pub const CONSENSUS_CONTRACT_DB_VERSION: &str = env!("CARGO_PKG_VERSION");
-pub const CONSENSUS_CONTRACT_COIN_MERKLE_TREE: &str = "coin_tree";
-
-/// zkas mint circuit namespace
-pub const CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1: &str = "Consensus_Mint_V1";
-pub const CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1: &str = "Consensus_Burn_V1";

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

@@ -16,26 +16,10 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use darkfi_money_contract::model::Output;
-use darkfi_sdk::{
-    crypto::{Coin, MerkleNode, Nullifier},
-    pasta::pallas,
-};
+use darkfi_money_contract::model::{Output, StakeInput};
+use darkfi_sdk::crypto::Coin;
 use darkfi_serial::{SerialDecodable, SerialEncodable};
 
-/// Anonymous input from `Money::Stake`
-#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
-pub struct StakeInput {
-    /// Blinding factor for `token_id`
-    pub token_blind: pallas::Scalar,
-    /// Pedersen commitment for the staked coin's value
-    pub value_commit: pallas::Point,
-    /// Revealed nullifier
-    pub nullifier: Nullifier,
-    /// Revealed Merkle root
-    pub merkle_root: MerkleNode,
-}
-
 /// Parameters for `Consensus::Stake`
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct ConsensusStakeParamsV1 {

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

@@ -27,7 +27,10 @@ use darkfi_sdk::{
 use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
 use crate::{
-    model::{MoneyFreezeUpdateV1, MoneyMintUpdateV1, MoneyStakeUpdateV1, MoneyTransferUpdateV1},
+    model::{
+        MoneyFreezeUpdateV1, MoneyMintUpdateV1, MoneyStakeUpdateV1, MoneyTransferUpdateV1,
+        MoneyUnstakeUpdateV1,
+    },
     MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE,
     MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_DB_VERSION, MONEY_CONTRACT_FAUCET_PUBKEYS,
     MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_TOKEN_FREEZE_TREE,
@@ -66,6 +69,13 @@ use stake_v1::{
     money_stake_get_metadata_v1, money_stake_process_instruction_v1, money_stake_process_update_v1,
 };
 
+/// `Money::Unstake` functions
+mod unstake_v1;
+use unstake_v1::{
+    money_unstake_get_metadata_v1, money_unstake_process_instruction_v1,
+    money_unstake_process_update_v1,
+};
+
 darkfi_sdk::define_contract!(
     init: init_contract,
     exec: process_instruction,
@@ -191,6 +201,11 @@ fn get_metadata(cid: ContractId, ix: &[u8]) -> ContractResult {
             let metadata = money_stake_get_metadata_v1(cid, call_idx, calls)?;
             Ok(set_return_data(&metadata)?)
         }
+
+        MoneyFunction::UnstakeV1 => {
+            let metadata = money_unstake_get_metadata_v1(cid, call_idx, calls)?;
+            Ok(set_return_data(&metadata)?)
+        }
     }
 }
 
@@ -234,6 +249,11 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
             let update_data = money_stake_process_instruction_v1(cid, call_idx, calls)?;
             Ok(set_return_data(&update_data)?)
         }
+
+        MoneyFunction::UnstakeV1 => {
+            let update_data = money_unstake_process_instruction_v1(cid, call_idx, calls)?;
+            Ok(set_return_data(&update_data)?)
+        }
     }
 }
 
@@ -269,5 +289,10 @@ fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
             let update: MoneyStakeUpdateV1 = deserialize(&update_data[1..])?;
             Ok(money_stake_process_update_v1(cid, update)?)
         }
+
+        MoneyFunction::UnstakeV1 => {
+            let update: MoneyUnstakeUpdateV1 = deserialize(&update_data[1..])?;
+            Ok(money_unstake_process_update_v1(cid, update)?)
+        }
     }
 }

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

@@ -85,7 +85,7 @@ pub(crate) fn money_stake_get_metadata_v1(
     Ok(metadata)
 }
 
-/// `process_instruction` function for `Money::TransferV1`
+/// `process_instruction` function for `Money::StakeV1`
 pub(crate) fn money_stake_process_instruction_v1(
     cid: ContractId,
     call_idx: u32,
@@ -153,6 +153,7 @@ pub(crate) fn money_stake_process_instruction_v1(
     let mut update_data = vec![];
     update_data.write_u8(MoneyFunction::StakeV1 as u8)?;
     update.encode(&mut update_data)?;
+
     // and return it
     Ok(update_data)
 }

+ 168 - 0
src/contract/money/src/entrypoint/unstake_v1.rs

@@ -0,0 +1,168 @@
+/* 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_sdk::{
+    crypto::{
+        pasta_prelude::*, pedersen_commitment_base, Coin, ContractId, MerkleNode,
+        CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID,
+    },
+    db::{db_contains_key, db_lookup, db_set},
+    error::{ContractError, ContractResult},
+    merkle_add, msg,
+    pasta::pallas,
+    ContractCall,
+};
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
+
+use crate::{
+    error::MoneyError,
+    model::{MoneyUnstakeParamsV1, MoneyUnstakeUpdateV1},
+    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_INFO_TREE, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+};
+
+/// `get_metadata` function for `Money::UnstakeV1`
+pub(crate) fn money_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: MoneyUnstakeParamsV1 = 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![];
+
+    // Grab the pedersen commitment from the anonymous output
+    let output = &params.output;
+    let value_coords = output.value_commit.to_affine().coordinates().unwrap();
+    let token_coords = output.token_commit.to_affine().coordinates().unwrap();
+
+    zk_public_inputs.push((
+        MONEY_CONTRACT_ZKAS_MINT_NS_V1.to_string(),
+        vec![
+            output.coin.inner(),
+            *value_coords.x(),
+            *value_coords.y(),
+            *token_coords.x(),
+            *token_coords.y(),
+        ],
+    ));
+
+    // Serialize everything gathered and return it
+    let mut metadata = vec![];
+    zk_public_inputs.encode(&mut metadata)?;
+
+    Ok(metadata)
+}
+
+/// `process_instruction` function for `Money::UnstakeV1`
+pub(crate) fn money_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: MoneyUnstakeParamsV1 = deserialize(&self_.data[1..])?;
+
+    // Access the necessary databases where there is information to
+    // validate this state transition.
+    let money_coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
+    let consensus_nullifiers_db =
+        db_lookup(*CONSENSUS_CONTRACT_ID, CONSENSUS_CONTRACT_NULLIFIERS_TREE)?;
+    let consensus_coin_roots_db =
+        db_lookup(*CONSENSUS_CONTRACT_ID, CONSENSUS_CONTRACT_COIN_ROOTS_TREE)?;
+
+    // ===================================
+    // Perform the actual state transition
+    // ===================================
+
+    msg!("[MoneyUnstakeV1] Validating anonymous output");
+    let input = &params.input;
+    let output = &params.output;
+
+    // Only native token can be staked
+    if output.token_commit != pedersen_commitment_base(DARK_TOKEN_ID.inner(), input.token_blind) {
+        msg!("[MoneyUnstakeV1] Error: Input used non-native token");
+        return Err(MoneyError::StakeInputNonNativeToken.into())
+    }
+
+    // Verify value commits match
+    if output.value_commit != input.value_commit {
+        msg!("[MoneyUnstakeV1] Error: Value commitments do not match");
+        return Err(MoneyError::ValueMismatch.into())
+    }
+
+    // The Merkle root is used to know whether this is a coin that
+    // existed in a previous state.
+    if !db_contains_key(consensus_coin_roots_db, &serialize(&input.merkle_root))? {
+        msg!("[MoneyUnstakeV1] Error: Merkle root not found in previous state");
+        return Err(MoneyError::TransferMerkleRootNotFound.into())
+    }
+
+    // The nullifiers should already exist. It is the double-mint protection.
+    if !db_contains_key(consensus_nullifiers_db, &serialize(&input.nullifier))? {
+        msg!("[MoneyUnstakeV1] Error: Duplicate nullifier found");
+        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");
+        return Err(MoneyError::SpendHookMismatch.into())
+    }
+
+    // Newly created coin for this call is in the output. Here we gather it,
+    // and we also check that it hasn't existed before.
+    if db_contains_key(money_coins_db, &serialize(&output.coin))? {
+        msg!("[MoneyUnstakeV1] Error: Duplicate coin found in output");
+        return Err(MoneyError::DuplicateCoin.into())
+    }
+    let coin = Coin::from(output.coin);
+
+    // Create a state update.
+    let update = MoneyUnstakeUpdateV1 { coin };
+    let mut update_data = vec![];
+    update_data.write_u8(MoneyFunction::UnstakeV1 as u8)?;
+    update.encode(&mut update_data)?;
+
+    // and return it
+    Ok(update_data)
+}
+
+/// `process_update` function for `Money::UnstakeV1`
+pub(crate) fn money_unstake_process_update_v1(
+    cid: ContractId,
+    update: MoneyUnstakeUpdateV1,
+) -> ContractResult {
+    // Grab all necessary db handles for where we want to write
+    let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
+    let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
+    let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+
+    msg!("[MoneyUnstakeV1] Adding new coin to the set");
+    db_set(coins_db, &serialize(&update.coin), &[])?;
+
+    msg!("[MoneyUnstakeV1] Adding new coin to the Merkle tree");
+    let coins: Vec<_> = vec![MerkleNode::from(update.coin.inner())];
+    merkle_add(info_db, coin_roots_db, &serialize(&MONEY_CONTRACT_COIN_MERKLE_TREE), &coins)?;
+
+    Ok(())
+}

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

@@ -30,7 +30,7 @@ pub enum MoneyFunction {
     FreezeV1 = 0x03,
     //Fee = 0x04,
     StakeV1 = 0x05,
-    //Unstake = 0x06,
+    UnstakeV1 = 0x06,
 }
 
 impl TryFrom<u8> for MoneyFunction {
@@ -44,7 +44,7 @@ impl TryFrom<u8> for MoneyFunction {
             0x03 => Ok(Self::FreezeV1),
             //0x04 => Ok(Self::Fee),
             0x05 => Ok(Self::StakeV1),
-            //0x06 => Ok(Self::Unstake),
+            0x06 => Ok(Self::UnstakeV1),
             _ => Err(ContractError::InvalidFunction),
         }
     }
@@ -84,3 +84,20 @@ pub const MONEY_CONTRACT_ZKAS_BURN_NS_V1: &str = "Burn_V1";
 pub const MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1: &str = "TokenMint_V1";
 /// zkas token freeze circuit namespace
 pub const MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1: &str = "TokenFreeze_V1";
+
+// These are the different sled trees that will be created
+// for the consensus contract.
+// We keep them here so we can reference them both in `Money`
+// and `Consensus` contracts.
+pub const CONSENSUS_CONTRACT_INFO_TREE: &str = "consensus_info";
+pub const CONSENSUS_CONTRACT_COINS_TREE: &str = "consensus_coins";
+pub const CONSENSUS_CONTRACT_COIN_ROOTS_TREE: &str = "consensus_coin_roots";
+pub const CONSENSUS_CONTRACT_NULLIFIERS_TREE: &str = "consensus_nullifiers";
+
+// These are keys inside the consensus info tree
+pub const CONSENSUS_CONTRACT_DB_VERSION: &str = env!("CARGO_PKG_VERSION");
+pub const CONSENSUS_CONTRACT_COIN_MERKLE_TREE: &str = "consensus_coin_tree";
+
+/// Consensus zkas circuits namespaces
+pub const CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1: &str = "Consensus_Mint_V1";
+pub const CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1: &str = "Consensus_Burn_V1";

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

@@ -60,6 +60,19 @@ pub struct Input {
     pub signature_public: PublicKey,
 }
 
+/// Anonymous input for staking contract calls
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct StakeInput {
+    /// Blinding factor for `token_id`
+    pub token_blind: pallas::Scalar,
+    /// Pedersen commitment for the staked coin's value
+    pub value_commit: pallas::Point,
+    /// Revealed nullifier
+    pub nullifier: Nullifier,
+    /// Revealed Merkle root
+    pub merkle_root: MerkleNode,
+}
+
 /// A contract call's anonymous output
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct Output {
@@ -139,3 +152,19 @@ pub struct MoneyStakeUpdateV1 {
     /// Revealed nullifier
     pub nullifier: Nullifier,
 }
+
+/// Parameters for `Money::Unstake`
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct MoneyUnstakeParamsV1 {
+    /// Burnt token revealed info
+    pub input: StakeInput,
+    /// Anonymous output
+    pub output: Output,
+}
+
+/// State update for `Money::Unstake`
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct MoneyUnstakeUpdateV1 {
+    /// The newly minted coin
+    pub coin: Coin,
+}