-
Notifications
You must be signed in to change notification settings - Fork 6.9k
twyne tvl refactor #17898
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
twyne tvl refactor #17898
Conversation
📝 WalkthroughWalkthroughAdds a new Ethereum TVL module for Twyne that discovers EVaults and Collateral Vaults from on‑chain events, resolves issuer/underlying assets via multicall, converts receipt-token amounts to underlying balances, and exposes Changes
Sequence DiagramsequenceDiagram
participant API as API (adapter)
participant Factory as Factory Contracts
participant Vault as Vault Contracts
participant MC as MultiCall
participant Agg as Aggregator
API->>Factory: fetch events (ProxyCreated, T_CollateralVaultCreated) from START_BLOCK
Factory-->>API: return vault addresses
API->>Vault: read receipt-token totals (totalAssets / totalAssetsDepositedOrReserved)
Vault-->>API: return receipt-token amounts
API->>MC: batch -> resolve issuer vaults & underlying assets
MC-->>API: issuer addresses, underlying token addresses
API->>MC: batch -> call convertToAssets on issuer vaults (permitFailure)
MC-->>API: underlying asset amounts
API->>Agg: aggregate balances (sum assets, subtract borrows)
Agg-->>API: final balances object
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Important Action Needed: IP Allowlist UpdateIf your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:
Reviews will stop working after February 8, 2026 if the new IP is not added to your allowlist. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
The adapter at projects/twyne exports TVL: |
|
The adapter at projects/twyne exports TVL: |
|
The adapter at projects/twyne exports TVL: |
|
The adapter at projects/twyne exports TVL: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@projects/twyne/index.js`:
- Around line 83-84: adjustedAmounts can be negative (BigInt(eVaultAmounts) -
BigInt(totalBorrows)) which will break convertToAssets; before calling
addUnderlyingBalances, coalesce missing eVaultAmounts/totalBorrows to zero,
compute each adjusted amount as max(BigInt(eVaultAmounts[i]) -
BigInt(totalBorrows[i] || 0), 0n) and pass that non-negative BigInt array to
addUnderlyingBalances (referencing adjustedAmounts, eVaultAmounts, totalBorrows,
convertToAssets, addUnderlyingBalances, twyneVaults).
- Around line 34-59: The current logic assumes every vault is ERC-4626 nested
(calls asset() on issuerVaults then convertToAssets()), which breaks for
CollateralVaults and mis-handles EVaults; update addUnderlyingBalances to branch
by vault type: for EVaults (Credit Vaults) call asset() once on the vault listed
in vaults/issuerVaults and then call convertToAssets(amount) on that same EVault
(use convertCalls built from issuerVaults and amounts), and for CollateralVaults
do NOT call asset()/convertToAssets but instead fetch the vault's collateral
holdings directly (e.g., query the vault's collateral/balance/totalAssets
method) and map those amounts to underlyingBalances; adjust the construction of
validIndices/convertCalls and the multiCall that creates underlyingAmounts so
EVault conversion and CollateralVault direct-balance queries are performed
separately using issuerVaults, underlyingAssets, convertToAssets, and vaults as
reference points.
| // Get issuer vaults (which issued the receipt tokens) | ||
| const issuerVaults = await api.multiCall({ | ||
| calls: vaults, | ||
| abi: 'address:asset', | ||
| }); | ||
|
|
||
| // Get underlying asset addresses from issuer vaults | ||
| const underlyingAssets = await api.multiCall({ | ||
| calls: issuerVaults, | ||
| abi: 'address:asset', | ||
| }); | ||
|
|
||
| const validIndices = []; | ||
| const convertCalls = []; | ||
| vaults.forEach((_, i) => { | ||
| if (issuerVaults[i] && amounts[i]) { | ||
| validIndices.push(i); | ||
| convertCalls.push({ target: issuerVaults[i], params: [amounts[i]] }); | ||
| } | ||
| }); | ||
|
|
||
| // Convert receipt token amounts to underlying amounts | ||
| const underlyingAmounts = await api.multiCall({ | ||
| calls: convertCalls, | ||
| abi: 'function convertToAssets(uint256) view returns (uint256)', | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for Twyne vault ABIs/interfaces and ERC4626 usage
rg -n "EVault|CollateralVault|convertToAssets|totalAssetsDepositedOrReserved|T_CollateralVaultCreated|ProxyCreated|asset\(" -g '*.{js,ts,json,md}' --max-count=100Repository: DefiLlama/DefiLlama-Adapters
Length of output: 10764
🏁 Script executed:
cat -n projects/twyne/index.jsRepository: DefiLlama/DefiLlama-Adapters
Length of output: 4437
🏁 Script executed:
# Search for Twyne documentation, ABI files, or contract details
find . -type f -name "*.md" -o -name "*.json" -o -name "*.ts" | xargs rg -l "Twyne|twyne" 2>/dev/null | head -20Repository: DefiLlama/DefiLlama-Adapters
Length of output: 54
🏁 Script executed:
# Also search for any references to EVault or collateral vault interfaces
rg -i "evault.*asset|collateral.*vault" -g '*.{md,txt,json}' -A 2 -B 2Repository: DefiLlama/DefiLlama-Adapters
Length of output: 3082
🏁 Script executed:
# Look for any Twyne ABI files or contract directories
find . -path "*/twyne/*" -type fRepository: DefiLlama/DefiLlama-Adapters
Length of output: 97
🏁 Script executed:
# Check if there's a README or documentation for Twyne
find . -name "README*" -o -name "*.md" | xargs grep -l "Twyne\|twyne" 2>/dev/nullRepository: DefiLlama/DefiLlama-Adapters
Length of output: 56
🌐 Web query:
Twyne protocol vault structure EVault CollateralVault ERC4626
💡 Result:
Twyne vault structure (Euler EVK EVault, Twyne CollateralVault, ERC‑4626)
-
Credit Vault (lender-facing) = Euler EVK “EVault” (ERC‑4626)
- Twyne’s Credit Vault is “unmodified Euler EVault” code and behaves like a standard ERC‑4626 tokenized vault for Credit‑LP deposits/withdrawals. (twyne.gitbook.io)
- Functionally, it’s a passive lending pool: it extends credit (lends) to Twyne collateral vaults and charges them interest via an interest-rate model. (twyne.gitbook.io)
-
Collateral Vault (borrower-facing) = Twyne-specific, per-owner vault (NOT ERC‑4626)
- In Twyne v1, each Collateral Vault has a single owner, and many functions are owner-only. (twyne.gitbook.io)
- It is not ERC20 / not ERC4626 (shares would need to be non-transferable due to single-owner design). (twyne.gitbook.io)
- It holds the borrower’s collateral (often Euler yield-bearing/wrapped collateral assets) and can deposit either the wrapped collateral asset directly or deposit underlying and convert/register it as collateral. (twyne.gitbook.io)
- Each collateral vault can borrow only one target asset (the debt asset) and includes flows for liquidation and handling external liquidation on the underlying lending market. (twyne.gitbook.io)
-
How this ties back to Euler EVK
- Euler EVK “credit vaults” are ERC‑4626 vaults with added borrowing/lending accounting, where borrowing is allowed against collateral deposited in other vaults (as permitted by the liability vault). (docs.euler.finance)
- Twyne composes this by using an EVK EVault as the pooled Credit Vault, while borrower positions live in Twyne’s CollateralVault wrapper/manager contract that interfaces with the underlying market and Twyne’s internal risk/liquidation logic. (twyne.gitbook.io)
Citations:
- 1: https://twyne.gitbook.io/twyne/for-developers/contracts/credit-vault
- 2: https://twyne.gitbook.io/twyne/for-developers/contracts/credit-vault
- 3: https://twyne.gitbook.io/twyne/for-developers/contracts/collateral-vault
- 4: https://twyne.gitbook.io/twyne/for-developers/contracts/collateral-vault
- 5: https://twyne.gitbook.io/twyne/for-developers/contracts/collateral-vault
- 6: https://twyne.gitbook.io/twyne/for-developers/contracts/collateral-vault
- 7: https://docs.euler.finance/developers/evk/
- 8: https://twyne.gitbook.io/twyne/for-developers/contracts/credit-vault
Branch addUnderlyingBalances to handle EVault and CollateralVault separately.
CollateralVaults are not ERC-4626 compliant and do not support the nested asset() + convertToAssets() pattern. EVaults (Credit Vaults) implement ERC-4626 but may not be nested—their asset() likely returns the credit token directly, not another issuer vault. The current implementation will fail or zero balances on CollateralVault processing (line 90) and potentially mishandle EVault conversion (line 84).
Create separate handlers:
- For EVaults: Call
asset()once and useconvertToAssets()to convert total credits to underlying - For CollateralVaults: Query the vault's collateral holdings directly without assuming
asset()or nested ERC-4626 structure
🤖 Prompt for AI Agents
In `@projects/twyne/index.js` around lines 34 - 59, The current logic assumes
every vault is ERC-4626 nested (calls asset() on issuerVaults then
convertToAssets()), which breaks for CollateralVaults and mis-handles EVaults;
update addUnderlyingBalances to branch by vault type: for EVaults (Credit
Vaults) call asset() once on the vault listed in vaults/issuerVaults and then
call convertToAssets(amount) on that same EVault (use convertCalls built from
issuerVaults and amounts), and for CollateralVaults do NOT call
asset()/convertToAssets but instead fetch the vault's collateral holdings
directly (e.g., query the vault's collateral/balance/totalAssets method) and map
those amounts to underlyingBalances; adjust the construction of
validIndices/convertCalls and the multiCall that creates underlyingAmounts so
EVault conversion and CollateralVault direct-balance queries are performed
separately using issuerVaults, underlyingAssets, convertToAssets, and vaults as
reference points.
| let adjustedAmounts = eVaultAmounts.map((amount, i) => BigInt(amount) - BigInt(totalBorrows[i])); | ||
| await addUnderlyingBalances(api, twyneVaults, adjustedAmounts); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against negative adjustedAmounts before convertToAssets.
BigInt(totalAssets) - BigInt(totalBorrows) can go negative if borrows exceed assets (or if totalAssets excludes borrows), which will break convertToAssets(uint256) and revert. Clamp at zero (and coalesce missing values) to keep TVL stable.
🛠️ Suggested fix
- let adjustedAmounts = eVaultAmounts.map((amount, i) => BigInt(amount) - BigInt(totalBorrows[i]));
+ let adjustedAmounts = eVaultAmounts.map((amount, i) => {
+ const assets = BigInt(amount ?? 0);
+ const borrows = BigInt(totalBorrows[i] ?? 0);
+ return assets > borrows ? assets - borrows : 0n;
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let adjustedAmounts = eVaultAmounts.map((amount, i) => BigInt(amount) - BigInt(totalBorrows[i])); | |
| await addUnderlyingBalances(api, twyneVaults, adjustedAmounts); | |
| let adjustedAmounts = eVaultAmounts.map((amount, i) => { | |
| const assets = BigInt(amount ?? 0); | |
| const borrows = BigInt(totalBorrows[i] ?? 0); | |
| return assets > borrows ? assets - borrows : 0n; | |
| }); | |
| await addUnderlyingBalances(api, twyneVaults, adjustedAmounts); |
🤖 Prompt for AI Agents
In `@projects/twyne/index.js` around lines 83 - 84, adjustedAmounts can be
negative (BigInt(eVaultAmounts) - BigInt(totalBorrows)) which will break
convertToAssets; before calling addUnderlyingBalances, coalesce missing
eVaultAmounts/totalBorrows to zero, compute each adjusted amount as
max(BigInt(eVaultAmounts[i]) - BigInt(totalBorrows[i] || 0), 0n) and pass that
non-negative BigInt array to addUnderlyingBalances (referencing adjustedAmounts,
eVaultAmounts, totalBorrows, convertToAssets, addUnderlyingBalances,
twyneVaults).
May resolve #17849
addUnderlyingBalances()helper function to reduce duplicated codeborrowedexport using the evault'stotalBorrows()view functionAlternatively, using
maxRelease()on collateral vaults was returning an identical value forborrowed:Summary by CodeRabbit
New Features
Documentation