Reports

Smart Contract Security Assessment

Twyne Incremental Review

Twyne is a risk-modular credit delegation protocol built for the Ethereum Virtual Machine. It addresses a fundamental inefficiency in current DeFi lending markets: the unused borrowing power of users who deposit assets but do not borrow against them. By enabling these depositors (Credit LPs) to earn additional yield by making their unused borrowing power available to borrowers, Twyne unlocks higher capital efficiency while maintaining the security constraints of the underlying lending markets.

7
Issues
1
C/H/M
Period
Aug 20, 2025 - Aug 21, 2025
Auditors
HHK, adriro

Review Summary

Protocol Overview

Twyne is a risk-modular credit delegation protocol built for the Ethereum Virtual Machine. It addresses a fundamental inefficiency in current DeFi lending markets: the unused borrowing power of users who deposit assets but do not borrow against them. By enabling these depositors (Credit LPs) to earn additional yield by making their unused borrowing power available to borrowers, Twyne unlocks higher capital efficiency while maintaining the security constraints of the underlying lending markets.

Protocol
Twyne
Timeline
Aug 20, 2025 - Aug 21, 2025
Audit Team
HHK, adriro

Audit Overview

Scope and Resources

Scope

This audit covers the incremental review for Twyne V1 in [PR 35](https://github.com/0xTwyne/twyne-contracts-v1/pull/35) to the smart contracts in scope across 1.5 days of review.

Overall Assessment

Evaluation Matrix

access control

mathematics

complexity

libraries

decentralization

code stability

documentation

monitoring

testing

Key Findings

Findings Summary

0
Critical
1
High
0
Medium
1
Low
4
Informational
1
Gas
H-1 Finding

H-1: Teleport should verify subaccount is tied to current borrower

High

Summary:

A missing validation could be used to pull funds from dangling authorization.

Description:

The updated functionality of teleport() can be used to migrate a position from a subaccount of the borrower.

237:     function teleport(uint toDeposit, uint toBorrow, address subAccount) external onlyBorrowerAndNotExtLiquidated whenNotPaused nonReentrant {
238:         createVaultSnapshot();
239: 
240:         totalAssetsDepositedOrReserved += toDeposit;
241:         _handleExcessCredit(_invariantCollateralAmount());
242: 
243:         if (toBorrow == type(uint).max) {
244:             toBorrow = IEVault(targetVault).debtOf(subAccount);
245:         }
246: 
247:         IEVC.BatchItem[] memory items = new IEVC.BatchItem[](3);
248:         items[0] = IEVC.BatchItem({
249:             targetContract: asset(),
250:             onBehalfOfAccount: address(this),
251:             value: 0,
252:             data: abi.encodeCall(IERC20.transferFrom, (subAccount, address(this), toDeposit)) // needs allowance
253:         });

The problem is the absence of a validation that ties the subaccount to the current borrower, leading to a transferFrom() action from an arbitrary account.

Usually, there should not be any active allowance from third-party accounts, as collateral vaults are per borrower. However, since vaults can be liquidated, the borrower may shift to the new liquidator, enabling the new borrower to access the old borrower's funds.

Impact:

High. New vault owners can siphon funds from a previous borrower.

Requirements:

  • Collateral vault gets liquidated
  • Excess approval exists
  • Previous vault owner contains a non-zero amount of the collateral token
  • Funds can ONLY be pulled from the previous vault owner

Recommendation:

Validate subaccount is an actual subaccount of the current borrower.

Developer Response:

Fixed in commit 5945b31. To clarify, the proper implementation already existed in the MockCollateralVault contract.

L-1 Finding

L-1: Missing SafeERC20 operations

Low

Summary:

The LeverageOperator contract executes ERC20 operations over arbitrary tokens without the SafeERC20 wrapper, potentially causing incompatibility issues.

Description:

Impact:

Low.

Recommendation:

Use safeTransfer() and forceApprove().

Developer Response:

fixed in PR#36.

I-1 Finding

I-1: Empty contracts in deployment script

Informational

Summary:

The contracts referenced in the productionSetup() function are empty accounts in Ethereum.

Description:

TwyneDeployEulerIntegration.s.sol#L280-L284.

280:         } else if (block.chainid == 1) {
281:             oracleRouterFactory = 0x72735e5dd42EDc979c600766532eA704842CfB7b;
282:             evc = EthereumVaultConnector(payable(0xC36aED7b7816aA21B660a33a637a8f9B9B70ad6c));
283:             factory = GenericFactory(0xd5e966dB359f1cB2A01280fCCBEB839Ac572CE35);
284:             protocolConfig = ProtocolConfig(0x3b68711EF6c1988c96CBD32d929b76cB09b579Ea);

Impact:

Informational.

Recommendation:

Ensure to deploy these contracts properly before executing the script.

Developer Response:

Acknowledged. These addresses are just placeholders. The deployment process involves executing an evk-periphery script which provides these addresses.

I-2 Finding

I-2: Unused variable in LeverageOperator

Informational

Summary:

The initialCollateralBalance variable is written but never read.

Description:

Impact:

Informational.

Recommendation:

Remove the variable.

Developer Response:

fixed in PR#36.

I-3 Finding

I-3: Ineffective locking mechanism in LeverageOperator

Informational

Summary:

The mechanism provided by flashloanLock does not enforce any lock.

Description:

The flashloanLock is toggled before executing the flashloan in executeLeverage(). The implementation doesn't provide any actual locking benefits because:

  • This doesn't work as a reentrancy guard: the function can be re-entered while the flag is on.
  • onMorphoFlashLoan() cannot be executed by anything other than the LeverageOperator itself, cause the caller must be Morpho, and Morpho only calls the flashloan initiator.

Impact:

Informational. Gas savings.

Recommendation:

Change the semantics of this lock to be a normal reentrancy guard in the executeLeverage() function or remove the flashloanLock variable.

Developer Response:

flashloanLock variable removed in PR#36. Added nonreentrant modifier for extra safety, even if it is not necessarily required.

I-4 Finding

I-4: Unchecked approval parameter inside `EulerWrapper`

Informational

Description:

The function depositUnderlyingToIntermediateVault() takes an intermediateVault parameter and approves euler asset on it.

However this intermediateVault is not enforced to be a legit vault deployed by the factory. This allows any user to approve a malicious contract.

While the contract is not supposed to hold funds, it is advised to check the intermediateVault against the Vault manager.

Impact:

Informational.

Recommendation:

Ensure the intermediateVault is a legit vault against the Vault Manager and/or implement the Gas finding recommended which will remove the approval().

Developer Response:

Fixed in PR#36.

G-1 Finding

G-1: Gas improvements in EulerWrapper

Gas

Description:

The EulerWrapper contract enables a zapper functionality to deposit in Twyne.

Both functions have the callThroughEVC modifier, which will re-route the call through the EVC. This modifier shouldn't be needed as the implementation doesn't strictly require it. Additionally, note that each call to EVK vaults would need to be re-wrapped in the EVC that corresponds to each vault (which might be Euler's or Twyne's).

Additionally, the pull token → approve → deposit cycle can leverage the skim functionality and instead do:

  1. transfer tokens from the caller to collateral
  2. call skim() on the collateral with the intermediate vault as the recipient
  3. call skim() on the intermediate vault with the user as the recipient

Impact:

Gas savings.

Recommendation:

Consider applying the suggested modifications.

Developer Response:

fixed in PR#36.

Final Remarks

TODO

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