Reports

Smart Contract Security Assessment

Twyne 2026-Q1 update

Twyne is a credit delegation protocol that lets borrowers rent unused borrowing power from other lenders to boost their Liquidation LTV. Lenders earn additional yield while borrowers get to ramp up their leverage or insulate their debt.

6
Issues
0
C/H/M
Period
Feb 06, 2026 - Feb 09, 2026
Auditors
Adriro, HHK

Review Summary

Protocol Overview

Twyne is a credit delegation protocol that lets borrowers rent unused borrowing power from other lenders to boost their Liquidation LTV. Lenders earn additional yield while borrowers get to ramp up their leverage or insulate their debt.

Protocol
Twyne
Timeline
Feb 06, 2026 - Feb 09, 2026
Audit Team
Adriro, HHK

Audit Overview

Scope and Resources

Scope

This audit covers 7 pull requests across 2.5 days of review.

Overall Assessment

The audit of Twyne's 2025 Q1 update identified no medium severity or above vulnerabilities, indicating a solid update that maintains the security standards of the protocol's foundation. The updates result in three major changes: a refactor of the IRM, usage of the intermediate vault address over collateral inside the buffer mapping, and a rework of the fixed Twyne LTV into a dynamic one.

Evaluation Matrix

access control

mathematics

complexity

libraries

decentralization

code stability

documentation

monitoring

testing

Key Findings

Findings Summary

0
Critical
0
High
0
Medium
1
Low
5
Informational
0
Gas
L-1 Finding

L-1: Reducing `maxTwyneLTV` can retroactively liquidate existing positions

Low

Summary:

When maxTwyneLTVs is lowered by governance or when the vault implementation is updated, positions that were healthy under the previous parameter may become immediately liquidatable.

Description:

In _collateralScaledByLiqLTV1e8(), the effective liquidation LTV is capped at Math.min(twyneLiqLTV, twyneVaultManager.maxTwyneLTVs(address(intermediateVault))). This value is read from twyneVaultManager at query time, meaning any reduction to maxTwyneLTVs takes effect immediately for all existing positions.

A borrower who opened a position at an LTV just below the previous maxTwyneLTV would find their position suddenly above the new threshold, making them liquidatable. The same applies if the vault implementation is upgraded and the new logic changes how twyneLiqLTV interacts with maxTwyneLTVs.

Impact:

Low. This requires a governance action to reduce maxTwyneLTVs, which is a privileged operation. However, the absence of a buffer or time-delay mechanism means the effect on existing borrowers is immediate and potentially severe.

Recommendation:

Consider implementing a time-delayed parameter change or a grace period during which existing positions are evaluated against the old maxTwyneLTV while new positions use the updated value. Additionally, assess the effects of upgrading the implementation over existing collateral vaults.

Developer Response:

Fixed in PR#239.

yAudit: Before deploying the ramp-down feature, verify no existing vault has twyneLiqLTV > maxTwyneLTVs on-chain, as those vaults would have already had their effective LTV reduced instantly by a prior maxTwyneLTVs change and the ramp mechanism cannot retroactively protect them.

I-1 Finding

I-1: IRMTwyneCurve constructor reverts with misleading error

Informational

Summary:

The IRMTwyneCurve constructor reuses the E_IRMUpdateUnauthorized() error from the Euler Vault Kit's IIRM interface to signal invalid constructor parameters, which is semantically incorrect and can mislead developers and monitoring tools during debugging.

Description:

The constructor validates that polynomialParameter_ and nonlinearPoint_ are non-zero and that nonlinearPoint_ is below 100%. When any of these checks fail, it reverts with E_IRMUpdateUnauthorized():

if (
    polynomialParameter_ == 0
    || nonlinearPoint_ == 0
    || nonlinearPoint_ >= 1e18
) revert E_IRMUpdateUnauthorized();

This error is defined in the IIRM interface and is intended for authorization failures, specifically when msg.sender is not the expected vault (as seen in computeInterestRate()). Using it for parameter validation conflates two distinct failure modes: unauthorized access and invalid input. Off-chain tooling or integrators catching E_IRMUpdateUnauthorized would incorrectly interpret a deployment misconfiguration as an authorization issue.

Impact:

Informational.

Recommendation:

Define a dedicated error (e.g., error E_InvalidIRMParameters()) for the constructor validation and revert with that instead.

Developer Response:

Acknowledged.

I-2 Finding

I-2: Oracle quotes scaled collateral in EulerCollateralVault

Informational

Summary:

The _collateralScaledByLiqLTV1e8() return value is passed directly into EulerRouter.getQuote() as the input amount. Since this value is already scaled by the LLTV (in 1e8 precision), the oracle must price it linearly — any non-linear pricing behavior would produce incorrect collateral valuations.

Description:

In EulerCollateralVault._canLiquidate(), the collateral value used for liquidation checks is computed by calling getQuote() with the output of _collateralScaledByLiqLTV1e8(false, adjExtLiqLTV). This value equals the user's collateral multiplied by a liquidation LTV factor at 1e8 precision. The oracle receives this scaled quantity and is expected to convert it to the unit of account.

If the oracle's getQuote() implementation is not linear in the input amount (i.e., getQuote(k * x) != k * getQuote(x)), the resulting collateral valuation will be incorrect.

Impact:

Informational.

Recommendation:

Document the linearity requirement for Oracles used with getQuote() in this context. Consider adding a comment or NatSpec annotation at the oracle configuration point to alert of this behavior.

Developer Response:

Fixed in PR#242.

I-3 Finding

I-3: Stale NatSpec on `_invariantCollateralAmount()` does not reflect dynamic liquidation LTV

Informational

Summary:

The @dev comment on _invariantCollateralAmount() describes the formula as ceil(userCollateral * twyneLiqLTV * MAXFACTOR / (externalLiqLTV * buffer)), but the actual computation now uses a dynamic liquidation LTV model introduced with the Dynamic Liquidation LTV update.

Description:

The NatSpec comment on _invariantCollateralAmount() in CollateralVaultBase references an older, static formula. The current implementation uses a dynamic LTV derived from the credit reservation invariant (C · λ̃^dyn_t = β_safe · λ̃_e · (C_LP + C)), as implemented in _collateralScaledByLiqLTV1e8().

Impact:

Informational.

Recommendation:

Update the @dev comment on _invariantCollateralAmount() to reflect the dynamic liquidation LTV formula currently in use.

Developer Response:

Fixed in PR#242.

I-4 Finding

I-4: `_handleExcessCredit` duplicates identical logic across Euler and Aave vaults

Informational

Description:

The credit handling logic in EulerCollateralVault.sol#L87 and AaveV3CollateralVault.sol#L140 is identical. The only difference is the Aave override mixing in rebalanceATokens_CV, which is aToken wrapper bookkeeping — not credit handling.

Impact:

Informational. Duplicated logic increases divergence risk — PR #243's bugfix had to be applied to both files independently.

Recommendation:

Move the shared logic into CollateralVaultBase. Aave can overrides with a simple super._handleExcessCredit(...) + rebalanceATokens_CV.

Developer Response:

Acknowledged.

I-5 Finding

I-5: No way to deregister an intermediate vault

Informational

Description:

VaultManager.setIntermediateVault() sets isIntermediateVault[vault] = true but there is no function to set it back to false. Once registered, an intermediate vault cannot be removed from the approved set.

Impact:

Informational. If an intermediate vault needs to be deprecated, it will remain permanently valid in isIntermediateVault.

Recommendation:

Add a bool value parameter to the function and set the mapping with it.

Developer Response:

Fixed in PR#252.

Final Remarks

Twyne's 2025 Q1 update introduces changes to the IRM, LTV mapping, and liquidation mechanism, no medium or above severity findings were found outlying solid development practices from the Twyne team. The dynamic liquidation LTV is a welcome change that will result in a better user experience. Some concerns were raised about the LTV potentially being manipulated using the cash balance on the intermediate vault, but this did not result in any meaningful threat and is a necessary trade-off for the feature. All issues identified during the review have been acknowledged or fixed. The final commit containing these changes on the private Twyne repository can be found at [f15c596c527d9865d95c2fe90f06d0367435fba7](https://github.com/0xTwyne/twyne-contracts/commit/f15c596c527d9865d95c2fe90f06d0367435fba7).

Methodology

Severity Classification

Critical

Immediate threat to user funds or protocol integrity

Direct loss of funds, protocol compromise

High

Significant security risk requiring urgent attention

Potential fund loss, major functionality disruption

Medium

Important issue that should be addressed

Limited fund risk, functionality concerns

Low

Minor issue with minimal impact

Best practice violations, minor inefficiencies

Gas

Findings that improve gas efficiency

Increased transaction costs

Informational

Code quality and best practice recommendations

Reduced maintainability and readability