|
@@ -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 = ¶ms.output;
|
|
let output = ¶ms.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 = ¶ms.input;
|
|
let input = ¶ms.input;
|
|
|
let output = ¶ms.output;
|
|
let output = ¶ms.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))? {
|