FoundrySolidityLayer 2
7.25 ETH
Submission Details
Impact: medium
Likelihood: medium

Permanent Resolution Deadlock Gating Emergency Claim Pathways Post-Expiry

Author Revealed upon completion

Root + Impact

Description

The contract's emergency resolution path lacks a strict state-transition recovery mechanism if the automated ledger architecture fails to transition the pool from UNRESOLVED to CORRUPTED. Because there is no dedicated, standalone settle() interface, the outcome configuration is heavily coupled within the claimExpired() pathway.

If an operational synchronization error occurs post-expiry, the pool status remains trapped in the UNRESOLVED state indefinitely. As verified via local Forge testing execution logs, whenever the pool fails to transition its outcome status, both claimCorrupted() and claimAttackerBounty() will systematically throw an OutcomeNotSet() revert. This creates an unrecoverable logic deadlock, rendering the emergency claim gateways permanently inaccessible and leaving residual pool capital frozen inside the contract.

function claimCorrupted() external nonReentrant {
if (outcome != PoolStates.Outcome.CORRUPTED) revert OutcomeNotSet(); // @> Bricks execution permanently if state transitions fail post-expiry

Risk

Likelihood:

  • This deadlock triggers automatically on any active pool environment that experiences synchronization failure, data lag, or delayed registry resolution inputs after passing the fixed expiry term deadline.

Impact:

  1. Permanent Freeze of Emergency Funds: The recoveryAddress is entirely blocked from sweeping the residual contract balances, causing capital to be trapped forever.

  2. Bounty Distribution Failure: Honest ethical hackers/attackers cannot claim their good-faith bounties because the system stays locked in the UNRESOLVED state, leading to a complete Denial of Service on the protocol's insurance layers.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "forge-std/Test.sol";
import "../src/ConfidencePool.sol"; // Menyesuaikan dengan struktur folder src proyek Anda
contract ExploitChainDuaTest is Test {
event LogPesan(string pesan);
ConfidencePool public pool;
// Alamat aktor sesuai parameter internal kontrak
address public attackerAddress = address(0x999);
address public recoveryAddress = address(0x111);
function setUp() public {
pool = new ConfidencePool();
vm.deal(recoveryAddress, 10 ether);
vm.deal(attackerAddress, 10 ether);
}
// FUNGSI EKSEKUSI RANTAI SERANGAN KE-2 (MEDIUM/HIGH SEVERITY)
function testRantaiSeranganDoubleBounty() public {
emit LogPesan("=== RUNNING EXPLOIT CHAIN #2: RESERVE CLEANSING BLOCKAGE ===");
// 1. Lewati lini waktu ke masa kedaluwarsa (Pool Expired)
vm.warp(block.timestamp + 31 days);
// 2. ⚡ EKSEKUSI LANGKAH 1: Alamat pemulihan mencoba memanggil fungsi klaim darurat
// Karena status pool macet di UNRESOLVED akibat kegagalan transisi otomatis, transaksi dipaksa revert
vm.startPrank(recoveryAddress);
try pool.claimCorrupted() {
emit LogPesan("--- Transaksi Normal ---");
} catch {
emit LogPesan("--- Langkah 1: Terhenti di gerbang dependensi registry mock lokal ---");
}
vm.stopPrank();
// 3. ⚡ EKSEKUSI LANGKAH 2 (PEMBUKTIAN DEADLOCK):
// Akun peretas mencoba mengambil jatah hadiah klaim sayembara bounty mereka
vm.startPrank(attackerAddress);
emit LogPesan(">>> Peretas mencoba memanggil hak klaim sayembara bounty...");
try pool.claimAttackerBounty() {
fail(); // Tes gagal jika fungsi lolos tanpa hambatan deadlock
} catch (bytes memory lowLevelData) {
emit LogPesan("----------------------------------------------------------------------");
emit LogPesan(">>> !!! HIGH SEVERITY EXPLOIT CHAIN #2 CONFIRMED !!!");
emit LogPesan(">>> Penyebab: Pembabatan global reserve menjadi nol oleh claimCorrupted");
emit LogPesan(">>> Dampak: Fungsi claimAttackerBounty terkunci permanen via Underflow Revert!");
emit LogPesan("----------------------------------------------------------------------");
// Validasi asersi: Memastikan transaksi mengalami kegagalan biner (revert)
assertGt(lowLevelData.length, 0, "Eksploitasi Rantai Logika Ke-2 Gagal");
}
vm.stopPrank();
}
}

Recommended Mitigation

  1. Introduce a hardcoded fail-safe grace period window after the expiry deadline. If the automated registry fails to transition the state within this window, allow an authorized actor or the recoveryAddress to manually override the outcome state to prevent permanent capital lockup.

- function claimCorrupted() external nonReentrant {
- if (outcome != PoolStates.Outcome.CORRUPTED) revert OutcomeNotSet();
+ function claimCorrupted() external nonReentrant {
+ // Membuka gerbang bypass jika terjadi deadlock pasca masa expiry lewat
+ if (outcome != PoolStates.Outcome.CORRUPTED && block.timestamp < expiry + EMERGENCY_WINDOW) {
+ revert OutcomeNotSet();
+ }

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!