Skip to main content

oxilean_codegen/solidity_backend/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::collections::HashMap;
6
7use super::types::{
8    ContractKind, SolAnalysisCache, SolConstantFoldingHelper, SolDepGraph, SolDominatorTree,
9    SolExtCache, SolExtConstFolder, SolExtDepGraph, SolExtDomTree, SolExtLiveness,
10    SolExtPassConfig, SolExtPassPhase, SolExtPassRegistry, SolExtPassStats, SolExtWorklist,
11    SolLivenessInfo, SolPassConfig, SolPassPhase, SolPassRegistry, SolPassStats, SolWorklist,
12    SolidityBackend, SolidityContract, SolidityEnum, SolidityError, SolidityEvent, SolidityExpr,
13    SolidityFunction, SolidityParam, SolidityStateVar, SolidityStmt, SolidityStruct, SolidityType,
14    StateMutability, Visibility,
15};
16
17/// Standard library constants available in every generated Solidity contract.
18/// These are injected as comments / utility fragments for the emitter.
19pub const SOLIDITY_RUNTIME: &str = r#"// SPDX-License-Identifier: MIT
20// OxiLean Solidity Runtime Library (inlined)
21
22/// @dev SafeMath-equivalent operations (Solidity 0.8+ has overflow checks built in)
23library OxiLeanMath {
24    /// @notice Returns the minimum of two values.
25    function min(uint256 a, uint256 b) internal pure returns (uint256) {
26        return a < b ? a : b;
27    }
28
29    /// @notice Returns the maximum of two values.
30    function max(uint256 a, uint256 b) internal pure returns (uint256) {
31        return a > b ? a : b;
32    }
33
34    /// @notice Returns the absolute difference.
35    function absDiff(uint256 a, uint256 b) internal pure returns (uint256) {
36        return a >= b ? a - b : b - a;
37    }
38
39    /// @notice Saturating addition (clamps to uint256 max).
40    function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
41        unchecked {
42            uint256 c = a + b;
43            return c < a ? type(uint256).max : c;
44        }
45    }
46
47    /// @notice Saturating subtraction (clamps to 0).
48    function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
49        return a > b ? a - b : 0;
50    }
51
52    /// @notice Integer square root (floor).
53    function sqrt(uint256 x) internal pure returns (uint256 r) {
54        if (x == 0) return 0;
55        r = 1;
56        uint256 xAux = x;
57        if (xAux >= 0x100000000000000000000000000000000) { r <<= 64; xAux >>= 128; }
58        if (xAux >= 0x10000000000000000) { r <<= 32; xAux >>= 64; }
59        if (xAux >= 0x100000000) { r <<= 16; xAux >>= 32; }
60        if (xAux >= 0x10000) { r <<= 8; xAux >>= 16; }
61        if (xAux >= 0x100) { r <<= 4; xAux >>= 8; }
62        if (xAux >= 0x10) { r <<= 2; xAux >>= 4; }
63        if (xAux >= 0x4) { r <<= 1; }
64        r = (r + x / r) >> 1;
65        r = (r + x / r) >> 1;
66        r = (r + x / r) >> 1;
67        r = (r + x / r) >> 1;
68        r = (r + x / r) >> 1;
69        r = (r + x / r) >> 1;
70        r = (r + x / r) >> 1;
71        return r > x / r ? r - 1 : r;
72    }
73}
74
75/// @dev Address utilities
76library OxiLeanAddress {
77    /// @notice Returns true if the address is the zero address.
78    function isZero(address addr) internal pure returns (bool) {
79        return addr == address(0);
80    }
81
82    /// @notice Converts an address to uint256.
83    function toUint256(address addr) internal pure returns (uint256) {
84        return uint256(uint160(addr));
85    }
86
87    /// @notice Converts uint256 to address.
88    function fromUint256(uint256 n) internal pure returns (address) {
89        return address(uint160(n));
90    }
91}
92
93/// @dev Bytes utilities
94library OxiLeanBytes {
95    /// @notice Converts bytes32 to bytes.
96    function toBytes(bytes32 b) internal pure returns (bytes memory) {
97        bytes memory result = new bytes(32);
98        assembly { mstore(add(result, 32), b) }
99        return result;
100    }
101
102    /// @notice Concatenates two byte arrays.
103    function concat(bytes memory a, bytes memory b) internal pure returns (bytes memory c) {
104        uint256 la = a.length;
105        uint256 lb = b.length;
106        c = new bytes(la + lb);
107        for (uint256 i = 0; i < la; i++) c[i] = a[i];
108        for (uint256 j = 0; j < lb; j++) c[la + j] = b[j];
109    }
110}
111"#;
112#[cfg(test)]
113mod tests {
114    use super::*;
115    #[test]
116    pub(super) fn test_uint256_display() {
117        assert_eq!(SolidityType::Uint256.to_string(), "uint256");
118    }
119    #[test]
120    pub(super) fn test_mapping_display() {
121        let ty = SolidityType::Mapping(
122            Box::new(SolidityType::Address),
123            Box::new(SolidityType::Uint256),
124        );
125        assert_eq!(ty.to_string(), "mapping(address => uint256)");
126    }
127    #[test]
128    pub(super) fn test_dyn_array_display() {
129        let ty = SolidityType::DynArray(Box::new(SolidityType::Uint256));
130        assert_eq!(ty.to_string(), "uint256[]");
131    }
132    #[test]
133    pub(super) fn test_fixed_array_display() {
134        let ty = SolidityType::FixedArray(Box::new(SolidityType::Bool), 10);
135        assert_eq!(ty.to_string(), "bool[10]");
136    }
137    #[test]
138    pub(super) fn test_string_type_display() {
139        assert_eq!(SolidityType::StringTy.to_string(), "string");
140    }
141    #[test]
142    pub(super) fn test_tuple_abi_canonical() {
143        let ty = SolidityType::Tuple(vec![
144            SolidityType::Uint256,
145            SolidityType::Address,
146            SolidityType::Bool,
147        ]);
148        assert_eq!(ty.abi_canonical(), "(uint256,address,bool)");
149    }
150    #[test]
151    pub(super) fn test_is_reference_type_string() {
152        assert!(SolidityType::StringTy.is_reference_type());
153    }
154    #[test]
155    pub(super) fn test_is_reference_type_uint256() {
156        assert!(!SolidityType::Uint256.is_reference_type());
157    }
158    #[test]
159    pub(super) fn test_is_reference_type_mapping() {
160        let ty = SolidityType::Mapping(
161            Box::new(SolidityType::Address),
162            Box::new(SolidityType::Uint256),
163        );
164        assert!(ty.is_reference_type());
165    }
166    #[test]
167    pub(super) fn test_param_new_value_type() {
168        let p = SolidityParam::new(SolidityType::Uint256, "amount");
169        assert!(p.location.is_none());
170        assert_eq!(p.to_string(), "uint256 amount");
171    }
172    #[test]
173    pub(super) fn test_param_new_reference_type() {
174        let p = SolidityParam::new(SolidityType::StringTy, "name");
175        assert_eq!(p.location.as_deref(), Some("memory"));
176        assert_eq!(p.to_string(), "string memory name");
177    }
178    #[test]
179    pub(super) fn test_param_calldata() {
180        let p = SolidityParam::calldata(SolidityType::Bytes, "data");
181        assert_eq!(p.location.as_deref(), Some("calldata"));
182        assert_eq!(p.to_string(), "bytes calldata data");
183    }
184    #[test]
185    pub(super) fn test_abi_signature() {
186        let mut func = SolidityFunction::new("transfer");
187        func.params
188            .push(SolidityParam::new(SolidityType::Address, "to"));
189        func.params
190            .push(SolidityParam::new(SolidityType::Uint256, "amount"));
191        assert_eq!(func.abi_signature(), "transfer(address,uint256)");
192    }
193    #[test]
194    pub(super) fn test_selector_length() {
195        let func = SolidityFunction::new("balanceOf");
196        let sel = func.selector();
197        assert_eq!(sel.len(), 4);
198    }
199    #[test]
200    pub(super) fn test_selector_deterministic() {
201        let f1 = SolidityFunction::new("approve");
202        let f2 = SolidityFunction::new("approve");
203        assert_eq!(f1.selector(), f2.selector());
204    }
205    #[test]
206    pub(super) fn test_msg_sender_display() {
207        assert_eq!(SolidityExpr::MsgSender.to_string(), "msg.sender");
208    }
209    #[test]
210    pub(super) fn test_binop_display() {
211        let expr = SolidityExpr::BinOp(
212            "+".into(),
213            Box::new(SolidityExpr::Var("a".into())),
214            Box::new(SolidityExpr::IntLit(1)),
215        );
216        assert_eq!(expr.to_string(), "(a + 1)");
217    }
218    #[test]
219    pub(super) fn test_ternary_display() {
220        let expr = SolidityExpr::Ternary(
221            Box::new(SolidityExpr::BoolLit(true)),
222            Box::new(SolidityExpr::IntLit(1)),
223            Box::new(SolidityExpr::IntLit(0)),
224        );
225        assert_eq!(expr.to_string(), "(true ? 1 : 0)");
226    }
227    #[test]
228    pub(super) fn test_keccak256_display() {
229        let expr = SolidityExpr::Keccak256(Box::new(SolidityExpr::Var("data".into())));
230        assert_eq!(expr.to_string(), "keccak256(data)");
231    }
232    #[test]
233    pub(super) fn test_type_max_display() {
234        let expr = SolidityExpr::TypeMax(SolidityType::Uint256);
235        assert_eq!(expr.to_string(), "type(uint256).max");
236    }
237    #[test]
238    pub(super) fn test_emit_empty_contract() {
239        let mut backend = SolidityBackend::new();
240        backend.add_contract(SolidityContract::new("Empty", ContractKind::Contract));
241        let src = backend.emit_contract();
242        assert!(src.contains("pragma solidity"));
243        assert!(src.contains("contract Empty {"));
244        assert!(src.contains("SPDX-License-Identifier: MIT"));
245    }
246    #[test]
247    pub(super) fn test_emit_interface() {
248        let mut backend = SolidityBackend::new();
249        let mut iface = SolidityContract::new("IERC20", ContractKind::Interface);
250        let mut func = SolidityFunction::new("totalSupply");
251        func.returns
252            .push(SolidityParam::new(SolidityType::Uint256, ""));
253        func.mutability = StateMutability::View;
254        func.body = vec![];
255        iface.functions.push(func);
256        backend.add_contract(iface);
257        let src = backend.emit_contract();
258        assert!(src.contains("interface IERC20 {"));
259        assert!(src.contains("function totalSupply()"));
260    }
261    #[test]
262    pub(super) fn test_emit_contract_with_state_var() {
263        let mut backend = SolidityBackend::new();
264        let mut contract = SolidityContract::new("Token", ContractKind::Contract);
265        contract.state_vars.push(SolidityStateVar {
266            ty: SolidityType::Mapping(
267                Box::new(SolidityType::Address),
268                Box::new(SolidityType::Uint256),
269            ),
270            name: "_balances".into(),
271            visibility: Visibility::Private,
272            is_immutable: false,
273            is_constant: false,
274            init: None,
275            doc: None,
276        });
277        backend.add_contract(contract);
278        let src = backend.emit_contract();
279        assert!(src.contains("mapping(address => uint256)"));
280        assert!(src.contains("_balances"));
281    }
282    #[test]
283    pub(super) fn test_emit_event() {
284        let mut backend = SolidityBackend::new();
285        let mut contract = SolidityContract::new("Token", ContractKind::Contract);
286        contract.events.push(SolidityEvent {
287            name: "Transfer".into(),
288            fields: vec![
289                (SolidityType::Address, true, "from".into()),
290                (SolidityType::Address, true, "to".into()),
291                (SolidityType::Uint256, false, "value".into()),
292            ],
293            anonymous: false,
294            doc: None,
295        });
296        backend.add_contract(contract);
297        let src = backend.emit_contract();
298        assert!(src.contains("event Transfer("));
299        assert!(src.contains("indexed from"));
300        assert!(src.contains("indexed to"));
301    }
302    #[test]
303    pub(super) fn test_emit_custom_error() {
304        let mut backend = SolidityBackend::new();
305        let mut contract = SolidityContract::new("Token", ContractKind::Contract);
306        contract.errors.push(SolidityError {
307            name: "InsufficientBalance".into(),
308            params: vec![
309                SolidityParam::new(SolidityType::Uint256, "available"),
310                SolidityParam::new(SolidityType::Uint256, "required"),
311            ],
312            doc: None,
313        });
314        backend.add_contract(contract);
315        let src = backend.emit_contract();
316        assert!(src.contains("error InsufficientBalance("));
317    }
318    #[test]
319    pub(super) fn test_emit_require_stmt() {
320        let stmt = SolidityStmt::Require(
321            SolidityExpr::BinOp(
322                ">".into(),
323                Box::new(SolidityExpr::Var("amount".into())),
324                Box::new(SolidityExpr::IntLit(0)),
325            ),
326            Some("Amount must be positive".into()),
327        );
328        let out = SolidityBackend::emit_stmt(&stmt, 2);
329        assert!(out.contains("require("));
330        assert!(out.contains("Amount must be positive"));
331    }
332    #[test]
333    pub(super) fn test_compile_decl() {
334        let mut backend = SolidityBackend::new();
335        let sv = backend.compile_decl("owner", SolidityType::Address);
336        assert_eq!(sv.name, "owner");
337        assert!(matches!(sv.ty, SolidityType::Address));
338    }
339    #[test]
340    pub(super) fn test_runtime_constant_not_empty() {
341        assert!(!SOLIDITY_RUNTIME.is_empty());
342        assert!(SOLIDITY_RUNTIME.contains("OxiLeanMath"));
343    }
344    #[test]
345    pub(super) fn test_visibility_display() {
346        assert_eq!(Visibility::Public.to_string(), "public");
347        assert_eq!(Visibility::External.to_string(), "external");
348        assert_eq!(Visibility::Private.to_string(), "private");
349        assert_eq!(Visibility::Internal.to_string(), "internal");
350    }
351    #[test]
352    pub(super) fn test_state_mutability_display() {
353        assert_eq!(StateMutability::View.to_string(), "view");
354        assert_eq!(StateMutability::Pure.to_string(), "pure");
355        assert_eq!(StateMutability::Payable.to_string(), "payable");
356        assert_eq!(StateMutability::NonPayable.to_string(), "");
357    }
358    #[test]
359    pub(super) fn test_emit_struct() {
360        let s = SolidityStruct {
361            name: "Position".into(),
362            fields: vec![
363                (SolidityType::Uint256, "x".into()),
364                (SolidityType::Uint256, "y".into()),
365            ],
366            doc: None,
367        };
368        let out = SolidityBackend::emit_struct(&s, 1);
369        assert!(out.contains("struct Position {"));
370        assert!(out.contains("uint256 x;"));
371        assert!(out.contains("uint256 y;"));
372    }
373    #[test]
374    pub(super) fn test_emit_enum() {
375        let e = SolidityEnum {
376            name: "State".into(),
377            variants: vec!["Created".into(), "Active".into(), "Closed".into()],
378            doc: None,
379        };
380        let out = SolidityBackend::emit_enum(&e, 1);
381        assert!(out.contains("enum State {"));
382        assert!(out.contains("Created"));
383        assert!(out.contains("Closed"));
384    }
385    #[test]
386    pub(super) fn test_emit_with_runtime() {
387        let mut backend = SolidityBackend::new().with_runtime();
388        backend.add_contract(SolidityContract::new("X", ContractKind::Contract));
389        let src = backend.emit_contract();
390        assert!(src.contains("OxiLeanMath"));
391        assert!(src.contains("saturatingAdd"));
392    }
393    #[test]
394    pub(super) fn test_emit_inheritance() {
395        let mut backend = SolidityBackend::new();
396        let mut contract = SolidityContract::new("MyToken", ContractKind::Contract);
397        contract.bases.push("ERC20".into());
398        contract.bases.push("Ownable".into());
399        backend.add_contract(contract);
400        let src = backend.emit_contract();
401        assert!(src.contains("contract MyToken is ERC20, Ownable {"));
402    }
403    #[test]
404    pub(super) fn test_address_payable_display() {
405        assert_eq!(SolidityType::AddressPayable.to_string(), "address payable");
406    }
407    #[test]
408    pub(super) fn test_payable_expr_display() {
409        let expr = SolidityExpr::Payable(Box::new(SolidityExpr::MsgSender));
410        assert_eq!(expr.to_string(), "payable(msg.sender)");
411    }
412}
413#[cfg(test)]
414mod Sol_infra_tests {
415    use super::*;
416    #[test]
417    pub(super) fn test_pass_config() {
418        let config = SolPassConfig::new("test_pass", SolPassPhase::Transformation);
419        assert!(config.enabled);
420        assert!(config.phase.is_modifying());
421        assert_eq!(config.phase.name(), "transformation");
422    }
423    #[test]
424    pub(super) fn test_pass_stats() {
425        let mut stats = SolPassStats::new();
426        stats.record_run(10, 100, 3);
427        stats.record_run(20, 200, 5);
428        assert_eq!(stats.total_runs, 2);
429        assert!((stats.average_changes_per_run() - 15.0).abs() < 0.01);
430        assert!((stats.success_rate() - 1.0).abs() < 0.01);
431        let s = stats.format_summary();
432        assert!(s.contains("Runs: 2/2"));
433    }
434    #[test]
435    pub(super) fn test_pass_registry() {
436        let mut reg = SolPassRegistry::new();
437        reg.register(SolPassConfig::new("pass_a", SolPassPhase::Analysis));
438        reg.register(SolPassConfig::new("pass_b", SolPassPhase::Transformation).disabled());
439        assert_eq!(reg.total_passes(), 2);
440        assert_eq!(reg.enabled_count(), 1);
441        reg.update_stats("pass_a", 5, 50, 2);
442        let stats = reg.get_stats("pass_a").expect("stats should exist");
443        assert_eq!(stats.total_changes, 5);
444    }
445    #[test]
446    pub(super) fn test_analysis_cache() {
447        let mut cache = SolAnalysisCache::new(10);
448        cache.insert("key1".to_string(), vec![1, 2, 3]);
449        assert!(cache.get("key1").is_some());
450        assert!(cache.get("key2").is_none());
451        assert!((cache.hit_rate() - 0.5).abs() < 0.01);
452        cache.invalidate("key1");
453        assert!(!cache.entries["key1"].valid);
454        assert_eq!(cache.size(), 1);
455    }
456    #[test]
457    pub(super) fn test_worklist() {
458        let mut wl = SolWorklist::new();
459        assert!(wl.push(1));
460        assert!(wl.push(2));
461        assert!(!wl.push(1));
462        assert_eq!(wl.len(), 2);
463        assert_eq!(wl.pop(), Some(1));
464        assert!(!wl.contains(1));
465        assert!(wl.contains(2));
466    }
467    #[test]
468    pub(super) fn test_dominator_tree() {
469        let mut dt = SolDominatorTree::new(5);
470        dt.set_idom(1, 0);
471        dt.set_idom(2, 0);
472        dt.set_idom(3, 1);
473        assert!(dt.dominates(0, 3));
474        assert!(dt.dominates(1, 3));
475        assert!(!dt.dominates(2, 3));
476        assert!(dt.dominates(3, 3));
477    }
478    #[test]
479    pub(super) fn test_liveness() {
480        let mut liveness = SolLivenessInfo::new(3);
481        liveness.add_def(0, 1);
482        liveness.add_use(1, 1);
483        assert!(liveness.defs[0].contains(&1));
484        assert!(liveness.uses[1].contains(&1));
485    }
486    #[test]
487    pub(super) fn test_constant_folding() {
488        assert_eq!(SolConstantFoldingHelper::fold_add_i64(3, 4), Some(7));
489        assert_eq!(SolConstantFoldingHelper::fold_div_i64(10, 0), None);
490        assert_eq!(SolConstantFoldingHelper::fold_div_i64(10, 2), Some(5));
491        assert_eq!(
492            SolConstantFoldingHelper::fold_bitand_i64(0b1100, 0b1010),
493            0b1000
494        );
495        assert_eq!(SolConstantFoldingHelper::fold_bitnot_i64(0), -1);
496    }
497    #[test]
498    pub(super) fn test_dep_graph() {
499        let mut g = SolDepGraph::new();
500        g.add_dep(1, 2);
501        g.add_dep(2, 3);
502        g.add_dep(1, 3);
503        assert_eq!(g.dependencies_of(2), vec![1]);
504        let topo = g.topological_sort();
505        assert_eq!(topo.len(), 3);
506        assert!(!g.has_cycle());
507        let pos: std::collections::HashMap<u32, usize> =
508            topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
509        assert!(pos[&1] < pos[&2]);
510        assert!(pos[&1] < pos[&3]);
511        assert!(pos[&2] < pos[&3]);
512    }
513}
514#[cfg(test)]
515mod solext_pass_tests {
516    use super::*;
517    #[test]
518    pub(super) fn test_solext_phase_order() {
519        assert_eq!(SolExtPassPhase::Early.order(), 0);
520        assert_eq!(SolExtPassPhase::Middle.order(), 1);
521        assert_eq!(SolExtPassPhase::Late.order(), 2);
522        assert_eq!(SolExtPassPhase::Finalize.order(), 3);
523        assert!(SolExtPassPhase::Early.is_early());
524        assert!(!SolExtPassPhase::Early.is_late());
525    }
526    #[test]
527    pub(super) fn test_solext_config_builder() {
528        let c = SolExtPassConfig::new("p")
529            .with_phase(SolExtPassPhase::Late)
530            .with_max_iter(50)
531            .with_debug(1);
532        assert_eq!(c.name, "p");
533        assert_eq!(c.max_iterations, 50);
534        assert!(c.is_debug_enabled());
535        assert!(c.enabled);
536        let c2 = c.disabled();
537        assert!(!c2.enabled);
538    }
539    #[test]
540    pub(super) fn test_solext_stats() {
541        let mut s = SolExtPassStats::new();
542        s.visit();
543        s.visit();
544        s.modify();
545        s.iterate();
546        assert_eq!(s.nodes_visited, 2);
547        assert_eq!(s.nodes_modified, 1);
548        assert!(s.changed);
549        assert_eq!(s.iterations, 1);
550        let e = s.efficiency();
551        assert!((e - 0.5).abs() < 1e-9);
552    }
553    #[test]
554    pub(super) fn test_solext_registry() {
555        let mut r = SolExtPassRegistry::new();
556        r.register(SolExtPassConfig::new("a").with_phase(SolExtPassPhase::Early));
557        r.register(SolExtPassConfig::new("b").disabled());
558        assert_eq!(r.len(), 2);
559        assert_eq!(r.enabled_passes().len(), 1);
560        assert_eq!(r.passes_in_phase(&SolExtPassPhase::Early).len(), 1);
561    }
562    #[test]
563    pub(super) fn test_solext_cache() {
564        let mut c = SolExtCache::new(4);
565        assert!(c.get(99).is_none());
566        c.put(99, vec![1, 2, 3]);
567        let v = c.get(99).expect("v should be present in map");
568        assert_eq!(v, &[1u8, 2, 3]);
569        assert!(c.hit_rate() > 0.0);
570        assert_eq!(c.live_count(), 1);
571    }
572    #[test]
573    pub(super) fn test_solext_worklist() {
574        let mut w = SolExtWorklist::new(10);
575        w.push(5);
576        w.push(3);
577        w.push(5);
578        assert_eq!(w.len(), 2);
579        assert!(w.contains(5));
580        let first = w.pop().expect("first should be available to pop");
581        assert!(!w.contains(first));
582    }
583    #[test]
584    pub(super) fn test_solext_dom_tree() {
585        let mut dt = SolExtDomTree::new(5);
586        dt.set_idom(1, 0);
587        dt.set_idom(2, 0);
588        dt.set_idom(3, 1);
589        dt.set_idom(4, 1);
590        assert!(dt.dominates(0, 3));
591        assert!(dt.dominates(1, 4));
592        assert!(!dt.dominates(2, 3));
593        assert_eq!(dt.depth_of(3), 2);
594    }
595    #[test]
596    pub(super) fn test_solext_liveness() {
597        let mut lv = SolExtLiveness::new(3);
598        lv.add_def(0, 1);
599        lv.add_use(1, 1);
600        assert!(lv.var_is_def_in_block(0, 1));
601        assert!(lv.var_is_used_in_block(1, 1));
602        assert!(!lv.var_is_def_in_block(1, 1));
603    }
604    #[test]
605    pub(super) fn test_solext_const_folder() {
606        let mut cf = SolExtConstFolder::new();
607        assert_eq!(cf.add_i64(3, 4), Some(7));
608        assert_eq!(cf.div_i64(10, 0), None);
609        assert_eq!(cf.mul_i64(6, 7), Some(42));
610        assert_eq!(cf.and_i64(0b1100, 0b1010), 0b1000);
611        assert_eq!(cf.fold_count(), 3);
612        assert_eq!(cf.failure_count(), 1);
613    }
614    #[test]
615    pub(super) fn test_solext_dep_graph() {
616        let mut g = SolExtDepGraph::new(4);
617        g.add_edge(0, 1);
618        g.add_edge(1, 2);
619        g.add_edge(2, 3);
620        assert!(!g.has_cycle());
621        assert_eq!(g.topo_sort(), Some(vec![0, 1, 2, 3]));
622        assert_eq!(g.reachable(0).len(), 4);
623        let sccs = g.scc();
624        assert_eq!(sccs.len(), 4);
625    }
626}