Skip to content

feat(protocols): #2402 add Euler v2 vaults (ERC-4626 reads and deposit/withdraw) - #2420

Open
walkonwayvs wants to merge 2 commits into
KeeperHub:stagingfrom
walkonwayvs:euler-v2-protocol
Open

feat(protocols): #2402 add Euler v2 vaults (ERC-4626 reads and deposit/withdraw)#2420
walkonwayvs wants to merge 2 commits into
KeeperHub:stagingfrom
walkonwayvs:euler-v2-protocol

Conversation

@walkonwayvs

Copy link
Copy Markdown

Issue

Closes #2402. Accepted with amendments; this implements the amended scope.

What this changes

Adds euler-v2 as a protocol: 18 standard ERC-4626 actions plus 11 Euler-specific reads (cash, totalBorrows, interestRate, interestAccumulator, accumulatedFees, debtOf, oracle, unitOfAccount, EVC, creator, decimals), across Ethereum and Base.

As triage noted, the capability already exists: an Euler EVault is reachable today through the morpho vault entry, which is userSpecifiedAddress with an empty address map. What is missing is identity and discoverability - no Euler name, icon, docs page or typed actions, and a position recorded under integration type morpho and labelled "MetaMorpho Vault". This closes that gap the same way the other 26 entries close it, and adds the Euler-specific reads that a generic ERC-4626 entry cannot express.

erc4626AbiOverrides({ decimals: 6 }) is passed explicitly. The helper defaults to 18, which is wrong for the USDC markets this is most used for. Vault decimals follow the underlying asset, so get-vault-decimals is exposed for non-USDC markets.

Scope

  • protocols/euler-v2.ts
  • docs/plugins/euler-v2.md and the docs/plugins/_meta.ts nav key
  • public/protocols/euler-v2.png
  • tests/unit/protocol-euler-v2.test.ts
  • tests/unit/__goldens__/protocol-calldata/euler-v2.json
  • protocols/index.ts and lib/types/integration.ts, regenerated

No coverage runner, per triage: neither chain has a funded persistent test wallet, and an unregistered suite would fail scripts/protocol-coverage-completeness.ts.

How it was verified

pnpm type-check clean, pnpm fix clean, 19 shape-lock tests and 538 calldata tests passing. Rebased on staging at caa60d103.

Test vault is 0x797DD80692c3b2dAdabCe8e30C07fDE5307D48a9 (eUSDC-2, mainnet), verified by eth_call on 2026-09-11: asset USDC, decimals 6, totalAssets ~2.036M, maxDeposit ~72.96M of headroom. Headroom is the reason this vault and not the largest by TVL: eUSDC-64 holds ~14.4M and is at its supply cap, so maxDeposit returns 0 and the deposit leg reverts.

The write path is verified by reading the vault kit source, not by execution. callThroughEVC (Dispatch.sol) routes a non-EVC caller through the connector and re-enters, so a direct wallet call works. Per triage, deposit and mint pass CHECKACCOUNT_NONE but withdraw and redeem check the share owner; the docs page states that asymmetry rather than implying all four behave alike.

Screenshots

Not applicable.

@github-actions

Copy link
Copy Markdown
Contributor

About the build check on this pull request

This pull request comes from a fork, so GitHub does not pass it the credentials build normally uses for our image registry cache and staging build configuration. The build still runs and still compiles the image, so a red build here is real; it just takes longer than on team branches.

Every workflow run on a pull request from a fork also waits for a maintainer to approve it, so checks can sit at "awaiting approval" for a while after each push. Nothing is needed from you for either of these.

@suisuss suisuss left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What this changes

protocols/euler-v2.ts defines Euler V2 as one userSpecifiedAddress vault contract with a 29-function inline EVK ABI, registry fallback addresses for chain 1 and 8453, erc4626AbiOverrides({ decimals: 6 }) plus eleven hand-written overrides for the Euler-specific reads, and a chain-1 ProtocolTestData block. Plus the docs page and its _meta.ts key, a real 460x460 logo, the golden, a 218-line shape-lock test, and both regenerated barrels.

Everything on the artefact checklist is present, which is the first time that has happened on one of these. I verified both declared vaults on chain: mainnet 0x797DD80692c3b2dAdabCe8e30C07fDE5307D48a9 is EVK Vault eUSDC-2, decimals() 6, asset USDC, 2,083,665.61 assets, interestRate() decoding to about 8.95% APR at 1e27 per-second scaling, which confirms the units in your description; Base 0x07954BEB7e137101A7cbb3e47864C684aEC50524 is EVK Vault eUSDC-100, decimals 6, Base USDC. No coverage runner, correctly, so there is no shard registration to do.

The write asymmetry is documented accurately in the Technical Notes, including that a self-withdrawal can revert while the account has an open borrow. That is the thing I most expected to be got wrong.

Does it match the description

Matches.

Blocking

  • protocols/euler-v2.ts inline ABI, the four write entries - the first parameter is named amount, while erc4626AbiOverrides keys its input overrides by assets and shares (lib/web3/standards/erc4626.ts:375-407) and deriveAction looks overrides up by the ABI parameter's own name (lib/abi/protocol-derive.ts:230). protocols/yearn-v3.ts works only because its inline ABI names those params assets and shares. -> Two things follow, and the second is the one that bites. The derived input is { name: "amount", label: "Amount" }, so the deposit form renders "Amount (uint256)" rather than "Asset Amount (wei)", and docs/plugins/euler-v2.md documents a config key named assets that does not exist - an MCP or REST caller following the docs and sending {"assets": "10000000"} has it silently defaulted. And buildActionWorkflow maps bindings by input name (lib/test-data/build-workflow.ts:372) with no validation of unknown keys, so assets: amount("USDC", "10") at :97 binds nothing and amount falls through to the uint default of "1". The golden records that: vault-deposit is 0x6e553f65 followed by ...0001 and the receiver - one wei, where yearn's equivalent golden carries the real amount. I checked previewDeposit(1) on the declared mainnet vault and it returns 0 shares, so EVK would revert E_ZeroShares; the requiredTokens: 1000 USDC and the fabricated approvals at :71-78 are funding a transaction that never spends them. -> Rename the four ABI params to assets / shares. Calldata is positional so the wire format does not change, then regenerate the golden.

Mechanical - actionable as-is

  • docs/plugins/euler-v2.md:10 - "Each vault is a separate contract -- you must provide the vault address" uses a double dash. Single dash.

  • protocols/euler-v2.ts:497-504 describes unitOfAccount as "the address of the asset the vault denominates risk calculations in", but both declared vaults return 0x0000000000000000000000000000000000000348, which is decimal 840 - the ISO-4217 code for USD, an EVK convention for USD-denominated markets, not a token. A workflow piping that into a token-balance step gets a non-contract address and a confusing downstream failure. A help tip rather than a code change.

  • :123-126 - writeExpectations covers vault-deposit and vault-mint only. The two actions whose EVC semantics are non-obvious, withdraw and redeem, have no post-condition at all.

  • :158-166 - the source comment says only that deposit passes CHECKACCOUNT_NONE, omitting mint and the withdraw/redeem side that the docs page covers properly. Worth bringing into line, since the comment is what the next person editing the file reads.

  • decimals: 6 is right for both declared vaults, but this is a userSpecifiedAddress protocol - a user can point it at eWETH (18) or eWBTC (8). Your comment concedes this and offers get-vault-decimals as the escape hatch, which is the honest framing. Worth knowing: I could not find any consumer of ProtocolActionOutput.decimals - buildOutputFieldsFromAction and outputToAbiParameter both drop it - so today it appears to be documentation-grade metadata repo-wide. Mark that UNSURE; I grepped lib/, plugins/ and components/ and found nothing, but a dynamic lookup could exist.

With the team

Nothing.

Verdict

Changes requested on the ABI parameter names - the four write bindings are silently dropped, so the golden froze a one-wei deposit and the docs name an input key that does not exist.

Worth saying that the shape-lock test is the most thorough one any of the protocol PRs has shipped, and it still could not catch this: it asserts counts, casing, address shape and registry round-trip, but never an input name or encoded calldata. One assertion that vault-deposit's first input is named assets would have.

@suisuss suisuss added the changes-requested Triage: reviewed, changes needed from the contributor label Sep 12, 2026
@walkonwayvs

Copy link
Copy Markdown
Author

Fixed, and thank you for catching it. The four write parameters are now named assets and shares.

You are right about where it came from: Euler's own EVault source names that parameter amount, and I copied the signature across without checking what erc4626AbiOverrides keys on. The failure is silent in every direction, which is what made it survive. The form renders a generic Amount field, the docs name a key that does not exist, and buildActionWorkflow drops the binding without complaint, so the golden froze a one-wei deposit that previewDeposit(1) would have reverted on. The funding and the fabricated approvals were paying for a transaction that never spent them.

Golden regenerated. vault-deposit now encodes 0x6e553f65 with 989680, which is the real 10 USDC.

On your closing point, that the shape-lock test could not catch this: agreed, and I have added the assertion. write actions name their amount input assets or shares pins the first ABI input for all four writes, with a comment explaining why the name is load-bearing rather than cosmetic. 20 tests now.

Also in this push:

  • writeExpectations extended to vault-withdraw and vault-redeem, which had no post-condition at all.
  • The source comment above the inline ABI now states the full asymmetry, matching the docs page. It said only that deposit passes CHECKACCOUNT_NONE, omitting mint and the withdraw/redeem side.
  • unitOfAccount description rewritten. You are right that a USD-denominated EVK market returns 0x348, which is 840, the ISO-4217 code rather than a token. The description now says so, since a workflow piping that into a token-balance step would fail confusingly.
  • The double dash on docs line 10 is now a single dash.

On decimals: 6 and ProtocolActionOutput.decimals having no consumer: noted, and I have not changed anything. get-vault-decimals stays as the escape hatch for eWETH or eWBTC markets.

@walkonwayvs
walkonwayvs requested a review from suisuss September 12, 2026 03:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changes-requested Triage: reviewed, changes needed from the contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(protocols): add Euler v2 vaults (ERC-4626 reads and deposit/withdraw)

2 participants