1use dashcore::{InstantLock, Transaction};
2
3use std::collections::BTreeMap;
4
5use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof;
6use crate::identity::state_transition::asset_lock_proof::{AssetLockProof, InstantAssetLockProof};
7use crate::identity::{Identity, IdentityPublicKey, KeyID};
8use crate::prelude::{Identifier, IdentityNonce};
9
10use crate::identity::identity_factory::IdentityFactory;
11#[cfg(feature = "state-transitions")]
12use crate::state_transition::{
13 identity_create_transition::IdentityCreateTransition,
14 identity_credit_transfer_transition::IdentityCreditTransferTransition,
15 identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition,
16 identity_topup_transition::IdentityTopUpTransition,
17 identity_update_transition::IdentityUpdateTransition,
18 public_key_in_creation::IdentityPublicKeyInCreation,
19};
20
21use crate::identity::core_script::CoreScript;
22use crate::withdrawal::Pooling;
23use crate::ProtocolError;
24
25#[derive(Clone)]
26pub struct IdentityFacade {
27 factory: IdentityFactory,
28}
29
30impl IdentityFacade {
31 pub fn new(protocol_version: u32) -> Self {
32 Self {
33 factory: IdentityFactory::new(protocol_version),
34 }
35 }
36
37 pub fn create(
38 &self,
39 id: Identifier,
40 public_keys: BTreeMap<KeyID, IdentityPublicKey>,
41 ) -> Result<Identity, ProtocolError> {
42 self.factory.create(id, public_keys)
43 }
44
45 #[cfg(all(feature = "identity-serialization", feature = "client"))]
56 pub fn create_from_buffer(
57 &self,
58 buffer: Vec<u8>,
59 #[cfg(feature = "validation")] skip_validation: bool,
60 ) -> Result<Identity, ProtocolError> {
61 self.factory.create_from_buffer(
62 buffer,
63 #[cfg(feature = "validation")]
64 skip_validation,
65 )
66 }
67
68 pub fn create_instant_lock_proof(
69 instant_lock: InstantLock,
70 asset_lock_transaction: Transaction,
71 output_index: u32,
72 ) -> InstantAssetLockProof {
73 IdentityFactory::create_instant_lock_proof(
74 instant_lock,
75 asset_lock_transaction,
76 output_index,
77 )
78 }
79
80 pub fn create_chain_asset_lock_proof(
81 core_chain_locked_height: u32,
82 out_point: [u8; 36],
83 ) -> ChainAssetLockProof {
84 IdentityFactory::create_chain_asset_lock_proof(core_chain_locked_height, out_point)
85 }
86
87 #[cfg(feature = "state-transitions")]
88 pub fn create_identity_create_transition(
89 &self,
90 identity: &Identity,
91 asset_lock_proof: AssetLockProof,
92 ) -> Result<IdentityCreateTransition, ProtocolError> {
93 self.factory
94 .create_identity_create_transition(identity, asset_lock_proof)
95 }
96
97 #[cfg(feature = "state-transitions")]
98 pub fn create_identity_topup_transition(
99 &self,
100 identity_id: Identifier,
101 asset_lock_proof: AssetLockProof,
102 ) -> Result<IdentityTopUpTransition, ProtocolError> {
103 self.factory
104 .create_identity_topup_transition(identity_id, asset_lock_proof)
105 }
106
107 #[cfg(feature = "state-transitions")]
108 pub fn create_identity_credit_transfer_transition(
109 &self,
110 identity: &Identity,
111 recipient_id: Identifier,
112 amount: u64,
113 identity_nonce: IdentityNonce,
114 ) -> Result<IdentityCreditTransferTransition, ProtocolError> {
115 self.factory.create_identity_credit_transfer_transition(
116 identity,
117 recipient_id,
118 amount,
119 identity_nonce,
120 )
121 }
122
123 #[cfg(feature = "state-transitions")]
124 pub fn create_identity_credit_withdrawal_transition(
125 &self,
126 identity_id: Identifier,
127 amount: u64,
128 core_fee_per_byte: u32,
129 pooling: Pooling,
130 output_script: Option<CoreScript>,
131 identity_nonce: u64,
132 ) -> Result<IdentityCreditWithdrawalTransition, ProtocolError> {
133 self.factory.create_identity_credit_withdrawal_transition(
134 identity_id,
135 amount,
136 core_fee_per_byte,
137 pooling,
138 output_script,
139 identity_nonce,
140 )
141 }
142
143 #[cfg(feature = "state-transitions")]
144 pub fn create_identity_update_transition(
145 &self,
146 identity: Identity,
147 identity_nonce: u64,
148 add_public_keys: Option<Vec<IdentityPublicKeyInCreation>>,
149 public_key_ids_to_disable: Option<Vec<KeyID>>,
150 ) -> Result<IdentityUpdateTransition, ProtocolError> {
151 self.factory.create_identity_update_transition(
152 identity,
153 identity_nonce,
154 add_public_keys,
155 public_key_ids_to_disable,
156 )
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use crate::identity::accessors::IdentityGettersV0;
164
165 fn facade_v1() -> IdentityFacade {
166 IdentityFacade::new(1)
167 }
168
169 #[test]
170 fn new_constructs_facade() {
171 let facade = IdentityFacade::new(1);
172 let cloned = facade.clone();
173 let id = Identifier::random();
175 let _identity = cloned.create(id, BTreeMap::new()).unwrap();
176 }
177
178 #[test]
179 fn create_returns_identity_with_given_id() {
180 let facade = facade_v1();
181 let id = Identifier::from([11u8; 32]);
182 let identity = facade.create(id, BTreeMap::new()).expect("create");
183 assert_eq!(identity.id(), id);
184 assert_eq!(identity.balance(), 0);
185 assert_eq!(identity.revision(), 0);
186 assert_eq!(identity.public_keys().len(), 0);
187 }
188
189 #[test]
190 fn create_errors_on_unknown_protocol_version() {
191 let facade = IdentityFacade::new(u32::MAX);
192 let id = Identifier::from([12u8; 32]);
193 let result = facade.create(id, BTreeMap::new());
194 assert!(result.is_err());
195 }
196
197 #[test]
198 fn create_chain_asset_lock_proof_preserves_inputs() {
199 let out_point = [7u8; 36];
200 let proof = IdentityFacade::create_chain_asset_lock_proof(333, out_point);
201 assert_eq!(proof.core_chain_locked_height, 333);
202 assert_eq!(proof.out_point, out_point.into());
203 }
204
205 #[test]
206 fn create_instant_lock_proof_preserves_output_index() {
207 use crate::identity::state_transition::asset_lock_proof::AssetLockProof;
208 use crate::tests::fixtures::instant_asset_lock_proof_fixture;
209 let fixture = instant_asset_lock_proof_fixture(None, None);
210 let (il, tx) = match &fixture {
211 AssetLockProof::Instant(p) => (p.instant_lock.clone(), p.transaction.clone()),
212 _ => panic!("expected instant"),
213 };
214 let built = IdentityFacade::create_instant_lock_proof(il, tx.clone(), 11);
215 assert_eq!(built.output_index, 11);
216 assert_eq!(built.transaction.txid(), tx.txid());
217 }
218
219 #[cfg(feature = "identity-serialization")]
220 #[test]
221 fn create_from_buffer_roundtrips_serialized_identity() {
222 use crate::serialization::PlatformSerializable;
223 let facade = facade_v1();
224 let id = Identifier::from([13u8; 32]);
225 let identity = facade.create(id, BTreeMap::new()).unwrap();
226 let bytes = PlatformSerializable::serialize_to_bytes(&identity).unwrap();
227 let restored = facade
228 .create_from_buffer(
229 bytes,
230 #[cfg(feature = "validation")]
231 true,
232 )
233 .expect("roundtrip succeeds");
234 assert_eq!(restored.id(), id);
235 }
236
237 #[cfg(feature = "identity-serialization")]
238 #[test]
239 fn create_from_buffer_errors_on_garbage() {
240 let facade = facade_v1();
241 let result = facade.create_from_buffer(
242 vec![0xEE; 10],
243 #[cfg(feature = "validation")]
244 true,
245 );
246 assert!(result.is_err());
247 }
248
249 #[cfg(feature = "state-transitions")]
250 mod state_transitions {
251 use super::*;
252 use crate::identity::identity_public_key::v0::IdentityPublicKeyV0;
253 use crate::identity::{KeyType, Purpose, SecurityLevel};
254 use crate::state_transition::identity_credit_transfer_transition::accessors::IdentityCreditTransferTransitionAccessorsV0;
255 use crate::state_transition::identity_credit_withdrawal_transition::accessors::IdentityCreditWithdrawalTransitionAccessorsV0;
256 use crate::state_transition::identity_topup_transition::accessors::IdentityTopUpTransitionAccessorsV0;
257 use crate::state_transition::identity_update_transition::accessors::IdentityUpdateTransitionAccessorsV0;
258 use crate::tests::fixtures::instant_asset_lock_proof_fixture;
259
260 fn sample_pk(id: u32) -> IdentityPublicKey {
261 IdentityPublicKey::V0(IdentityPublicKeyV0 {
262 id,
263 purpose: Purpose::AUTHENTICATION,
264 security_level: SecurityLevel::MASTER,
265 contract_bounds: None,
266 key_type: KeyType::ECDSA_SECP256K1,
267 read_only: false,
268 data: vec![0u8; 33].into(),
269 disabled_at: None,
270 })
271 }
272
273 #[test]
274 fn create_identity_create_transition_wraps_factory() {
275 let facade = facade_v1();
276 let proof = instant_asset_lock_proof_fixture(None, None);
277 let expected_id = proof.create_identifier().unwrap();
278 let identity = facade.create(expected_id, BTreeMap::new()).unwrap();
279 let transition = facade
280 .create_identity_create_transition(&identity, proof)
281 .expect("transition");
282 match transition {
283 IdentityCreateTransition::V0(v0) => {
284 assert_eq!(v0.identity_id, expected_id);
285 }
286 }
287 }
288
289 #[test]
290 fn create_identity_topup_transition_uses_given_id_and_proof() {
291 let facade = facade_v1();
292 let identity_id = Identifier::from([21u8; 32]);
293 let proof = instant_asset_lock_proof_fixture(None, None);
294 let transition = facade
295 .create_identity_topup_transition(identity_id, proof)
296 .expect("transition");
297 assert_eq!(*transition.identity_id(), identity_id);
298 }
299
300 #[test]
301 fn create_identity_credit_transfer_transition_uses_identity_id() {
302 let facade = facade_v1();
303 let id = Identifier::from([31u8; 32]);
304 let identity = facade.create(id, BTreeMap::new()).unwrap();
305 let recipient = Identifier::from([32u8; 32]);
306 let transition = facade
307 .create_identity_credit_transfer_transition(&identity, recipient, 500, 3)
308 .expect("transition");
309 assert_eq!(transition.identity_id(), id);
310 assert_eq!(transition.recipient_id(), recipient);
311 assert_eq!(transition.amount(), 500);
312 assert_eq!(transition.nonce(), 3);
313 }
314
315 #[test]
316 fn create_identity_credit_withdrawal_transition_v0_requires_output_script() {
317 let facade = facade_v1();
318 let id = Identifier::from([41u8; 32]);
319 let result = facade.create_identity_credit_withdrawal_transition(
320 id,
321 1000,
322 1,
323 Pooling::Never,
324 None,
325 5,
326 );
327 match result {
328 Err(ProtocolError::Generic(msg)) => {
329 assert!(msg.contains("Output script is required"));
330 }
331 other => panic!("expected Generic error, got {:?}", other),
332 }
333 }
334
335 #[test]
336 fn create_identity_credit_withdrawal_transition_v0_with_script_succeeds() {
337 let facade = facade_v1();
338 let id = Identifier::from([42u8; 32]);
339 let script = CoreScript::new_p2pkh([0xFF; 20]);
340 let transition = facade
341 .create_identity_credit_withdrawal_transition(
342 id,
343 1500,
344 2,
345 Pooling::IfAvailable,
346 Some(script.clone()),
347 7,
348 )
349 .expect("transition");
350 assert_eq!(transition.identity_id(), id);
351 assert_eq!(transition.amount(), 1500);
352 assert_eq!(transition.core_fee_per_byte(), 2);
353 assert_eq!(transition.output_script(), Some(script));
354 assert_eq!(transition.nonce(), 7);
355 }
356
357 #[test]
358 fn create_identity_update_transition_adds_and_disables_keys() {
359 let facade = facade_v1();
360 let id = Identifier::from([51u8; 32]);
361 let identity = facade.create(id, BTreeMap::new()).unwrap();
362 let new_key: IdentityPublicKeyInCreation = sample_pk(1).into();
363 let transition = facade
364 .create_identity_update_transition(
365 identity,
366 9,
367 Some(vec![new_key.clone()]),
368 Some(vec![5u32, 6u32]),
369 )
370 .expect("transition");
371 assert_eq!(transition.identity_id(), id);
372 assert_eq!(transition.nonce(), 9);
373 assert_eq!(transition.revision(), 1);
375 assert_eq!(transition.public_keys_to_add().len(), 1);
376 assert_eq!(transition.public_key_ids_to_disable(), &[5u32, 6][..]);
377 }
378 }
379}