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