Просмотр исходного кода

contract/money: StakeV1 functionality added

aggstam 3 лет назад
Родитель
Сommit
89ad70b933

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

@@ -27,7 +27,7 @@ use darkfi_sdk::{
 use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
 
 use crate::{
-    model::{MoneyFreezeUpdateV1, MoneyMintUpdateV1, MoneyTransferUpdateV1},
+    model::{MoneyFreezeUpdateV1, MoneyMintUpdateV1, MoneyStakeUpdateV1, MoneyTransferUpdateV1},
     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,
@@ -60,6 +60,12 @@ use freeze_v1::{
     money_freeze_process_update_v1,
 };
 
+/// `Money::Stake` functions
+mod stake_v1;
+use stake_v1::{
+    money_stake_get_metadata_v1, money_stake_process_instruction_v1, money_stake_process_update_v1,
+};
+
 darkfi_sdk::define_contract!(
     init: init_contract,
     exec: process_instruction,
@@ -180,6 +186,11 @@ fn get_metadata(cid: ContractId, ix: &[u8]) -> ContractResult {
             let metadata = money_freeze_get_metadata_v1(cid, call_idx, calls)?;
             Ok(set_return_data(&metadata)?)
         }
+
+        MoneyFunction::StakeV1 => {
+            let metadata = money_stake_get_metadata_v1(cid, call_idx, calls)?;
+            Ok(set_return_data(&metadata)?)
+        }
     }
 }
 
@@ -218,6 +229,11 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
             let update_data = money_freeze_process_instruction_v1(cid, call_idx, calls)?;
             Ok(set_return_data(&update_data)?)
         }
+
+        MoneyFunction::StakeV1 => {
+            let update_data = money_stake_process_instruction_v1(cid, call_idx, calls)?;
+            Ok(set_return_data(&update_data)?)
+        }
     }
 }
 
@@ -248,5 +264,10 @@ fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
             let update: MoneyFreezeUpdateV1 = deserialize(&update_data[1..])?;
             Ok(money_freeze_process_update_v1(cid, update)?)
         }
+
+        MoneyFunction::StakeV1 => {
+            let update: MoneyStakeUpdateV1 = deserialize(&update_data[1..])?;
+            Ok(money_stake_process_update_v1(cid, update)?)
+        }
     }
 }

+ 172 - 0
src/contract/money/src/entrypoint/stake_v1.rs

@@ -0,0 +1,172 @@
+/* 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::{
+        contract_id::CONSENSUS_CONTRACT_ID, pasta_prelude::*, pedersen_commitment_base, ContractId,
+        PublicKey, DARK_TOKEN_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::{
+    error::MoneyError,
+    model::{MoneyStakeParamsV1, MoneyStakeUpdateV1},
+    MoneyFunction, CONSENSUS_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_NULLIFIERS_TREE,
+    MONEY_CONTRACT_ZKAS_BURN_NS_V1,
+};
+
+/// `get_metadata` function for `Money::StakeV1`
+pub(crate) fn money_stake_get_metadata_v1(
+    _cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: MoneyStakeParamsV1 = 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((
+        MONEY_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 `Money::TransferV1`
+pub(crate) fn money_stake_process_instruction_v1(
+    cid: ContractId,
+    call_idx: u32,
+    calls: Vec<ContractCall>,
+) -> Result<Vec<u8>, ContractError> {
+    let self_ = &calls[call_idx as usize];
+    let params: MoneyStakeParamsV1 = deserialize(&self_.data[1..])?;
+
+    // Access the necessary databases where there is information to
+    // validate this state transition.
+    let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
+    let coin_roots_db = db_lookup(*CONSENSUS_CONTRACT_ID, CONSENSUS_CONTRACT_COIN_ROOTS_TREE)?;
+
+    // ===================================
+    // Perform the actual state transition
+    // ===================================
+
+    msg!("[StakeV1] Validating anonymous input");
+    let input = &params.input;
+
+    // Only native token can be staked
+    if input.token_commit != pedersen_commitment_base(DARK_TOKEN_ID.inner(), params.token_blind) {
+        msg!("[StakeV1] 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!("[StakeV1] 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!("[StakeV1] 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!("[StakeV1] 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!("[StakeV1] 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!("[StakeV1] Error: Invoking contract call does not match spend hook");
+        return Err(MoneyError::SpendHookMismatch.into())
+    }
+
+    if input.spend_hook != CONSENSUS_CONTRACT_ID.inner() {
+        msg!("[StakeV1] Error: Spend hook is not consensus contract");
+        return Err(MoneyError::StakeSpendHookNonConsensusContract.into())
+    }
+
+    // At this point the state transition has passed, so we create a state update
+    let update = MoneyStakeUpdateV1 { nullifier: input.nullifier };
+    let mut update_data = vec![];
+    update_data.write_u8(MoneyFunction::StakeV1 as u8)?;
+    update.encode(&mut update_data)?;
+    // and return it
+    Ok(update_data)
+}
+
+/// `process_update` function for `Money::StakeV1`
+pub(crate) fn money_stake_process_update_v1(
+    cid: ContractId,
+    update: MoneyStakeUpdateV1,
+) -> ContractResult {
+    // Grab all necessary db handles for where we want to write
+    let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
+
+    msg!("[StakeV1] Adding new nullifier to the set");
+    db_set(nullifiers_db, &serialize(&update.nullifier), &[])?;
+
+    Ok(())
+}

+ 12 - 0
src/contract/money/src/error.rs

@@ -73,6 +73,15 @@ pub enum MoneyError {
 
     #[error("Token mint is frozen")]
     MintFrozen,
+
+    #[error("Input used non-native token")]
+    StakeInputNonNativeToken,
+
+    #[error("Missing spend hook")]
+    StakeMissingSpendHook,
+
+    #[error("Spend hook is not consensus contract")]
+    StakeSpendHookNonConsensusContract,
 }
 
 impl From<MoneyError> for ContractError {
@@ -96,6 +105,9 @@ impl From<MoneyError> for ContractError {
             MoneyError::SwapMerkleRootNotFound => Self::Custom(16),
             MoneyError::TokenIdDoesNotDeriveFromMint => Self::Custom(17),
             MoneyError::MintFrozen => Self::Custom(18),
+            MoneyError::StakeInputNonNativeToken => Self::Custom(19),
+            MoneyError::StakeMissingSpendHook => Self::Custom(20),
+            MoneyError::StakeSpendHookNonConsensusContract => Self::Custom(21),
         }
     }
 }

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

@@ -29,7 +29,7 @@ pub enum MoneyFunction {
     MintV1 = 0x02,
     FreezeV1 = 0x03,
     //Fee = 0x04,
-    //Stake = 0x05,
+    StakeV1 = 0x05,
     //Unstake = 0x06,
 }
 
@@ -43,7 +43,7 @@ impl TryFrom<u8> for MoneyFunction {
             0x02 => Ok(Self::MintV1),
             0x03 => Ok(Self::FreezeV1),
             //0x04 => Ok(Self::Fee),
-            //0x05 => Ok(Self::Stake),
+            0x05 => Ok(Self::StakeV1),
             //0x06 => Ok(Self::Unstake),
             _ => Err(ContractError::InvalidFunction),
         }
@@ -84,3 +84,6 @@ 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 consensus sled trees we access for information
+pub const CONSENSUS_CONTRACT_COIN_ROOTS_TREE: &str = "coin_roots";

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

@@ -123,3 +123,21 @@ pub struct MoneyFreezeUpdateV1 {
     /// Mint authority public key
     pub signature_public: PublicKey,
 }
+
+/// Parameters for `Money::Stake`
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct MoneyStakeParamsV1 {
+    /// Blinding factor for `value`
+    pub value_blind: pallas::Scalar,
+    /// Blinding factor for `token_id`
+    pub token_blind: pallas::Scalar,
+    /// Anonymous input
+    pub input: Input,
+}
+
+/// State update for `Money::Stake`
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
+pub struct MoneyStakeUpdateV1 {
+    /// Revealed nullifier
+    pub nullifier: Nullifier,
+}