diff --git a/src/contract/consensus/src/entrypoint/stake_v1.rs b/src/contract/consensus/src/entrypoint/stake_v1.rs index 1f5f0662d..f401c3b84 100644 --- a/src/contract/consensus/src/entrypoint/stake_v1.rs +++ b/src/contract/consensus/src/entrypoint/stake_v1.rs @@ -82,35 +82,40 @@ pub(crate) fn consensus_stake_process_instruction_v1( call_idx: u32, calls: Vec>, ) -> Result, ContractError> { - let self_ = &calls[call_idx as usize].data; - let params: ConsensusStakeParamsV1 = deserialize(&self_.data[1..])?; + let self_ = &calls[call_idx as usize]; + let params: ConsensusStakeParamsV1 = deserialize(&self_.data.data[1..])?; - // Check previous call is money contract - // FIXME: This changes with Money::Fee + // Check child call is money contract if call_idx == 0 { - msg!("[ConsensusStakeV1] Error: previous_call_idx will be out of bounds"); + msg!("[ConsensusStakeV1] Error: child_call_idx will be out of bounds"); return Err(MoneyError::CallIdxOutOfBounds.into()) } - // Verify previous call corresponds to Money::StakeV1 - let previous_call_idx = call_idx - 1; - let previous = &calls[previous_call_idx as usize].data; - if previous.contract_id.inner() != MONEY_CONTRACT_ID.inner() { - msg!("[ConsensusStakeV1] Error: Previous contract call is not money contract"); - return Err(MoneyError::StakePreviousCallNotMoneyContract.into()) + let child_call_indexes = &self_.children_indexes; + if child_call_indexes.len() != 1 { + msg!("[ConsensusStakeV1] Error: child_call_idx is missing"); + return Err(MoneyError::StakeChildCallNotMoneyContract.into()) + } + let child_call_idx = child_call_indexes[0]; + + // Verify child call corresponds to Money::StakeV1 + let child = &calls[child_call_idx].data; + if child.contract_id.inner() != MONEY_CONTRACT_ID.inner() { + msg!("[ConsensusStakeV1] Error: Child contract call is not money contract"); + return Err(MoneyError::StakeChildCallNotMoneyContract.into()) } - if previous.data[0] != MoneyFunction::StakeV1 as u8 { - msg!("[ConsensusStakeV1] Error: Previous call function mismatch"); - return Err(MoneyError::PreviousCallFunctionMismatch.into()) + if child.data[0] != MoneyFunction::StakeV1 as u8 { + msg!("[ConsensusStakeV1] Error: Child call function mismatch"); + return Err(MoneyError::ChildCallFunctionMismatch.into()) } - // Verify that the previous call's input is the same as this one's - let previous_params: MoneyStakeParamsV1 = deserialize(&previous.data[1..])?; - let previous_input = &previous_params.input; - if previous_input != ¶ms.input { - msg!("[ConsensusStakeV1] Error: Previous call input mismatch"); - return Err(MoneyError::PreviousCallInputMismatch.into()) + // Verify that the child call's input is the same as this one's + let child_params: MoneyStakeParamsV1 = deserialize(&child.data[1..])?; + let child_input = &child_params.input; + if child_input != ¶ms.input { + msg!("[ConsensusStakeV1] Error: Child call input mismatch"); + return Err(MoneyError::ChildCallInputMismatch.into()) } // Access the necessary databases where there is information to diff --git a/src/contract/consensus/src/entrypoint/unstake_v1.rs b/src/contract/consensus/src/entrypoint/unstake_v1.rs index c8bf8477b..51262e741 100644 --- a/src/contract/consensus/src/entrypoint/unstake_v1.rs +++ b/src/contract/consensus/src/entrypoint/unstake_v1.rs @@ -85,8 +85,8 @@ pub(crate) fn consensus_unstake_process_instruction_v1( call_idx: u32, calls: Vec>, ) -> Result, ContractError> { - let self_ = &calls[call_idx as usize].data; - let params: ConsensusUnstakeParamsV1 = deserialize(&self_.data[1..])?; + let self_ = &calls[call_idx as usize]; + let params: ConsensusUnstakeParamsV1 = deserialize(&self_.data.data[1..])?; let input = ¶ms.input; // Access the necessary databases where there is information to @@ -98,30 +98,36 @@ pub(crate) fn consensus_unstake_process_instruction_v1( // Perform the actual state transition // =================================== - // Check next call is money contract - let next_call_idx = call_idx + 1; - if next_call_idx >= calls.len() as u32 { - msg!("[ConsensusUnstakeV1] Error: next_call_idx out of bounds"); + // Check parent call is money contract + let parent_call_idx = self_.parent_index; + if parent_call_idx.is_none() { + msg!("[ConsensusUnstakeV1] Error: parent_call_idx is missing"); + return Err(MoneyError::UnstakeParentCallNotMoneyContract.into()) + } + let parent_call_idx = parent_call_idx.unwrap(); + + if parent_call_idx >= calls.len() { + msg!("[ConsensusUnstakeV1] Error: parent_call_idx out of bounds"); return Err(MoneyError::CallIdxOutOfBounds.into()) } - let next = &calls[next_call_idx as usize].data; - if next.contract_id.inner() != MONEY_CONTRACT_ID.inner() { - msg!("[ConsensusUnstakeV1] Error: Next contract call is not money contract"); - return Err(MoneyError::UnstakeNextCallNotMoneyContract.into()) + let parent = &calls[parent_call_idx].data; + if parent.contract_id.inner() != MONEY_CONTRACT_ID.inner() { + msg!("[ConsensusUnstakeV1] Error: Parent contract call is not money contract"); + return Err(MoneyError::UnstakeParentCallNotMoneyContract.into()) } - // Verify next call corresponds to Money::UnstakeV1 - if next.data[0] != MoneyFunction::UnstakeV1 as u8 { - msg!("[ConsensusUnstakeV1] Error: Next call function mismatch"); - return Err(MoneyError::NextCallFunctionMismatch.into()) + // Verify parent call corresponds to Money::UnstakeV1 + if parent.data[0] != MoneyFunction::UnstakeV1 as u8 { + msg!("[ConsensusUnstakeV1] Error: Parent call function mismatch"); + return Err(MoneyError::ParentCallFunctionMismatch.into()) } - // Verify next call input is the same as this calls input - let next_params: MoneyUnstakeParamsV1 = deserialize(&next.data[1..])?; - if input != &next_params.input { - msg!("[ConsensusUnstakeV1] Error: Next call input mismatch"); - return Err(MoneyError::NextCallInputMismatch.into()) + // Verify parent call input is the same as this calls input + let parent_params: MoneyUnstakeParamsV1 = deserialize(&parent.data[1..])?; + if input != &parent_params.input { + msg!("[ConsensusUnstakeV1] Error: Parent call input mismatch"); + return Err(MoneyError::ParentCallInputMismatch.into()) } msg!("[ConsensusUnstakeV1] Validating anonymous input"); diff --git a/src/contract/money/src/entrypoint/stake_v1.rs b/src/contract/money/src/entrypoint/stake_v1.rs index bda02cdba..ba114e27a 100644 --- a/src/contract/money/src/entrypoint/stake_v1.rs +++ b/src/contract/money/src/entrypoint/stake_v1.rs @@ -86,8 +86,8 @@ pub(crate) fn money_stake_process_instruction_v1( call_idx: u32, calls: Vec>, ) -> Result, ContractError> { - let self_ = &calls[call_idx as usize].data; - let params: MoneyStakeParamsV1 = deserialize(&self_.data[1..])?; + let self_ = &calls[call_idx as usize]; + let params: MoneyStakeParamsV1 = deserialize(&self_.data.data[1..])?; // Access the necessary databases where there is information to // validate this state transition. @@ -126,30 +126,36 @@ pub(crate) fn money_stake_process_instruction_v1( return Err(MoneyError::DuplicateNullifier.into()) } - // Check next call is consensus contract - let next_call_idx = call_idx + 1; - if next_call_idx >= calls.len() as u32 { + // Check parent call is consensus contract + let parent_call_idx = self_.parent_index; + if parent_call_idx.is_none() { + msg!("[MoneyStakeV1] Error: parent_call_idx is missing"); + return Err(MoneyError::StakeParentCallNotConsensusContract.into()) + } + let parent_call_idx = parent_call_idx.unwrap(); + + if parent_call_idx >= calls.len() { msg!("[MoneyStakeV1] Error: next_call_idx out of bounds"); return Err(MoneyError::CallIdxOutOfBounds.into()) } - // Verify next call corresponds to Consensus::StakeV1 (0x01) - let next = &calls[next_call_idx as usize].data; - if next.contract_id.inner() != CONSENSUS_CONTRACT_ID.inner() { - msg!("[MoneyStakeV1] Error: Next contract call is not consensus contract"); - return Err(MoneyError::StakeNextCallNotConsensusContract.into()) + // Verify parent call corresponds to Consensus::StakeV1 (0x01) + let parent = &calls[parent_call_idx].data; + if parent.contract_id.inner() != CONSENSUS_CONTRACT_ID.inner() { + msg!("[MoneyStakeV1] Error: Parent contract call is not consensus contract"); + return Err(MoneyError::StakeParentCallNotConsensusContract.into()) } - if next.data[0] != 0x01 { - msg!("[MoneyStakeV1] Error: Next call function mismatch"); - return Err(MoneyError::NextCallFunctionMismatch.into()) + if parent.data[0] != 0x01 { + msg!("[MoneyStakeV1] Error: Parent call function mismatch"); + return Err(MoneyError::ParentCallFunctionMismatch.into()) } - // Verify next call ConsensusInput is the same as this calls input - let next_params: ConsensusStakeParamsV1 = deserialize(&next.data[1..])?; - if input != &next_params.input { - msg!("[MoneyStakeV1] Error: Next call input mismatch"); - return Err(MoneyError::NextCallInputMismatch.into()) + // Verify parent call ConsensusInput is the same as this calls input + let parent_params: ConsensusStakeParamsV1 = deserialize(&parent.data[1..])?; + if input != &parent_params.input { + msg!("[MoneyStakeV1] Error: Parent call input mismatch"); + return Err(MoneyError::ParentCallInputMismatch.into()) } // At this point the state transition has passed, so we create a state update diff --git a/src/contract/money/src/entrypoint/transfer_v1.rs b/src/contract/money/src/entrypoint/transfer_v1.rs index 3fc252b2f..5d1d7b571 100644 --- a/src/contract/money/src/entrypoint/transfer_v1.rs +++ b/src/contract/money/src/entrypoint/transfer_v1.rs @@ -109,8 +109,8 @@ pub(crate) fn money_transfer_process_instruction_v1( call_idx: u32, calls: Vec>, ) -> Result, ContractError> { - let self_ = &calls[call_idx as usize].data; - let params: MoneyTransferParamsV1 = deserialize(&self_.data[1..])?; + let self_ = &calls[call_idx as usize]; + let params: MoneyTransferParamsV1 = deserialize(&self_.data.data[1..])?; if params.clear_inputs.len() + params.inputs.len() < 1 { msg!("[TransferV1] Error: No inputs in the call"); @@ -188,14 +188,20 @@ pub(crate) fn money_transfer_process_instruction_v1( // If spend hook is set, check its correctness if input.spend_hook != pallas::Base::ZERO { - let next_call_idx = call_idx + 1; - if next_call_idx >= calls.len() as u32 { - msg!("[TransferV1] Error: next_call_idx out of bounds (input {})", i); + let parent_call_idx = self_.parent_index; + if parent_call_idx.is_none() { + msg!("[TransferV1] Error: parent_call_idx is missing"); + return Err(MoneyError::CallIdxOutOfBounds.into()) + } + let parent_call_idx = parent_call_idx.unwrap(); + + if parent_call_idx >= calls.len() { + msg!("[TransferV1] Error: parent_call_idx out of bounds (input {})", i); return Err(MoneyError::CallIdxOutOfBounds.into()) } - let next = &calls[next_call_idx as usize].data; - if next.contract_id.inner() != input.spend_hook { + let parent = &calls[parent_call_idx].data; + if parent.contract_id.inner() != input.spend_hook { msg!("[TransferV1] Error: Invoked contract call does not match spend hook in input {}", i); return Err(MoneyError::SpendHookMismatch.into()) } diff --git a/src/contract/money/src/entrypoint/unstake_v1.rs b/src/contract/money/src/entrypoint/unstake_v1.rs index d93d7557f..a1cf2a8df 100644 --- a/src/contract/money/src/entrypoint/unstake_v1.rs +++ b/src/contract/money/src/entrypoint/unstake_v1.rs @@ -79,8 +79,8 @@ pub(crate) fn money_unstake_process_instruction_v1( call_idx: u32, calls: Vec>, ) -> Result, ContractError> { - let self_ = &calls[call_idx as usize].data; - let params: MoneyUnstakeParamsV1 = deserialize(&self_.data[1..])?; + let self_ = &calls[call_idx as usize]; + let params: MoneyUnstakeParamsV1 = deserialize(&self_.data.data[1..])?; let input = ¶ms.input; let output = ¶ms.output; @@ -96,31 +96,37 @@ pub(crate) fn money_unstake_process_instruction_v1( // Perform the actual state transition // =================================== - // Check previous call is consensus contract + // Check child call is consensus contract if call_idx == 0 { - msg!("[MoneyUnstakeV1] Error: previous_call_idx will be out of bounds"); + msg!("[MoneyUnstakeV1] Error: child_call_idx will be out of bounds"); return Err(MoneyError::CallIdxOutOfBounds.into()) } - let previous_call_idx = call_idx - 1; - let previous = &calls[previous_call_idx as usize].data; - if previous.contract_id.inner() != CONSENSUS_CONTRACT_ID.inner() { - msg!("[MoneyUnstakeV1] Error: Previous contract call is not consensus contract"); - return Err(MoneyError::UnstakePreviousCallNotConsensusContract.into()) + let child_call_indexes = &self_.children_indexes; + if child_call_indexes.len() != 1 { + msg!("[MoneyUnstakeV1] Error: child_call_idx is missing"); + return Err(MoneyError::UnstakeChildCallNotConsensusContract.into()) + } + let child_call_idx = child_call_indexes[0]; + + let child = &calls[child_call_idx].data; + if child.contract_id.inner() != CONSENSUS_CONTRACT_ID.inner() { + msg!("[MoneyUnstakeV1] Error: Child contract call is not consensus contract"); + return Err(MoneyError::UnstakeChildCallNotConsensusContract.into()) } - // Verify previous call corresponds to Consensus::UnstakeV1 (0x04) - if previous.data[0] != 0x04 { - msg!("[MoneyUnstakeV1] Error: Previous call function mismatch"); - return Err(MoneyError::PreviousCallFunctionMismatch.into()) + // Verify child call corresponds to Consensus::UnstakeV1 (0x04) + if child.data[0] != 0x04 { + msg!("[MoneyUnstakeV1] Error: Child call function mismatch"); + return Err(MoneyError::ChildCallFunctionMismatch.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::PreviousCallInputMismatch.into()) + // Verify child call input is the same as this calls StakeInput + let child_params: ConsensusUnstakeParamsV1 = deserialize(&child.data[1..])?; + let child_input = &child_params.input; + if child_input != input { + msg!("[MoneyUnstakeV1] Error: Child call input mismatch"); + return Err(MoneyError::ChildCallInputMismatch.into()) } msg!("[MoneyUnstakeV1] Validating anonymous output"); diff --git a/src/contract/money/src/error.rs b/src/contract/money/src/error.rs index 38562d581..934898129 100644 --- a/src/contract/money/src/error.rs +++ b/src/contract/money/src/error.rs @@ -20,7 +20,7 @@ use darkfi_sdk::error::ContractError; #[derive(Debug, Clone, thiserror::Error)] // TODO: Make generic contract common errors like -// NextCallFunctionMismatch +// ParentCallFunctionMismatch pub enum MoneyError { #[error("Missing inputs in transfer call")] TransferMissingInputs, @@ -85,32 +85,32 @@ pub enum MoneyError { #[error("Missing nullifier")] StakeMissingNullifier, - #[error("Next contract call is not consensus contract")] - StakeNextCallNotConsensusContract, + #[error("Parent contract call is not consensus contract")] + StakeParentCallNotConsensusContract, - #[error("Previous contract call is not money contract")] - StakePreviousCallNotMoneyContract, + #[error("Child contract call is not money contract")] + StakeChildCallNotMoneyContract, #[error("Spend hook is not consensus contract")] UnstakeSpendHookNotConsensusContract, - #[error("Next contract call is not money contract")] - UnstakeNextCallNotMoneyContract, + #[error("Parent contract call is not money contract")] + UnstakeParentCallNotMoneyContract, - #[error("Previous contract call is not consensus contract")] - UnstakePreviousCallNotConsensusContract, + #[error("Child contract call is not consensus contract")] + UnstakeChildCallNotConsensusContract, - #[error("Next call function mismatch")] - NextCallFunctionMismatch, + #[error("Parent call function mismatch")] + ParentCallFunctionMismatch, - #[error("Next call input mismatch")] - NextCallInputMismatch, + #[error("Parent call input mismatch")] + ParentCallInputMismatch, - #[error("Previous call function mismatch")] - PreviousCallFunctionMismatch, + #[error("Child call function mismatch")] + ChildCallFunctionMismatch, - #[error("Previous call input mismatch")] - PreviousCallInputMismatch, + #[error("Child call input mismatch")] + ChildCallInputMismatch, #[error("Call is not executed on genesis slot")] GenesisCallNonGenesisSlot, @@ -155,15 +155,15 @@ impl From for ContractError { MoneyError::StakeInputNonNativeToken => Self::Custom(19), MoneyError::StakeMissingSpendHook => Self::Custom(20), MoneyError::StakeMissingNullifier => Self::Custom(21), - MoneyError::StakeNextCallNotConsensusContract => Self::Custom(22), - MoneyError::StakePreviousCallNotMoneyContract => Self::Custom(23), + MoneyError::StakeParentCallNotConsensusContract => Self::Custom(22), + MoneyError::StakeChildCallNotMoneyContract => Self::Custom(23), MoneyError::UnstakeSpendHookNotConsensusContract => Self::Custom(24), - MoneyError::UnstakeNextCallNotMoneyContract => Self::Custom(25), - MoneyError::UnstakePreviousCallNotConsensusContract => Self::Custom(26), - MoneyError::NextCallFunctionMismatch => Self::Custom(27), - MoneyError::NextCallInputMismatch => Self::Custom(28), - MoneyError::PreviousCallFunctionMismatch => Self::Custom(29), - MoneyError::PreviousCallInputMismatch => Self::Custom(30), + MoneyError::UnstakeParentCallNotMoneyContract => Self::Custom(25), + MoneyError::UnstakeChildCallNotConsensusContract => Self::Custom(26), + MoneyError::ParentCallFunctionMismatch => Self::Custom(27), + MoneyError::ParentCallInputMismatch => Self::Custom(28), + MoneyError::ChildCallFunctionMismatch => Self::Custom(29), + MoneyError::ChildCallInputMismatch => Self::Custom(30), MoneyError::GenesisCallNonGenesisSlot => Self::Custom(31), MoneyError::MissingNullifier => Self::Custom(32), MoneyError::PoWRewardCallAfterCutoffSlot => Self::Custom(33),