Skip to content

fix(sdk): resolve AccountLens ABI through ABIService - #82

Closed
LeonardEulerXYZ wants to merge 3 commits into
euler-xyz:mainfrom
LeonardEulerXYZ:feat/account-lens-abi-service
Closed

fix(sdk): resolve AccountLens ABI through ABIService#82
LeonardEulerXYZ wants to merge 3 commits into
euler-xyz:mainfrom
LeonardEulerXYZ:feat/account-lens-abi-service

Conversation

@LeonardEulerXYZ

@LeonardEulerXYZ LeonardEulerXYZ commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • resolve the AccountLens ABI through the existing ABIService for onchain account reads, reward-stream reads, and execution simulation
  • pass the same resolved ABI through simulation batch encoding and result decoding
  • coalesce concurrent ABI requests and evict failed requests so a later call can retry
  • retain the bundled ABI as a backwards-compatible fallback for services constructed directly without an ABIService

Why

The SDK currently combines two different update models:

  • deployment addresses are loaded from the mutable euler-interfaces deployment document
  • the AccountLens ABI is compiled into each published SDK package

If euler-interfaces replaces the deployed AccountLens address with a contract whose return tuple changed, an existing SDK can call the new address and decode it with the old bundled ABI. In particular, adding leading fields to VaultAccountInfo keeps the function selector unchanged but changes the return-data layout.

This change makes newly published SDK versions resolve the runtime ABI from the same interface repository used for deployment metadata, instead of leaving AccountLens reads tied exclusively to the bundled copy.

Type compatibility

ABIService returns viem's broad runtime Abi type, so viem cannot preserve literal-ABI inference at these call sites. The runtime ABI is therefore contained inside the existing adapters and simulation implementation, while the SDK's stable domain result types (EVCAccountInfo, VaultAccountInfo, account entities, and reward-stream entities) remain unchanged.

Existing helper and constructor call sites remain source-compatible:

  • ABI parameters added to exported batch helpers and query wrappers are optional
  • AccountOnchainAdapter receives ABIService as a new optional trailing constructor argument
  • directly constructed account, rewards, and execution services retain the bundled ABI fallback

Rollout boundary

This PR protects consumers of a newly published SDK version. It cannot repair already-published SDK bundles that continue to fetch mutable deployment addresses while carrying an older ABI.

The corresponding euler-interfaces rollout must therefore preserve/version the legacy AccountLens deployment for old clients rather than silently replacing an ABI-incompatible address. Also, the ABI and deployment documents are still separate master reads; a follow-up versioned manifest or immutable commit reference is required for strict atomic address/ABI coupling.

Consumers using a custom deploymentsUrl, deployment-service override, or explicit reward-stream accountLensAddress must also provide a matching custom abiService; otherwise the custom address will still be paired with the ABI fetched from euler-interfaces/master.

Validation

  • pnpm --filter @eulerxyz/euler-v2-sdk test — 479 tests passed
  • pnpm --filter @eulerxyz/euler-v2-sdk typecheck
  • pnpm --filter @eulerxyz/euler-v2-sdk build
  • pnpm --filter @eulerxyz/euler-v2-sdk lint — passed; existing warnings remain outside this diff
  • targeted Biome lint on all changed source files
  • git diff --check

Related

Summary by CodeRabbit

  • New Features

    • Account and reward data now support runtime-resolved contract ABIs.
    • Added automatic fallback to the bundled ABI when runtime retrieval fails or is incomplete.
    • Improved handling of whole-vault query failures, preventing unavailable positions and rewards from appearing.
    • ABI requests are cached, consolidated during concurrent requests, and retried after failures.
  • Bug Fixes

    • Improved validation and error reporting for unsuccessful or malformed ABI responses.
    • Simulations continue using the fallback ABI when runtime ABI retrieval fails.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The SDK now resolves AccountLens ABIs at runtime, validates and caches ABI requests, falls back to bundled ABIs when needed, and propagates the resolved ABI through account reads, simulations, rewards, and SDK service wiring.

Changes

AccountLens ABI integration

Layer / File(s) Summary
ABI fetching and resolution
packages/euler-v2-sdk/src/services/abiService/abiService.ts, packages/euler-v2-sdk/src/services/accountService/.../resolveAccountLensAbi.ts
ABI requests are cached by URL, validated as successful array payloads, retried after failures, and resolved with bundled-ABI fallback when required functions are unavailable.
Runtime ABI account reads
packages/euler-v2-sdk/src/services/accountService/.../accountOnchainAdapter.ts, .../accountLensTypes.ts, packages/euler-v2-sdk/test/accountLensAbiService.test.ts
AccountLens ABIs flow through encoded calls, contract reads, batch simulations, and query-key handling; whole-vault query failures are reported as unavailable positions.
Simulation ABI propagation
packages/euler-v2-sdk/src/services/executionService/*, packages/euler-v2-sdk/test/simulate.test.ts
Execution simulation resolves and uses the runtime ABI for lens batches and snapshot decoding, skipping failed vault queries.
Rewards and SDK service wiring
packages/euler-v2-sdk/src/services/rewardsService/rewardsService.ts, packages/euler-v2-sdk/src/sdk/buildSDK.ts
RewardsService resolves AccountLens ABIs, omits failed reward queries, and receives the shared ABIService through SDK construction and wiring.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SDK
  participant ABIService
  participant AccountOnchainAdapter
  participant ExecutionService
  participant RewardsService
  participant Provider

  SDK->>AccountOnchainAdapter: inject ABIService
  SDK->>ExecutionService: setABIService
  SDK->>RewardsService: setABIService
  AccountOnchainAdapter->>ABIService: resolve AccountLens ABI
  ABIService-->>AccountOnchainAdapter: runtime or bundled ABI
  AccountOnchainAdapter->>Provider: readContract with resolved ABI
  ExecutionService->>ABIService: resolve AccountLens ABI
  ABIService-->>ExecutionService: runtime or bundled ABI
  ExecutionService->>Provider: simulate lens batch and decode snapshot
  RewardsService->>ABIService: resolve AccountLens ABI
  ABIService-->>RewardsService: runtime or bundled ABI
  RewardsService->>Provider: readContract with resolved ABI
Loading

Possibly related PRs

Suggested reviewers: dglowinski

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: resolving the AccountLens ABI through ABIService in the SDK.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Reject unsuccessful or malformed ABI responses while keeping failed fetches retryable.

Ignore whole-vault AccountLens query failures in account, rewards, and simulation results.

@LeonardEulerXYZ LeonardEulerXYZ left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Code review

Verdict: blocking address/ABI rollout issue remains.

The implementation consistently threads one resolved ABI through account reads, reward-stream reads, and simulation encoding/decoding. The cache coalescing and failed-request eviction are also sound in the directly tested path.

The unresolved problem is composition with euler-interfaces#225: the deployment address and ABI are still obtained through separate mutable master reads, while the bundled fallback remains the legacy ABI. This leaves both new and already-published clients exposed to incompatible pairings.

A live mainnet fixture against vault 0x0120c2748545a4D9C875CDdFB439f786d6f1B460 confirmed the full mismatch matrix:

  • legacy address + legacy ABI: correct decode;
  • replacement address + replacement ABI: correct decode;
  • legacy address + replacement ABI: decode throws;
  • replacement address + legacy ABI: silently decodes corrupt fields (vault became 0x000000000000000000000000000000006A68c197 and assetsAccount became an absurd shifted value).

This can occur for a new SDK instance constructed before the interfaces update but first used afterward, and it is guaranteed for old SDK bundles once PR #225 replaces the mutable accountLens address. The PR body correctly mentions the boundary, but the linked rollout does not yet implement the required preservation/versioning.

Before rollout, bind the address and ABI atomically/versionedly (for example, preserve the legacy accountLens field and add a new versioned field consumed only by compatible SDKs, or use an immutable manifest/commit containing both). The optional bundled fallback should only be used with a deployment known to match it; otherwise require an explicitly matching ABI service and fail closed. Add a regression that decodes real old/new tuple payloads and exercises both mismatched pairings—the current tests assert ABI plumbing by object identity but do not discriminate this failure.

Validation on head 529effe7:

  • focused ABI/simulation tests: 27 passed;
  • full SDK suite: 482 passed;
  • typecheck: passed;
  • build: passed;
  • lint: passed with five warnings in unchanged files;
  • git diff --check: passed;
  • live mainnet old/new AccountLens composition probe: reproduced the incompatibility above;
  • whole-diff executable/supply-chain sweep: no unrelated suspicious changes found.

This PR is authored by the authenticated LeonardEulerXYZ account, so this is a comment review rather than an approval/request-changes event.

const evc = deployment.addresses.coreAddrs.evc;
const resolvedAccountLensAbi =
(await this.abiService?.fetchABI(chainId, "AccountLens")) ??
accountLensAbi;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Blocking rollout issue: this fallback is not safe once euler-interfaces#225 replaces lensAddrs.accountLens. A live call to the deployed replacement mainnet lens decoded with the bundled legacy ABI without throwing, but shifted every field (vault became 0x000…6A68c197, while assetsAccount became a huge value). The inverse pairing—legacy address with the new ABI—throws. Because the address and ABI are fetched independently from mutable master, even a newly published SDK can observe either mismatch; already-published SDKs will necessarily use this legacy ABI with the new address. Please version/bind the address+ABI pair and only allow this fallback for a deployment known to match it, rather than treating the fallback as generally backward-compatible.

Resolving the AccountLens ABI through ABIService made every account read,
reward-stream read, and simulation depend on fetching the ABI document: a
404, rate limit, or blocked host rejected before any RPC call, even though
a usable bundled ABI ships with the package.

Add resolveAccountLensAbi, which degrades to the bundled ABI when the
document is unreachable or is missing the functions the SDK calls, and
report the fallback where a channel exists (a FALLBACK_USED data issue on
account reads, a warning log for rewards and simulation).

Also:
- keep the resolved ABI out of AccountLens query cache keys, so a ~33KB
  array is not serialized into every key (and into consumer query keys
  derived from the same helpers)
- key the ABI request cache by resolved URL rather than contract name, so
  it tracks whatever getABIURL keys on
- extract the duplicated SOURCE_UNAVAILABLE data issue in
  collectSettledVaultAccountInfos and document the two originalValue shapes
- document why a whole-vault queryFailure is dropped silently in
  decodeAccountSnapshot: the EVC batch item succeeded, so it cannot surface
  via failedBatchItems

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dglowinski
dglowinski marked this pull request as ready for review July 29, 2026 12:53
@dglowinski

Copy link
Copy Markdown
Collaborator

Superseded by #85, which carries the same commit history on the first-party fix/account-lens-runtime-sources branch and includes the latest runtime ABI, deployment-source, and reward-query fixes.

@dglowinski dglowinski closed this Jul 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/euler-v2-sdk/src/services/executionService/simulate.ts (1)

467-473: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fallback warning fires on every simulation.

simulateTransactionPlan runs per quote/refresh, and a failed ABI fetch stays failed for the failure-cache window, so this can emit a warning on every call. Consider deduping (warn once per chain+reason) to keep the console usable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/euler-v2-sdk/src/services/executionService/simulate.ts` around lines
467 - 473, Deduplicate the fallback warning in simulateTransactionPlan so the
same chainId and fallbackReason are logged only once during the failure-cache
window. Update the fallbackReason handling around resolveAccountLensAbi, using
persistent module- or service-scoped state, while preserving the existing
fallback behavior and warning content.
packages/euler-v2-sdk/src/services/accountService/adapters/accountOnchainAdapter/resolveAccountLensAbi.ts (1)

23-27: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

missingFunctions only checks names, not signatures.

A runtime ABI whose getVaultAccountInfo inputs/outputs changed still passes validation, so the mismatch surfaces only as a decode failure (or a silently shifted result) at read time. Consider also asserting the expected input arity/types for the two functions so a reshaped lens is detected here, where the fallback reason can be reported.

♻️ Sketch: validate inputs too
-const REQUIRED_FUNCTIONS = [
-	"getEVCAccountInfo",
-	"getVaultAccountInfo",
-] as const;
+const REQUIRED_FUNCTIONS = [
+	{ name: "getEVCAccountInfo", inputs: ["address", "address"] },
+	{ name: "getVaultAccountInfo", inputs: ["address", "address"] },
+] as const;
 
 const missingFunctions = (abi: Abi): string[] =>
 	REQUIRED_FUNCTIONS.filter(
-		(name) =>
-			!abi.some((item) => item.type === "function" && item.name === name),
-	);
+		(required) =>
+			!abi.some(
+				(item) =>
+					item.type === "function" &&
+					item.name === required.name &&
+					item.inputs.length === required.inputs.length &&
+					item.inputs.every((input, i) => input.type === required.inputs[i]),
+			),
+	).map((required) => required.name);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/euler-v2-sdk/src/services/accountService/adapters/accountOnchainAdapter/resolveAccountLensAbi.ts`
around lines 23 - 27, Update missingFunctions to validate the expected
signatures of the required lens functions, not just their names: assert the
input arity and types for getVaultAccountInfo and the other required function,
while preserving the existing missing-name behavior. Treat any signature
mismatch as missing so the adapter falls back before read-time decoding.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/euler-v2-sdk/src/services/accountService/adapters/accountOnchainAdapter/accountOnchainAdapter.ts`:
- Around line 122-133: Update getQueryKeyEVCAccountInfo and the corresponding
key construction around the additionally affected range to include a small
discriminator indicating whether the bundled fallback ABI was used. Keep the ABI
array itself out of the key, but ensure fallback-decoded and runtime-ABI-decoded
results produce distinct cache keys while preserving the existing provider,
address, and sub-account components.

In `@packages/euler-v2-sdk/src/services/executionService/simulate.ts`:
- Around line 863-873: Log in-band query failures before dropping the result. In
packages/euler-v2-sdk/src/services/executionService/simulate.ts:863-873, update
the vaultAccount queryFailure branch to warn with meta.subAccount, meta.vault,
and queryFailureReason before continue; in
packages/euler-v2-sdk/src/services/rewardsService/rewardsService.ts:616-627,
update the queryFailure branch to warn with the vault and queryFailureReason
before returning [].

---

Nitpick comments:
In
`@packages/euler-v2-sdk/src/services/accountService/adapters/accountOnchainAdapter/resolveAccountLensAbi.ts`:
- Around line 23-27: Update missingFunctions to validate the expected signatures
of the required lens functions, not just their names: assert the input arity and
types for getVaultAccountInfo and the other required function, while preserving
the existing missing-name behavior. Treat any signature mismatch as missing so
the adapter falls back before read-time decoding.

In `@packages/euler-v2-sdk/src/services/executionService/simulate.ts`:
- Around line 467-473: Deduplicate the fallback warning in
simulateTransactionPlan so the same chainId and fallbackReason are logged only
once during the failure-cache window. Update the fallbackReason handling around
resolveAccountLensAbi, using persistent module- or service-scoped state, while
preserving the existing fallback behavior and warning content.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: euler-xyz/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3baf5823-60d3-4066-9e12-b2df8ea417c2

📥 Commits

Reviewing files that changed from the base of the PR and between 93daf32 and 6d98d05.

📒 Files selected for processing (10)
  • packages/euler-v2-sdk/src/sdk/buildSDK.ts
  • packages/euler-v2-sdk/src/services/abiService/abiService.ts
  • packages/euler-v2-sdk/src/services/accountService/adapters/accountOnchainAdapter/accountLensTypes.ts
  • packages/euler-v2-sdk/src/services/accountService/adapters/accountOnchainAdapter/accountOnchainAdapter.ts
  • packages/euler-v2-sdk/src/services/accountService/adapters/accountOnchainAdapter/resolveAccountLensAbi.ts
  • packages/euler-v2-sdk/src/services/executionService/executionService.ts
  • packages/euler-v2-sdk/src/services/executionService/simulate.ts
  • packages/euler-v2-sdk/src/services/rewardsService/rewardsService.ts
  • packages/euler-v2-sdk/test/accountLensAbiService.test.ts
  • packages/euler-v2-sdk/test/simulate.test.ts

Comment on lines +122 to +133
// The resolved ABI is deliberately not part of the key: it is fetched once and
// memoized for the lifetime of the ABIService, so it is constant for every read
// keyed here, and serializing a ~33KB array into every key is pure overhead.
getQueryKeyEVCAccountInfo(
provider: ReturnType<ProviderService["getProvider"]>,
accountLensAddress: Address,
evc: Address,
subAccount: Address,
_abi?: Abi,
): string | null {
return serializeQueryArgs([provider, accountLensAddress, evc, subAccount]);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Cache key assumes the resolved ABI never changes within one process.

The comment holds only while resolution outcome is stable. ABIService.fetchABI now evicts failed requests so a later call retries, so an early fetchSubAccount can read with the bundled fallback while a later one reads with the runtime ABI — both keyed identically, so the fallback-decoded result is served from cache for the remaining TTL. A cheap fix is to fold a small discriminator (e.g. whether the fallback was used) into the key rather than the whole array.

🔧 Suggested key discriminator
 	getQueryKeyVaultAccountInfo(
 		provider: ReturnType<ProviderService["getProvider"]>,
 		accountLensAddress: Address,
 		subAccount: Address,
 		vault: Address,
-		_abi?: Abi,
+		abi?: Abi,
 	): string | null {
 		return serializeQueryArgs([
 			provider,
 			accountLensAddress,
 			subAccount,
 			vault,
+			abi === bundledAccountLensAbi ? "bundled" : "runtime",
 		]);
 	}

Also applies to: 154-168

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/euler-v2-sdk/src/services/accountService/adapters/accountOnchainAdapter/accountOnchainAdapter.ts`
around lines 122 - 133, Update getQueryKeyEVCAccountInfo and the corresponding
key construction around the additionally affected range to include a small
discriminator indicating whether the bundled fallback ABI was used. Keep the ABI
array itself out of the key, but ensure fallback-decoded and runtime-ABI-decoded
results produce distinct cache keys while preserving the existing provider,
address, and sub-account components.

Comment on lines 863 to +873
if (meta.kind === "vaultAccount") {
const decodedVaultInfo = decodeFunctionResult({
abi: accountLensAbi,
abi: resolvedAccountLensAbi,
functionName: "getVaultAccountInfo",
data: resultItem.result,
}) as unknown as VaultAccountInfo;
// The lens reports a whole-vault query failure in-band, so the EVC batch
// item itself succeeded and this will not appear in `failedBatchItems`.
// Drop the position, matching how a failed batch item is skipped above:
// a partial snapshot degrades to "position absent" rather than throwing.
if (decodedVaultInfo.queryFailure) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

In-band AccountLens queryFailure is swallowed on both result shapes that lack a diagnostics channel. The EVC batch item / read succeeds, so nothing reaches failedBatchItems or a DataIssue; the data just disappears and is indistinguishable from an empty position. The account adapter path reports these as SOURCE_UNAVAILABLE — these two paths should at least log the reason, mirroring the ABI-fallback warnings already added in both functions.

  • packages/euler-v2-sdk/src/services/executionService/simulate.ts#L863-L873: before continue, warn with meta.subAccount, meta.vault, and queryFailureReason so a dropped position that skews canExecute/snapshots is visible.
  • packages/euler-v2-sdk/src/services/rewardsService/rewardsService.ts#L616-L627: in the queryFailure branch, warn with the vault and queryFailureReason before returning [].
📍 Affects 2 files
  • packages/euler-v2-sdk/src/services/executionService/simulate.ts#L863-L873 (this comment)
  • packages/euler-v2-sdk/src/services/rewardsService/rewardsService.ts#L616-L627
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/euler-v2-sdk/src/services/executionService/simulate.ts` around lines
863 - 873, Log in-band query failures before dropping the result. In
packages/euler-v2-sdk/src/services/executionService/simulate.ts:863-873, update
the vaultAccount queryFailure branch to warn with meta.subAccount, meta.vault,
and queryFailureReason before continue; in
packages/euler-v2-sdk/src/services/rewardsService/rewardsService.ts:616-627,
update the queryFailure branch to warn with the vault and queryFailureReason
before returning [].

@coderabbitai coderabbitai Bot mentioned this pull request Aug 13, 2026
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants