1use std::collections::BTreeMap;
2
3use crate::address_funds::AddressFundsFeeStrategy;
4use crate::address_funds::{OrchardAddress, PlatformAddress};
5use crate::fee::Credits;
6use crate::identity::signer::Signer;
7use crate::prelude::{AddressNonce, UserFeeIncrease};
8use crate::state_transition::shield_transition::methods::ShieldTransitionMethodsV0;
9use crate::state_transition::shield_transition::ShieldTransition;
10use crate::state_transition::StateTransition;
11use crate::ProtocolError;
12use platform_version::version::PlatformVersion;
13
14use super::{build_output_only_bundle, serialize_authorized_bundle, OrchardProver};
15
16#[allow(clippy::too_many_arguments)]
32pub async fn build_shield_transition<S: Signer<PlatformAddress>, P: OrchardProver>(
33 recipient: &OrchardAddress,
34 shield_amount: u64,
35 inputs: BTreeMap<PlatformAddress, (AddressNonce, Credits)>,
36 fee_strategy: AddressFundsFeeStrategy,
37 signer: &S,
38 user_fee_increase: UserFeeIncrease,
39 prover: &P,
40 memo: [u8; 36],
41 platform_version: &PlatformVersion,
42) -> Result<StateTransition, ProtocolError> {
43 if fee_strategy.is_empty() {
44 return Err(ProtocolError::ShieldedBuildError(
45 "fee_strategy must have at least one step".to_string(),
46 ));
47 }
48
49 let bundle = build_output_only_bundle(recipient, shield_amount, memo, prover)?;
50 let sb = serialize_authorized_bundle(&bundle);
51
52 ShieldTransition::try_from_bundle_with_signer(
53 inputs,
54 sb.actions,
55 sb.value_balance.unsigned_abs(),
56 sb.anchor,
57 sb.proof,
58 sb.binding_signature,
59 fee_strategy,
60 signer,
61 user_fee_increase,
62 platform_version,
63 )
64 .await
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use crate::address_funds::AddressFundsFeeStrategyStep;
71 use crate::address_funds::AddressWitness;
72 use crate::shielded::builder::test_helpers::{test_orchard_address, TestProver};
73 use platform_value::BinaryData;
74
75 #[derive(Debug)]
78 struct DummySigner;
79
80 #[async_trait::async_trait]
81 impl Signer<PlatformAddress> for DummySigner {
82 async fn sign(
83 &self,
84 _key: &PlatformAddress,
85 _data: &[u8],
86 ) -> Result<BinaryData, ProtocolError> {
87 Ok(BinaryData::new(vec![0u8; 65]))
88 }
89
90 async fn sign_create_witness(
91 &self,
92 _key: &PlatformAddress,
93 _data: &[u8],
94 ) -> Result<AddressWitness, ProtocolError> {
95 Ok(AddressWitness::P2pkh {
96 signature: BinaryData::new(vec![0u8; 65]),
97 })
98 }
99
100 fn can_sign_with(&self, _key: &PlatformAddress) -> bool {
101 true
102 }
103 }
104
105 #[tokio::test]
106 async fn test_build_shield_empty_fee_strategy() {
107 let recipient = test_orchard_address();
108 let platform_version = PlatformVersion::latest();
109 let result = build_shield_transition(
110 &recipient,
111 1000,
112 BTreeMap::new(),
113 vec![], &DummySigner,
115 0,
116 &TestProver,
117 [0u8; 36],
118 platform_version,
119 )
120 .await;
121
122 assert!(result.is_err());
123 let err = result.unwrap_err().to_string();
124 assert!(
125 err.contains("fee_strategy must have at least one step"),
126 "unexpected error: {}",
127 err
128 );
129 }
130
131 #[tokio::test]
132 async fn test_build_shield_transition_valid() {
133 let recipient = test_orchard_address();
134 let platform_version = PlatformVersion::latest();
135 let input_address = PlatformAddress::P2pkh([1u8; 20]);
137 let mut inputs = BTreeMap::new();
138 inputs.insert(input_address, (0u32, 100_000u64));
139
140 let fee_strategy = vec![AddressFundsFeeStrategyStep::DeductFromInput(0)];
141
142 let result = build_shield_transition(
143 &recipient,
144 50_000,
145 inputs,
146 fee_strategy,
147 &DummySigner,
148 0,
149 &TestProver,
150 [0u8; 36],
151 platform_version,
152 )
153 .await;
154
155 assert!(result.is_ok(), "expected Ok, got: {:?}", result.err());
156 match result.unwrap() {
157 StateTransition::Shield(_) => {} other => panic!("expected Shield variant, got {:?}", other),
159 }
160 }
161
162 #[tokio::test]
167 async fn test_build_shield_multiple_inputs_all_plumbed() {
168 let recipient = test_orchard_address();
171 let platform_version = PlatformVersion::latest();
172
173 let mut inputs = BTreeMap::new();
174 inputs.insert(PlatformAddress::P2pkh([1u8; 20]), (0u32, 100_000u64));
175 inputs.insert(PlatformAddress::P2pkh([2u8; 20]), (0u32, 200_000u64));
176 inputs.insert(PlatformAddress::P2pkh([3u8; 20]), (0u32, 300_000u64));
177
178 let fee_strategy = vec![AddressFundsFeeStrategyStep::DeductFromInput(0)];
179
180 let result = build_shield_transition(
181 &recipient,
182 50_000,
183 inputs,
184 fee_strategy,
185 &DummySigner,
186 0,
187 &TestProver,
188 [0u8; 36],
189 platform_version,
190 )
191 .await;
192 assert!(
193 result.is_ok(),
194 "multi-input shield should succeed: {:?}",
195 result.err()
196 );
197 }
198
199 #[tokio::test]
200 async fn test_build_shield_user_fee_increase_non_zero_succeeds() {
201 let recipient = test_orchard_address();
204 let platform_version = PlatformVersion::latest();
205 let input_address = PlatformAddress::P2pkh([5u8; 20]);
206 let mut inputs = BTreeMap::new();
207 inputs.insert(input_address, (0u32, 500_000u64));
208
209 let fee_strategy = vec![AddressFundsFeeStrategyStep::DeductFromInput(0)];
210
211 let result = build_shield_transition(
212 &recipient,
213 100_000,
214 inputs,
215 fee_strategy,
216 &DummySigner,
217 42, &TestProver,
219 [9u8; 36],
220 platform_version,
221 )
222 .await;
223 assert!(
224 result.is_ok(),
225 "non-zero user_fee_increase should succeed: {:?}",
226 result.err()
227 );
228 }
229
230 #[tokio::test]
231 async fn test_build_shield_memo_is_fully_plumbed() {
232 let recipient = test_orchard_address();
235 let platform_version = PlatformVersion::latest();
236 let input_address = PlatformAddress::P2pkh([9u8; 20]);
237 let mut inputs = BTreeMap::new();
238 inputs.insert(input_address, (5u32, 200_000u64));
239
240 let fee_strategy = vec![AddressFundsFeeStrategyStep::DeductFromInput(0)];
241 let mut memo = [0u8; 36];
242 for (i, b) in memo.iter_mut().enumerate() {
243 *b = i as u8;
244 }
245
246 let result = build_shield_transition(
247 &recipient,
248 80_000,
249 inputs,
250 fee_strategy,
251 &DummySigner,
252 0,
253 &TestProver,
254 memo,
255 platform_version,
256 )
257 .await;
258 assert!(
259 result.is_ok(),
260 "varied memo should succeed: {:?}",
261 result.err()
262 );
263 }
264}