1pub mod builder;
57pub mod errors;
58pub mod firewall;
59pub mod registry;
60pub mod types;
61
62#[cfg(feature = "testnet")]
63pub mod testnet;
64
65pub use builder::{
68 build_firewall_lock_args, build_firewall_lock_script, build_firewall_spend_cell_deps,
69};
70pub use errors::{error_codes, FirewallError};
71pub use firewall::{check_transaction, is_blacklisted, preflight_check};
72pub use registry::{encode_governance_header, encode_registry_payload, parse_registry_payload};
73pub use types::{
74 CellDepLike, DepType, FirewallConfig, FirewallLockConfig, FirewallSpendDepsConfig,
75 GovernanceHeader, HashType, OutPointLike, RegistryEntry, RegistryPayload, RegistrySpec,
76 ScriptLike, TransactionCellDep, TxOutputLike, UnsignedTxLike,
77};
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 fn spec(tag: u8) -> RegistrySpec {
86 let mut type_id = [0u8; 32];
87 type_id[0] = tag;
88 let mut code_hash = [0u8; 32];
89 code_hash[0] = tag;
90 RegistrySpec {
91 code_hash,
92 hash_type: HashType::Type,
93 type_id_value: type_id,
94 required: true,
95 }
96 }
97
98 fn dep_for_spec(s: &RegistrySpec, data: Vec<u8>) -> CellDepLike {
99 let mut args = vec![0u8; 66];
101 args[34..66].copy_from_slice(&s.type_id_value);
102 CellDepLike {
103 type_script: Some(ScriptLike {
104 code_hash: s.code_hash,
105 hash_type: s.hash_type.clone(),
106 args,
107 }),
108 data,
109 }
110 }
111
112 fn registry(ids: &[&[u8]]) -> Vec<u8> {
113 registry_with_expiry(&ids.iter().map(|id| (*id, 0u64)).collect::<Vec<_>>())
114 }
115
116 fn registry_with_expiry(ids: &[(&[u8], u64)]) -> Vec<u8> {
117 encode_registry_payload(&RegistryPayload {
118 version: 2,
119 governance_header: None,
120 entries: ids
121 .iter()
122 .map(|(id, exp)| RegistryEntry {
123 identifier: id.to_vec(),
124 expires_at: *exp,
125 })
126 .collect(),
127 })
128 .unwrap()
129 }
130
131 fn cfg1(s: RegistrySpec) -> FirewallConfig {
132 FirewallConfig {
133 registries: vec![s],
134 }
135 }
136
137 #[test]
140 fn reject_missing_dep() {
141 let s = spec(1);
142 let tx = UnsignedTxLike {
143 cell_deps: vec![],
144 outputs: vec![],
145 };
146 let err = check_transaction(&cfg1(s), &tx, 0).unwrap_err();
147 assert_eq!(err, FirewallError::MissingRegistryCellDep);
148 assert_eq!(err.code(), 8);
149 }
150
151 #[test]
152 fn reject_blacklisted_lock_args() {
153 let s = spec(1);
154 let dep = dep_for_spec(&s, registry(&[&[0xaa, 0xbb]]));
155 let tx = UnsignedTxLike {
156 cell_deps: vec![dep],
157 outputs: vec![TxOutputLike {
158 lock_args: vec![0xaa, 0xbb],
159 type_args: None,
160 }],
161 };
162 let err = check_transaction(&cfg1(s), &tx, 0).unwrap_err();
163 assert_eq!(err, FirewallError::BlacklistedLockArgs);
164 assert_eq!(err.code(), 11);
165 }
166
167 #[test]
168 fn reject_ambiguous_registry_dep() {
169 let s = spec(1);
170 let dep = dep_for_spec(&s, registry(&[&[0xaa]]));
171 let tx = UnsignedTxLike {
172 cell_deps: vec![dep.clone(), dep],
173 outputs: vec![TxOutputLike {
174 lock_args: vec![0x00],
175 type_args: None,
176 }],
177 };
178 let err = check_transaction(&cfg1(s), &tx, 0).unwrap_err();
179 assert_eq!(err, FirewallError::AmbiguousRegistryCellDep);
180 assert_eq!(err.code(), 17);
181 }
182
183 #[test]
184 fn reject_registry_not_sorted() {
185 let s = spec(1);
186 let dep = dep_for_spec(&s, registry(&[&[0xbb], &[0xaa]]));
187 let tx = UnsignedTxLike {
188 cell_deps: vec![dep],
189 outputs: vec![TxOutputLike {
190 lock_args: vec![0x00],
191 type_args: None,
192 }],
193 };
194 let err = check_transaction(&cfg1(s), &tx, 0).unwrap_err();
195 assert_eq!(err, FirewallError::RegistryNotSorted);
196 assert_eq!(err.code(), 10);
197 }
198
199 #[test]
200 fn reject_blacklisted_type_args() {
201 let s = spec(1);
202 let dep = dep_for_spec(&s, registry(&[&[0x55, 0x66]]));
203 let tx = UnsignedTxLike {
204 cell_deps: vec![dep],
205 outputs: vec![TxOutputLike {
206 lock_args: vec![0x11, 0x22],
207 type_args: Some(vec![0x55, 0x66]),
208 }],
209 };
210 let err = check_transaction(&cfg1(s), &tx, 0).unwrap_err();
211 assert_eq!(err, FirewallError::BlacklistedTypeArgs);
212 assert_eq!(err.code(), 12);
213 }
214
215 #[test]
216 fn reject_v1_registry() {
217 let mut data = Vec::new();
218 data.extend_from_slice(b"BLKL");
219 data.push(1);
220 data.extend_from_slice(&0u32.to_le_bytes());
221 assert_eq!(
222 parse_registry_payload(&data).unwrap_err(),
223 FirewallError::InvalidRegistryData,
224 );
225 }
226
227 #[test]
228 fn reject_unknown_version() {
229 let mut data = Vec::new();
230 data.extend_from_slice(b"BLKL");
231 data.push(3);
232 data.extend_from_slice(&[0u8; 4]);
233 assert_eq!(
234 parse_registry_payload(&data).unwrap_err(),
235 FirewallError::InvalidRegistryData,
236 );
237 }
238
239 #[test]
240 fn parse_v2_registry_with_governance_header() {
241 let data = registry(&[&[0xaa, 0xbb]]);
242 let payload = parse_registry_payload(&data).unwrap();
243 assert_eq!(payload.version, 2);
244 assert_eq!(payload.entries.len(), 1);
245 let gh = payload.governance_header.unwrap();
246 assert_eq!(gh.signer_count, 0);
247 }
248
249 #[test]
250 fn expire_check_active() {
251 let s = spec(1);
252 let dep = dep_for_spec(&s, registry_with_expiry(&[(&[0xaa], 1000)]));
253 let tx = UnsignedTxLike {
254 cell_deps: vec![dep],
255 outputs: vec![TxOutputLike {
256 lock_args: vec![0xaa],
257 type_args: None,
258 }],
259 };
260 assert_eq!(
261 check_transaction(&cfg1(s), &tx, 999).unwrap_err(),
262 FirewallError::BlacklistedLockArgs,
263 );
264 }
265
266 #[test]
267 fn expire_check_expired() {
268 let s = spec(1);
269 let dep = dep_for_spec(&s, registry_with_expiry(&[(&[0xaa], 1000)]));
270 let tx = UnsignedTxLike {
271 cell_deps: vec![dep],
272 outputs: vec![TxOutputLike {
273 lock_args: vec![0xaa],
274 type_args: None,
275 }],
276 };
277 assert!(check_transaction(&cfg1(s), &tx, 1000).is_ok());
278 }
279
280 #[test]
281 fn permanent_entry_always_blacklisted() {
282 let s = spec(1);
283 let dep = dep_for_spec(&s, registry_with_expiry(&[(&[0xbb], 0)]));
284 let tx = UnsignedTxLike {
285 cell_deps: vec![dep],
286 outputs: vec![TxOutputLike {
287 lock_args: vec![0xbb],
288 type_args: None,
289 }],
290 };
291 assert_eq!(
292 check_transaction(&cfg1(s), &tx, u64::MAX).unwrap_err(),
293 FirewallError::BlacklistedLockArgs,
294 );
295 }
296
297 #[test]
298 fn multi_registry_both_checked() {
299 let s1 = spec(1);
300 let s2 = spec(2);
301 let dep1 = dep_for_spec(&s1, registry(&[&[0x11]]));
302 let dep2 = dep_for_spec(&s2, registry(&[&[0x22]]));
303 let firewall_cfg = FirewallConfig {
304 registries: vec![s1, s2],
305 };
306 let tx = UnsignedTxLike {
307 cell_deps: vec![dep1, dep2],
308 outputs: vec![TxOutputLike {
309 lock_args: vec![0x22],
310 type_args: None,
311 }],
312 };
313 assert_eq!(
314 check_transaction(&firewall_cfg, &tx, 0).unwrap_err(),
315 FirewallError::BlacklistedLockArgs,
316 );
317 }
318
319 #[test]
320 fn multi_registry_missing_required() {
321 let s1 = spec(1);
322 let s2 = spec(2);
323 let dep1 = dep_for_spec(&s1, registry(&[]));
324 let firewall_cfg = FirewallConfig {
325 registries: vec![s1, s2],
326 };
327 let tx = UnsignedTxLike {
328 cell_deps: vec![dep1],
329 outputs: vec![],
330 };
331 assert_eq!(
332 check_transaction(&firewall_cfg, &tx, 0).unwrap_err(),
333 FirewallError::MissingRegistryCellDep,
334 );
335 }
336
337 #[test]
338 fn multi_registry_optional_miss_ok() {
339 let s1 = spec(1);
340 let mut s2 = spec(2);
341 s2.required = false;
342 let dep1 = dep_for_spec(&s1, registry(&[]));
343 let firewall_cfg = FirewallConfig {
344 registries: vec![s1, s2],
345 };
346 let tx = UnsignedTxLike {
347 cell_deps: vec![dep1],
348 outputs: vec![TxOutputLike {
349 lock_args: vec![0x99],
350 type_args: None,
351 }],
352 };
353 assert!(check_transaction(&firewall_cfg, &tx, 0).is_ok());
354 }
355
356 #[test]
357 fn v2_trailing_data_rejected() {
358 let mut data = registry(&[&[0xaa]]);
359 data.push(0xff);
360 assert_eq!(
361 parse_registry_payload(&data).unwrap_err(),
362 FirewallError::InvalidRegistryData,
363 );
364 }
365
366 #[test]
369 fn dep_with_wrong_args_length_not_matched() {
370 let s = spec(1);
371 let mut args = vec![0u8; 67];
373 args[34..66].copy_from_slice(&s.type_id_value);
374 let dep = CellDepLike {
375 type_script: Some(ScriptLike {
376 code_hash: s.code_hash,
377 hash_type: s.hash_type.clone(),
378 args,
379 }),
380 data: registry(&[]),
381 };
382 let tx = UnsignedTxLike {
383 cell_deps: vec![dep],
384 outputs: vec![],
385 };
386 assert_eq!(
387 check_transaction(&cfg1(s), &tx, 0).unwrap_err(),
388 FirewallError::MissingRegistryCellDep,
389 );
390 }
391
392 #[test]
395 fn build_lock_args_roundtrip() {
396 let spec = RegistrySpec {
397 code_hash: [0xab; 32],
398 hash_type: HashType::Type,
399 type_id_value: [0xcd; 32],
400 required: true,
401 };
402 let config = FirewallLockConfig {
403 firewall_code_hash: [0x01; 32],
404 firewall_hash_type: HashType::Type,
405 flags: 0x01,
406 registries: vec![spec],
407 inner_code_hash: [0x02; 32],
408 inner_hash_type: HashType::Data1,
409 inner_args: vec![0x11, 0x22, 0x33],
410 };
411 let args = build_firewall_lock_args(&config).unwrap();
412 assert_eq!(args[0], 0x02); assert_eq!(args[1], 0x01); assert_eq!(args[2], 0x01); assert_eq!(&args[3..35], &[0xab; 32]); assert_eq!(args[35], 0x01); assert_eq!(&args[36..68], &[0xcd; 32]); assert_eq!(args[68], 0x01); assert_eq!(&args[69..101], &[0x02; 32]); assert_eq!(args[101], 0x02); assert_eq!(args[102], 0x03);
422 assert_eq!(args[103], 0x00); assert_eq!(&args[104..107], &[0x11, 0x22, 0x33]); }
425
426 #[test]
427 fn build_lock_args_rejects_no_check_bits() {
428 let config = FirewallLockConfig {
429 firewall_code_hash: [0u8; 32],
430 firewall_hash_type: HashType::Type,
431 flags: 0x00,
432 registries: vec![],
433 inner_code_hash: [0u8; 32],
434 inner_hash_type: HashType::Type,
435 inner_args: vec![],
436 };
437 assert_eq!(
438 build_firewall_lock_args(&config).unwrap_err(),
439 FirewallError::InvalidRegistryData,
440 );
441 }
442
443 #[test]
444 fn build_lock_args_rejects_reserved_bits() {
445 let config = FirewallLockConfig {
446 firewall_code_hash: [0u8; 32],
447 firewall_hash_type: HashType::Type,
448 flags: 0x05, registries: vec![],
450 inner_code_hash: [0u8; 32],
451 inner_hash_type: HashType::Type,
452 inner_args: vec![],
453 };
454 assert_eq!(
455 build_firewall_lock_args(&config).unwrap_err(),
456 FirewallError::InvalidRegistryData,
457 );
458 }
459
460 #[test]
463 fn encode_decode_roundtrip() {
464 let original = RegistryPayload {
465 version: 2,
466 governance_header: Some(GovernanceHeader {
467 signer_count: 2,
468 threshold: 2,
469 pubkeys: vec![[0x02; 33], [0x03; 33]],
470 validator_count: 3,
471 validator_merkle_root: [0xee; 32],
472 }),
473 entries: vec![
474 RegistryEntry {
475 identifier: vec![0x01],
476 expires_at: 0,
477 },
478 RegistryEntry {
479 identifier: vec![0x02],
480 expires_at: 9999,
481 },
482 ],
483 };
484 let encoded = encode_registry_payload(&original).unwrap();
485 let decoded = parse_registry_payload(&encoded).unwrap();
486 assert_eq!(decoded.version, 2);
487 assert_eq!(decoded.entries.len(), 2);
488 let gh = decoded.governance_header.unwrap();
489 assert_eq!(gh.signer_count, 2);
490 assert_eq!(gh.threshold, 2);
491 assert_eq!(gh.pubkeys.len(), 2);
492 assert_eq!(gh.validator_count, 3);
493 assert_eq!(gh.validator_merkle_root, [0xee; 32]);
494 }
495
496 #[test]
497 fn encode_registry_payload_rejects_long_id() {
498 let payload = RegistryPayload {
499 version: 2,
500 governance_header: None,
501 entries: vec![RegistryEntry {
502 identifier: vec![0u8; 256], expires_at: 0,
504 }],
505 };
506 assert_eq!(
507 encode_registry_payload(&payload).unwrap_err(),
508 FirewallError::InvalidRegistryData,
509 );
510 }
511
512 #[test]
515 fn is_blacklisted_standalone() {
516 let payload = parse_registry_payload(®istry(&[&[0xde, 0xad]])).unwrap();
517 assert!(is_blacklisted(&[0xde, 0xad], &[payload.clone()], 0));
518 assert!(!is_blacklisted(&[0xbe, 0xef], &[payload], 0));
519 }
520
521 #[test]
522 fn preflight_check_standalone() {
523 let payload = parse_registry_payload(®istry(&[&[0xca, 0xfe]])).unwrap();
524 let ok_outputs = vec![TxOutputLike {
525 lock_args: vec![0x00],
526 type_args: None,
527 }];
528 assert!(preflight_check(&ok_outputs, &[payload.clone()], 0).is_ok());
529 let bad_outputs = vec![TxOutputLike {
530 lock_args: vec![0xca, 0xfe],
531 type_args: None,
532 }];
533 assert_eq!(
534 preflight_check(&bad_outputs, &[payload], 0).unwrap_err(),
535 FirewallError::BlacklistedLockArgs,
536 );
537 }
538
539 #[test]
542 fn error_code_values_match_contract() {
543 assert_eq!(error_codes::INVALID_ARGS_LAYOUT, 5);
544 assert_eq!(error_codes::UNSUPPORTED_VERSION, 6);
545 assert_eq!(error_codes::UNSUPPORTED_FLAGS, 7);
546 assert_eq!(error_codes::MISSING_REGISTRY_CELL_DEP, 8);
547 assert_eq!(error_codes::INVALID_REGISTRY_DATA, 9);
548 assert_eq!(error_codes::REGISTRY_NOT_SORTED, 10);
549 assert_eq!(error_codes::BLACKLISTED_LOCK_ARGS, 11);
550 assert_eq!(error_codes::BLACKLISTED_TYPE_ARGS, 12);
551 assert_eq!(error_codes::MISSING_INNER_LOCK_CELL_DEP, 13);
552 assert_eq!(error_codes::INVALID_INNER_LOCK_SCRIPT, 14);
553 assert_eq!(error_codes::INNER_LOCK_REJECTED, 15);
554 assert_eq!(error_codes::OUTPUT_SCRIPT_PARSE_FAILED, 16);
555 assert_eq!(error_codes::AMBIGUOUS_REGISTRY_CELL_DEP, 17);
556 }
557}