dpp/shielded/builder/
token_claim_to_pool.rs1use grovedb_commitment_tree::OutgoingViewingKey;
2
3use crate::address_funds::OrchardAddress;
4use crate::balances::credits::TokenAmount;
5use crate::data_contract::associated_token::token_distribution_key::TokenDistributionType;
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#[allow(clippy::too_many_arguments)]
28pub async fn build_token_claim_to_pool_transition<
29 S: Signer<IdentityPublicKey>,
30 P: OrchardProver,
31>(
32 token_id: Identifier,
33 owner_id: Identifier,
34 data_contract_id: Identifier,
35 token_contract_position: u16,
36 recipient: &OrchardAddress,
37 amount: TokenAmount,
38 distribution_type: TokenDistributionType,
39 claim_up_to: Option<u64>,
40 memo: [u8; 36],
41 sender_ovk: Option<OutgoingViewingKey>,
42 public_note: Option<String>,
43 identity_public_key: &IdentityPublicKey,
44 identity_contract_nonce: IdentityNonce,
45 user_fee_increase: UserFeeIncrease,
46 signer: &S,
47 prover: &P,
48 platform_version: &PlatformVersion,
49) -> Result<StateTransition, ProtocolError> {
50 if amount == 0 {
51 return Err(ProtocolError::ShieldedBuildError(
52 "token claim to pool amount must be greater than zero".to_string(),
53 ));
54 }
55 if amount > i64::MAX as u64 {
56 return Err(ProtocolError::ShieldedBuildError(format!(
57 "token claim to pool amount {} exceeds maximum allowed value {}",
58 amount,
59 i64::MAX as u64
60 )));
61 }
62 if distribution_type == TokenDistributionType::Perpetual && claim_up_to.is_none() {
63 return Err(ProtocolError::ShieldedBuildError(
64 "a perpetual claim into the pool must name the moment it claims up to".to_string(),
65 ));
66 }
67
68 let extra_sighash_data = token_pool_output_only_extra_sighash_data(
69 TokenTransitionActionType::ClaimToPool,
70 token_id.as_bytes(),
71 owner_id.as_bytes(),
72 platform_version,
73 )?;
74 let bundle = build_output_only_bundle(
75 recipient,
76 amount,
77 memo,
78 sender_ovk,
79 0,
80 &extra_sighash_data,
81 prover,
82 )?;
83 let sb = serialize_authorized_bundle(&bundle);
84
85 if sb.value_balance != -(amount as i64) {
86 return Err(ProtocolError::ShieldedBuildError(format!(
87 "token claim to pool amount bundle value balance {} does not equal -{}",
88 sb.value_balance, amount
89 )));
90 }
91
92 BatchTransition::new_token_claim_to_pool_transition(
93 token_id,
94 owner_id,
95 data_contract_id,
96 token_contract_position,
97 distribution_type,
98 claim_up_to,
99 OrchardBundleParams {
100 actions: sb.actions,
101 anchor: sb.anchor,
102 proof: sb.proof,
103 binding_signature: sb.binding_signature,
104 },
105 public_note,
106 identity_public_key,
107 identity_contract_nonce,
108 user_fee_increase,
109 signer,
110 platform_version,
111 None,
112 )
113 .await
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::shielded::builder::test_helpers::{
120 test_identity_key, test_orchard_address, DummyIdentitySigner, TestProver,
121 };
122
123 #[tokio::test]
124 async fn perpetual_claim_needs_a_moment() {
125 let key = test_identity_key();
126 let err = build_token_claim_to_pool_transition(
127 Identifier::from([1u8; 32]),
128 Identifier::from([2u8; 32]),
129 Identifier::from([3u8; 32]),
130 0,
131 &test_orchard_address(),
132 100,
133 TokenDistributionType::Perpetual,
134 None,
135 [0u8; 36],
136 None,
137 None,
138 &key,
139 1,
140 0,
141 &DummyIdentitySigner,
142 &TestProver,
143 PlatformVersion::latest(),
144 )
145 .await
146 .expect_err("perpetual claim without a moment must be rejected")
147 .to_string();
148 assert!(err.contains("claims up to"), "unexpected error: {err}");
149 }
150}