Skip to content

Commit ea23eae

Browse files
author
Wetshakat
committed
feat: add comprehensive unit tests for critical contract modules
- Add tests for emergency module (pause/resume, circuit breaker): 13 tests - Add tests for liquidity module (pools, fees, AMM): 13 tests - Add tests for audit module (records, compliance, queries): 10 tests - Add tests for BFT consensus module (validators, proposals, voting): 14 tests - Add tests for atomic swap module (validation, error paths): 8 tests - Rewrite bridge tests from commented stubs to working tests: 15 tests - Expose missing contract functions for testability (circuit breaker, audit queries, fee structure, liquidity checks) - Export CircuitBreaker and BridgeFeeStructure types - Update CI to run tests, clippy, and coverage reporting with tarpaulin Total: 112 -> 185 passing tests (+73 new), 0 modules with zero coverage
1 parent 7e5efdd commit ea23eae

File tree

193 files changed

+58110
-309
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

193 files changed

+58110
-309
lines changed

.github/workflows/ci.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,47 @@ jobs:
2525

2626
- name: Build WASM (release)
2727
run: cargo build --target wasm32-unknown-unknown --release
28+
29+
- name: Run tests
30+
run: cargo test --workspace
31+
32+
- name: Clippy
33+
run: cargo clippy --all-targets --all-features -- -D warnings
34+
35+
coverage:
36+
runs-on: ubuntu-latest
37+
needs: rust
38+
steps:
39+
- uses: actions/checkout@v4
40+
41+
- name: Install Rust toolchain
42+
uses: dtolnay/rust-toolchain@stable
43+
44+
- name: Cache cargo
45+
uses: Swatinem/rust-cache@v2
46+
47+
- name: Install cargo-tarpaulin
48+
run: cargo install cargo-tarpaulin
49+
50+
- name: Generate coverage report
51+
run: |
52+
cargo tarpaulin \
53+
--workspace \
54+
--out xml \
55+
--out html \
56+
--output-dir coverage \
57+
--skip-clean \
58+
--exclude-files "*/tests/*" "*/test_*" \
59+
--timeout 300
60+
61+
- name: Upload coverage to Codecov
62+
uses: codecov/codecov-action@v4
63+
with:
64+
files: coverage/cobertura.xml
65+
fail_ci_if_error: false
66+
67+
- name: Upload coverage artifacts
68+
uses: actions/upload-artifact@v4
69+
with:
70+
name: coverage-report
71+
path: coverage/

contracts/teachlink/src/lib.rs

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,10 @@ pub use assessment::{
140140
pub use errors::{BridgeError, EscrowError, MobilePlatformError, RewardsError};
141141
pub use types::{
142142
AlertConditionType, AlertRule, ArbitratorProfile, AtomicSwap, AuditRecord, BackupManifest,
143-
BackupSchedule, BridgeMetrics, BridgeProposal, BridgeTransaction, CachedBridgeSummary,
144-
ChainConfig, ChainMetrics, ComplianceReport, ConsensusState, ContentMetadata, ContentToken,
145-
ContentTokenParameters, ContentType, CrossChainMessage, CrossChainPacket, DashboardAnalytics,
143+
BackupSchedule, BridgeFeeStructure, BridgeMetrics, BridgeProposal, BridgeTransaction,
144+
CachedBridgeSummary, ChainConfig, ChainMetrics, CircuitBreaker, ComplianceReport,
145+
ConsensusState, ContentMetadata, ContentToken, ContentTokenParameters, ContentType,
146+
Contribution, ContributionType, CrossChainMessage, CrossChainPacket, DashboardAnalytics,
146147
DisputeOutcome, EmergencyState, Escrow, EscrowMetrics, EscrowParameters, EscrowRole,
147148
EscrowSigner, EscrowStatus, LiquidityPool, MultiChainAsset, NotificationChannel,
148149
NotificationContent, NotificationPreference, NotificationSchedule, NotificationTemplate,
@@ -509,6 +510,16 @@ impl TeachLinkBridge {
509510
liquidity::LiquidityManager::get_available_liquidity(&env, chain_id)
510511
}
511512

513+
/// Get fee structure
514+
pub fn get_fee_structure(env: Env) -> BridgeFeeStructure {
515+
liquidity::LiquidityManager::get_fee_structure(&env)
516+
}
517+
518+
/// Check if pool has sufficient liquidity
519+
pub fn has_sufficient_liquidity(env: Env, chain_id: u32, amount: i128) -> bool {
520+
liquidity::LiquidityManager::has_sufficient_liquidity(&env, chain_id, amount)
521+
}
522+
512523
// ========== Message Passing Functions ==========
513524

514525
/// Send a cross-chain packet
@@ -642,6 +653,51 @@ impl TeachLinkBridge {
642653
emergency::EmergencyManager::get_emergency_state(&env)
643654
}
644655

656+
/// Check circuit breaker for a transaction
657+
pub fn check_circuit_breaker(
658+
env: Env,
659+
chain_id: u32,
660+
amount: i128,
661+
) -> Result<(), BridgeError> {
662+
emergency::EmergencyManager::check_circuit_breaker(&env, chain_id, amount)
663+
}
664+
665+
/// Reset circuit breaker for a chain
666+
pub fn reset_circuit_breaker(
667+
env: Env,
668+
chain_id: u32,
669+
resetter: Address,
670+
) -> Result<(), BridgeError> {
671+
emergency::EmergencyManager::reset_circuit_breaker(&env, chain_id, resetter)
672+
}
673+
674+
/// Get circuit breaker state
675+
pub fn get_circuit_breaker(env: Env, chain_id: u32) -> Option<CircuitBreaker> {
676+
emergency::EmergencyManager::get_circuit_breaker(&env, chain_id)
677+
}
678+
679+
/// Get all paused chains
680+
pub fn get_paused_chains(env: Env) -> Vec<u32> {
681+
emergency::EmergencyManager::get_paused_chains(&env)
682+
}
683+
684+
/// Update circuit breaker limits
685+
pub fn update_circuit_breaker_limits(
686+
env: Env,
687+
chain_id: u32,
688+
max_daily_volume: i128,
689+
max_transaction_amount: i128,
690+
updater: Address,
691+
) -> Result<(), BridgeError> {
692+
emergency::EmergencyManager::update_circuit_breaker_limits(
693+
&env,
694+
chain_id,
695+
max_daily_volume,
696+
max_transaction_amount,
697+
updater,
698+
)
699+
}
700+
645701
// ========== Audit and Compliance Functions ==========
646702

647703
/// Create an audit record
@@ -674,6 +730,66 @@ impl TeachLinkBridge {
674730
audit::AuditManager::get_compliance_report(&env, report_id)
675731
}
676732

733+
/// Get audit record count
734+
pub fn get_audit_count(env: Env) -> u64 {
735+
audit::AuditManager::get_audit_count(&env)
736+
}
737+
738+
/// Get audit records by operation type
739+
pub fn get_audit_records_by_type(
740+
env: Env,
741+
operation_type: types::OperationType,
742+
) -> Vec<AuditRecord> {
743+
audit::AuditManager::get_audit_records_by_type(&env, operation_type)
744+
}
745+
746+
/// Get audit records by operator
747+
pub fn get_audit_records_by_operator(env: Env, operator: Address) -> Vec<AuditRecord> {
748+
audit::AuditManager::get_audit_records_by_operator(&env, operator)
749+
}
750+
751+
/// Get audit records by time range
752+
pub fn get_audit_records_by_time(
753+
env: Env,
754+
start_time: u64,
755+
end_time: u64,
756+
) -> Vec<AuditRecord> {
757+
audit::AuditManager::get_audit_records_by_time(&env, start_time, end_time)
758+
}
759+
760+
/// Get recent audit records
761+
pub fn get_recent_audit_records(env: Env, count: u32) -> Vec<AuditRecord> {
762+
audit::AuditManager::get_recent_audit_records(&env, count)
763+
}
764+
765+
/// Clear old audit records
766+
pub fn clear_old_records(env: Env, before_timestamp: u64, admin: Address) -> Result<u32, BridgeError> {
767+
audit::AuditManager::clear_old_records(&env, before_timestamp, admin)
768+
}
769+
770+
/// Log bridge operation
771+
pub fn log_bridge_operation(
772+
env: Env,
773+
is_outgoing: bool,
774+
operator: Address,
775+
amount: i128,
776+
chain_id: u32,
777+
tx_hash: Bytes,
778+
) -> Result<u64, BridgeError> {
779+
audit::AuditManager::log_bridge_operation(&env, is_outgoing, operator, amount, chain_id, tx_hash)
780+
}
781+
782+
/// Log emergency operation
783+
pub fn log_emergency_operation(
784+
env: Env,
785+
is_pause: bool,
786+
operator: Address,
787+
reason: Bytes,
788+
tx_hash: Bytes,
789+
) -> Result<u64, BridgeError> {
790+
audit::AuditManager::log_emergency_operation(&env, is_pause, operator, reason, tx_hash)
791+
}
792+
677793
// ========== Atomic Swap Functions ==========
678794

679795
/// Initiate an atomic swap
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
{
2+
"generators": {
3+
"address": 4,
4+
"nonce": 0,
5+
"mux_id": 0
6+
},
7+
"auth": [
8+
[],
9+
[]
10+
],
11+
"ledger": {
12+
"protocol_version": 25,
13+
"sequence_number": 0,
14+
"timestamp": 0,
15+
"network_id": "0000000000000000000000000000000000000000000000000000000000000000",
16+
"base_reserve": 0,
17+
"min_persistent_entry_ttl": 4096,
18+
"min_temp_entry_ttl": 16,
19+
"max_entry_ttl": 6312000,
20+
"ledger_entries": [
21+
{
22+
"entry": {
23+
"last_modified_ledger_seq": 0,
24+
"data": {
25+
"contract_data": {
26+
"ext": "v0",
27+
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
28+
"key": {
29+
"symbol": "nonce"
30+
},
31+
"durability": "persistent",
32+
"val": {
33+
"map": [
34+
{
35+
"key": {
36+
"u64": "7"
37+
},
38+
"val": {
39+
"bool": true
40+
}
41+
}
42+
]
43+
}
44+
}
45+
},
46+
"ext": "v0"
47+
},
48+
"live_until": 4095
49+
},
50+
{
51+
"entry": {
52+
"last_modified_ledger_seq": 0,
53+
"data": {
54+
"contract_data": {
55+
"ext": "v0",
56+
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
57+
"key": "ledger_key_contract_instance",
58+
"durability": "persistent",
59+
"val": {
60+
"contract_instance": {
61+
"executable": {
62+
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
63+
},
64+
"storage": [
65+
{
66+
"key": {
67+
"symbol": "min_valid"
68+
},
69+
"val": {
70+
"u32": 1
71+
}
72+
},
73+
{
74+
"key": {
75+
"symbol": "token"
76+
},
77+
"val": {
78+
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
79+
}
80+
},
81+
{
82+
"key": {
83+
"symbol": "validtor"
84+
},
85+
"val": {
86+
"map": [
87+
{
88+
"key": {
89+
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
90+
},
91+
"val": {
92+
"bool": true
93+
}
94+
}
95+
]
96+
}
97+
}
98+
]
99+
}
100+
}
101+
}
102+
},
103+
"ext": "v0"
104+
},
105+
"live_until": 4095
106+
},
107+
{
108+
"entry": {
109+
"last_modified_ledger_seq": 0,
110+
"data": {
111+
"contract_code": {
112+
"ext": "v0",
113+
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
114+
"code": ""
115+
}
116+
},
117+
"ext": "v0"
118+
},
119+
"live_until": 4095
120+
}
121+
]
122+
},
123+
"events": []
124+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"generators": {
3+
"address": 1,
4+
"nonce": 0,
5+
"mux_id": 0
6+
},
7+
"auth": [
8+
[],
9+
[]
10+
],
11+
"ledger": {
12+
"protocol_version": 25,
13+
"sequence_number": 0,
14+
"timestamp": 0,
15+
"network_id": "0000000000000000000000000000000000000000000000000000000000000000",
16+
"base_reserve": 0,
17+
"min_persistent_entry_ttl": 4096,
18+
"min_temp_entry_ttl": 16,
19+
"max_entry_ttl": 6312000,
20+
"ledger_entries": [
21+
{
22+
"entry": {
23+
"last_modified_ledger_seq": 0,
24+
"data": {
25+
"contract_data": {
26+
"ext": "v0",
27+
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
28+
"key": "ledger_key_contract_instance",
29+
"durability": "persistent",
30+
"val": {
31+
"contract_instance": {
32+
"executable": {
33+
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
34+
},
35+
"storage": null
36+
}
37+
}
38+
}
39+
},
40+
"ext": "v0"
41+
},
42+
"live_until": 4095
43+
},
44+
{
45+
"entry": {
46+
"last_modified_ledger_seq": 0,
47+
"data": {
48+
"contract_code": {
49+
"ext": "v0",
50+
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
51+
"code": ""
52+
}
53+
},
54+
"ext": "v0"
55+
},
56+
"live_until": 4095
57+
}
58+
]
59+
},
60+
"events": []
61+
}

0 commit comments

Comments
 (0)