|
|
@@ -16,17 +16,28 @@
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
*/
|
|
|
|
|
|
+use darkfi_money_contract::{
|
|
|
+ error::MoneyError, MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_NULLIFIERS_TREE,
|
|
|
+};
|
|
|
use darkfi_sdk::{
|
|
|
- crypto::{ContractId, PublicKey},
|
|
|
+ crypto::{
|
|
|
+ contract_id::{CONSENSUS_CONTRACT_ID, MONEY_CONTRACT_ID},
|
|
|
+ pasta_prelude::*,
|
|
|
+ pedersen_commitment_base, Coin, ContractId, MerkleNode, DARK_TOKEN_ID,
|
|
|
+ },
|
|
|
+ db::{db_contains_key, db_lookup, db_set},
|
|
|
error::{ContractError, ContractResult},
|
|
|
+ merkle_add, msg,
|
|
|
pasta::pallas,
|
|
|
ContractCall,
|
|
|
};
|
|
|
-use darkfi_serial::{deserialize, Encodable, WriteExt};
|
|
|
+use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
|
|
|
|
|
|
use crate::{
|
|
|
model::{ConsensusStakeParamsV1, ConsensusStakeUpdateV1},
|
|
|
- ConsensusFunction,
|
|
|
+ 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,
|
|
|
};
|
|
|
|
|
|
/// `get_metadata` function for `Consensus::StakeV1`
|
|
|
@@ -36,33 +47,99 @@ pub(crate) fn consensus_stake_get_metadata_v1(
|
|
|
calls: Vec<ContractCall>,
|
|
|
) -> Result<Vec<u8>, ContractError> {
|
|
|
let self_ = &calls[call_idx as usize];
|
|
|
- let _params: ConsensusStakeParamsV1 = deserialize(&self_.data[1..])?;
|
|
|
+ let params: ConsensusStakeParamsV1 = deserialize(&self_.data[1..])?;
|
|
|
|
|
|
// Public inputs for the ZK proofs we have to verify
|
|
|
- let zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
|
|
|
- // Public keys for the transaction signatures we have to verify
|
|
|
- let signature_pubkeys: Vec<PublicKey> = vec![];
|
|
|
+ let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
|
|
|
+
|
|
|
+ // Grab the pedersen commitment from the anonymous output
|
|
|
+ let output = ¶ms.output;
|
|
|
+ let value_coords = output.value_commit.to_affine().coordinates().unwrap();
|
|
|
+ let token_coords = output.token_commit.to_affine().coordinates().unwrap();
|
|
|
|
|
|
- // TODO: implement
|
|
|
+ zk_public_inputs.push((
|
|
|
+ CONSENSUS_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)?;
|
|
|
- signature_pubkeys.encode(&mut metadata)?;
|
|
|
|
|
|
Ok(metadata)
|
|
|
}
|
|
|
|
|
|
/// `process_instruction` function for `Consensus::StakeV1`
|
|
|
pub(crate) fn consensus_stake_process_instruction_v1(
|
|
|
- _cid: ContractId,
|
|
|
- _call_idx: u32,
|
|
|
- _calls: Vec<ContractCall>,
|
|
|
+ cid: ContractId,
|
|
|
+ call_idx: u32,
|
|
|
+ calls: Vec<ContractCall>,
|
|
|
) -> Result<Vec<u8>, ContractError> {
|
|
|
- // TODO: implement
|
|
|
+ let self_ = &calls[call_idx as usize];
|
|
|
+ let params: ConsensusStakeParamsV1 = deserialize(&self_.data[1..])?;
|
|
|
+
|
|
|
+ // Access the necessary databases where there is information to
|
|
|
+ // validate this state transition.
|
|
|
+ let consenus_coins_db = db_lookup(cid, CONSENSUS_CONTRACT_COINS_TREE)?;
|
|
|
+ let money_nullifiers_db = db_lookup(*MONEY_CONTRACT_ID, MONEY_CONTRACT_NULLIFIERS_TREE)?;
|
|
|
+ let money_coin_roots_db = db_lookup(*MONEY_CONTRACT_ID, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
|
|
|
+
|
|
|
+ // ===================================
|
|
|
+ // Perform the actual state transition
|
|
|
+ // ===================================
|
|
|
+
|
|
|
+ msg!("[StakeV1] Validating anonymous output");
|
|
|
+ let input = ¶ms.input;
|
|
|
+ let output = ¶ms.output;
|
|
|
+
|
|
|
+ // Only native token can be staked
|
|
|
+ if output.token_commit != pedersen_commitment_base(DARK_TOKEN_ID.inner(), input.token_blind) {
|
|
|
+ msg!("[StakeV1] Error: Input used non-native token");
|
|
|
+ return Err(MoneyError::StakeInputNonNativeToken.into())
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify value commits match
|
|
|
+ if output.value_commit != input.value_commit {
|
|
|
+ msg!("[StakeV1] 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(money_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 already exist. It is the double-mint protection.
|
|
|
+ if !db_contains_key(money_nullifiers_db, &serialize(&input.nullifier))? {
|
|
|
+ msg!("[StakeV1] 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!("[StakeV1] 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(consenus_coins_db, &serialize(&output.coin))? {
|
|
|
+ msg!("[StakeV1] Error: Duplicate coin found in output");
|
|
|
+ return Err(MoneyError::DuplicateCoin.into())
|
|
|
+ }
|
|
|
+ let coin = Coin::from(output.coin);
|
|
|
|
|
|
// Create a state update.
|
|
|
- let update = ConsensusStakeUpdateV1 {};
|
|
|
+ let update = ConsensusStakeUpdateV1 { coin };
|
|
|
let mut update_data = vec![];
|
|
|
update_data.write_u8(ConsensusFunction::StakeV1 as u8)?;
|
|
|
update.encode(&mut update_data)?;
|
|
|
@@ -72,10 +149,20 @@ pub(crate) fn consensus_stake_process_instruction_v1(
|
|
|
|
|
|
/// `process_update` function for `Consensus::StakeV1`
|
|
|
pub(crate) fn consensus_stake_process_update_v1(
|
|
|
- _cid: ContractId,
|
|
|
- _update: ConsensusStakeUpdateV1,
|
|
|
+ cid: ContractId,
|
|
|
+ update: ConsensusStakeUpdateV1,
|
|
|
) -> ContractResult {
|
|
|
- // TODO: implement
|
|
|
+ // Grab all necessary db handles for where we want to write
|
|
|
+ let info_db = db_lookup(cid, CONSENSUS_CONTRACT_INFO_TREE)?;
|
|
|
+ let coins_db = db_lookup(cid, CONSENSUS_CONTRACT_COINS_TREE)?;
|
|
|
+ let coin_roots_db = db_lookup(cid, CONSENSUS_CONTRACT_COIN_ROOTS_TREE)?;
|
|
|
+
|
|
|
+ msg!("[StakeV1] Adding new coin to the set");
|
|
|
+ db_set(coins_db, &serialize(&update.coin), &[])?;
|
|
|
+
|
|
|
+ msg!("[StakeV1] Adding new coin to the Merkle tree");
|
|
|
+ let coins: Vec<_> = vec![MerkleNode::from(update.coin.inner())];
|
|
|
+ merkle_add(info_db, coin_roots_db, &serialize(&CONSENSUS_CONTRACT_COIN_MERKLE_TREE), &coins)?;
|
|
|
|
|
|
Ok(())
|
|
|
}
|