Skip to main content

dpp/shielded/builder/
token_direct_purchase_to_pool.rs

1use grovedb_commitment_tree::OutgoingViewingKey;
2
3use crate::address_funds::OrchardAddress;
4use crate::balances::credits::TokenAmount;
5use crate::fee::Credits;
6use crate::identity::signer::Signer;
7use crate::identity::IdentityPublicKey;
8use crate::prelude::{Identifier, IdentityNonce, UserFeeIncrease};
9use crate::shielded::{token_pool_output_only_extra_sighash_data, OrchardBundleParams};
10use crate::state_transition::batch_transition::batched_transition::token_transition_action_type::TokenTransitionActionType;
11use crate::state_transition::batch_transition::methods::v1::DocumentsBatchTransitionMethodsV1;
12use crate::state_transition::batch_transition::BatchTransition;
13use crate::state_transition::StateTransition;
14use crate::ProtocolError;
15use platform_version::version::PlatformVersion;
16
17use super::{build_output_only_bundle, serialize_authorized_bundle, OrchardProver};
18
19/// Builds a `TokenDirectPurchaseToPool` batch transition: proves an outputs-only Orchard bundle
20/// creating `token_count` of the token as a note for `recipient` inside the token's shielded
21/// pool, then wraps it in a batch transition signed by `owner_id`, the buyer, who pays at most
22/// `total_agreed_price` credits to the contract owner plus the fee.
23#[allow(clippy::too_many_arguments)]
24pub async fn build_token_direct_purchase_to_pool_transition<
25    S: Signer<IdentityPublicKey>,
26    P: OrchardProver,
27>(
28    token_id: Identifier,
29    owner_id: Identifier,
30    data_contract_id: Identifier,
31    token_contract_position: u16,
32    recipient: &OrchardAddress,
33    token_count: TokenAmount,
34    total_agreed_price: Credits,
35    memo: [u8; 36],
36    sender_ovk: Option<OutgoingViewingKey>,
37    identity_public_key: &IdentityPublicKey,
38    identity_contract_nonce: IdentityNonce,
39    user_fee_increase: UserFeeIncrease,
40    signer: &S,
41    prover: &P,
42    platform_version: &PlatformVersion,
43) -> Result<StateTransition, ProtocolError> {
44    if token_count == 0 {
45        return Err(ProtocolError::ShieldedBuildError(
46            "token direct purchase to pool count must be greater than zero".to_string(),
47        ));
48    }
49    if token_count > i64::MAX as u64 {
50        return Err(ProtocolError::ShieldedBuildError(format!(
51            "token direct purchase to pool count {} exceeds maximum allowed value {}",
52            token_count,
53            i64::MAX as u64
54        )));
55    }
56
57    let extra_sighash_data = token_pool_output_only_extra_sighash_data(
58        TokenTransitionActionType::DirectPurchaseToPool,
59        token_id.as_bytes(),
60        owner_id.as_bytes(),
61        platform_version,
62    )?;
63    let bundle = build_output_only_bundle(
64        recipient,
65        token_count,
66        memo,
67        sender_ovk,
68        0,
69        &extra_sighash_data,
70        prover,
71    )?;
72    let sb = serialize_authorized_bundle(&bundle);
73
74    if sb.value_balance != -(token_count as i64) {
75        return Err(ProtocolError::ShieldedBuildError(format!(
76            "token direct purchase to pool count bundle value balance {} does not equal -{}",
77            sb.value_balance, token_count
78        )));
79    }
80
81    BatchTransition::new_token_direct_purchase_to_pool_transition(
82        token_id,
83        owner_id,
84        data_contract_id,
85        token_contract_position,
86        token_count,
87        total_agreed_price,
88        OrchardBundleParams {
89            actions: sb.actions,
90            anchor: sb.anchor,
91            proof: sb.proof,
92            binding_signature: sb.binding_signature,
93        },
94        identity_public_key,
95        identity_contract_nonce,
96        user_fee_increase,
97        signer,
98        platform_version,
99        None,
100    )
101    .await
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use crate::shielded::builder::test_helpers::{
108        test_identity_key, test_orchard_address, DummyIdentitySigner, TestProver,
109    };
110
111    #[tokio::test]
112    async fn rejects_zero_count() {
113        let key = test_identity_key();
114        let err = build_token_direct_purchase_to_pool_transition(
115            Identifier::from([1u8; 32]),
116            Identifier::from([2u8; 32]),
117            Identifier::from([3u8; 32]),
118            0,
119            &test_orchard_address(),
120            0,
121            1_000,
122            [0u8; 36],
123            None,
124            &key,
125            1,
126            0,
127            &DummyIdentitySigner,
128            &TestProver,
129            PlatformVersion::latest(),
130        )
131        .await
132        .expect_err("zero count must be rejected")
133        .to_string();
134        assert!(err.contains("greater than zero"), "unexpected error: {err}");
135    }
136}