DeFi Security AllianceRequest an audit
Menu

EVM patterns

Smart Contract Dependency Risk: Libraries, Live Protocols and Audit Scope

A standard library can be reviewed while the application built from it remains unsafe. Smart contract dependency risk comes from the chosen version, local overrides and assumptions about live external systems, so each boundary needs its own evidence.

Tower of translucent blocks with one blue block half pulled from the base, illustrating dependency risk in smart contracts.

Key facts

Original measurement
14 dependencies plus the root in the v5.4.0 ERC4626 import closure
Direct imports
4 at the root, expanding to 19 graph edges
Measurement limit
Static source traversal; no deployment or vulnerability count
Scope decision
Review local overrides and accepted external behavior separately

Inherited libraries

A vault can contain very little handwritten code and still rely on a substantial source graph. Importing a standard implementation brings in its parent contracts, interfaces and utility libraries. The developer sees one import line; the compiler resolves a larger collection of files. That difference is the starting point for smart contract dependency risk.

Consider an illustrative vault that inherits OpenZeppelin's ERC4626 implementation and changes how managed assets are calculated. The inherited deposit and redemption paths now depend on the new accounting behavior. Even if the underlying library received review, that review cannot establish the correctness of the override or the asset adapter it calls. The boundary is the composition.

Our fixed-version import traversal found that the root file's direct imports led to additional dependencies. That is source evidence, not evidence of a defect. A source graph helps define what must be available for review; it does not tell you how much of every file remains reachable in a particular compiled deployment.

Separate unchanged library code from modified code and inherited behavior affected by overrides. Auditors need that distinction to allocate effort. A patch hidden inside a vendored package is particularly easy to overlook if the scope calls the dependency standard. Preserve a diff against the exact upstream revision and identify why each modification exists.

Library cases require different review evidence
CaseWhat changesEvidence to provide
Unmodified tagged releaseApplication chooses how to call or inherit itExact version and integration assumptions
Overridden behaviorA parent operation can use child-defined behaviorOverride map and tests of inherited entry points
Vendored patchUpstream code differs from the reviewed releasePatch diff and provenance of the baseline
Upgradeable variantInitialization and storage assumptions differCorrect package family and upgrade validation record

OpenZeppelin's current documentation distinguishes audited release tags from development releases. Read that statement as a description of its release process, not as a certification of your application. Using a tagged dependency gives the reviewer a stable reference. It does not remove the need to inspect the way the application combines that dependency with its own state and permissions.

The import closure behind one vault file

Method

Source and selection
Follow every relative Solidity import transitively from ERC4626.sol in the fixed OpenZeppelin Contracts v5.4.0 tag. Deduplicate normalized paths. Count root separately.
Retrieved
Observation
Static traversal at the OpenZeppelin Contracts v5.4.0 tag; every discovered relative import was fetched successfully.

Results

Recorded observations on
ObservationCountMeaning
Root direct imports4Imports declared by ERC4626.sol
Unique source files15Root included
Transitive dependencies14Unique files other than the root
Import edges19References between source files

The root's 4 direct imports expanded to 15 unique Solidity source files, including the root. Shared files were counted once even when multiple imports reached them. The graph contained 19 import edges.

For scoping, the useful result is the difference between direct imports and the complete source set. Sending only the root and its immediate neighbors would leave source dependencies out of the review bundle. The count does not imply that every imported declaration is deployed or that every edge deserves equal review effort. Those questions require compiler output and application-specific reachability analysis.

Limits

  • The survey followed source imports only. It did not compile the vault or execute tests.
  • The fixed v5.4.0 tag is a reproducibility choice, not a recommendation to ignore later security releases.
  • Compiler binaries, package installation scripts and off-chain dependencies were excluded from this Solidity import closure.

The reproducible record is kept in the repository: seo/research/articles-32-41-2026-09-05/surveys.py and seo/research/articles-32-41-2026-09-05/survey-33.json. These file paths are not public downloads.

Integrated protocols and oracles

An imported library and an integrated protocol are different dependencies. Library source usually participates in the application's build. A live protocol remains another system with its own deployment, configuration and operators. The interface can stay the same while the behavior behind it changes.

For each external call, identify the chain and target address as well as the interface. Then record who can alter the target, which implementation is active and what happens when the dependency reverts or returns an unexpected result. An address in a test fixture is not sufficient evidence that the production deployment uses the same system.

A price adapter illustrates the distinction. The adapter may correctly decode a returned value while the application makes an unsafe assumption about freshness, decimals or the circumstances in which the value is usable. Scoping only the adapter's syntax leaves the economic decision outside the review. The oracle manipulation analysis explains why the source of a price belongs in the threat model.

Dependencies cross different review boundariesDependency assurance moves from a reproducible source set to the live authority that can change an integration.Source packageApplication logicLive integrationChange authority
Dependencies cross different review boundaries. Dependency assurance moves from a reproducible source set to the live authority that can change an integration.
  1. Resolve imported files and record their exact upstream revision.
  2. Inspect overrides and the assumptions introduced by local composition.
  3. Bind the external interface to its chain, address and implementation.
  4. Identify who can change behavior after the review and how the team will notice.

The same reasoning applies to a token used by a vault. The interface does not fully describe transfer fees, callbacks, rebasing or administrative controls. State the supported asset behavior and identify the check that rejects unsupported behavior. If the design intentionally accepts a constrained set of assets, make the onboarding process part of the review boundary.

An integration failure also needs a user-facing policy. Determine whether deposits stop, withdrawals remain possible or accounting falls back to a previous observation. These choices have different consequences. A fallback that preserves availability may keep accepting an obsolete price; a fail-closed policy may trap a legitimate operation until the dependency recovers. The application owner must choose and document the intended behavior before a reviewer can assess it.

Version pinning

A version constraint describes allowed releases. A lockfile or resolved commit records what a particular build selected. Retain both when they serve different parts of the build process. A review is reproducible only if another engineer can recover the same source set and compiler inputs.

Use an immutable revision in the engagement record and preserve the dependency package itself or its verified retrieval location. A branch name is a moving reference. A release tag is a better operational handle, but the record should still include the resolved commit and checksums when those artifacts are available. This is evidence management rather than an assertion that a tag is malicious.

Pinning does not mean freezing forever. It makes changes visible so the owner can decide when a dependency update requires renewed review. Create an update path that compares the previously reviewed version with the proposed version, identifies affected application paths and records the resulting acceptance decision. The time to establish this process is before an urgent advisory arrives.

Version records answer separate questions
RecordQuestionInsufficient substitute
Declared package constraintWhich versions may installation select?A remembered package name
Resolved lockfile or commitWhich source version did this build select?A mutable branch label
Compiler inputs and settingsHow was the source transformed?Only the Solidity pragma
Deployment comparisonDoes production correspond to the checked build?A successful local compilation

Storage compatibility needs special attention when upgrades are involved. OpenZeppelin warns that storage layouts across major versions should be assumed incompatible. Its documentation gives a concrete major-version transition as an unsafe assumption. The general scoping consequence is to inspect the actual upgrade path and validation result, rather than treating a higher version number as proof of a safe replacement.

Record deployment configuration next to the version data. Constructor arguments, initializer parameters and external target addresses can change behavior even when the source revision stays fixed. A reviewer who receives only the source commit cannot reconstruct those decisions from the repository name.

Reproduce the dependency that actually shipped

Imagine an application that pins a library package but copies one helper into its own repository to customize rounding. The package lock describes the installed dependency. It does not describe the copied helper's later changes. The review package should identify the copied source, its original version and the local diff so the assessor can examine the behavior that actually executes.

A useful release record separates source identity from build identity. Retain the source revision and dependency resolution alongside the compiler configuration used for the release. Then connect those inputs to the deployed artifact through the project's verification process. A reproducible source tree is valuable evidence, but the application still needs a check that the deployed system corresponds to it.

Dependency updates deserve a behavioral review even when the public interface stays the same. Compare the assumptions used by the caller: return values, revert behavior, rounding and initialization. The question is whether a change invalidates an application property, not merely whether the project still compiles. A test that only checks successful compilation cannot answer that question.

Use an explicit decision record when postponing an update. Identify the affected dependency, why the team is holding the version and what evidence would trigger reconsideration. That makes the unresolved exposure visible to the release owner. It also avoids treating the phrase pinned dependency as a permanent security conclusion: a pin stabilizes an input while the surrounding threat and deployment conditions can still change.

Supply-chain checks

The Solidity import graph is only one part of the build's supply chain. An installation command can involve package registries, scripts and compiler downloads before the contract is compiled. Those steps can change what enters the build or expose the environment that performs it. Keep their review separate from the contract-level source closure so neither task disappears into a vague dependency checkbox.

Start by reproducing the build in a controlled environment with the resolved dependency set. Record the commands, compiler executable and output comparison. If the build cannot be reproduced, stop treating its source bundle as a verified representation of production. Investigate the mismatch before adding more scanner output.

  1. Inventory the dependency sources, resolved versions and any local modifications. Preserve the actual lockfiles and remappings used by the build.
  2. Inspect installation and build steps that execute code or retrieve binaries, including the permissions available to those steps.
  3. Compare the proposed update with the reviewed baseline and identify application paths affected by changed interfaces or behavior.
  4. Reproduce the intended build, validate the resulting deployment match and retain the evidence with the release record.

Reviewing a dependency advisory is also an applicability exercise. Match the affected package, version and code path to the application. A package-name match can be useful for triage but does not establish that an affected function is used. Conversely, the absence of an advisory cannot establish that the integration assumptions are correct.

Assign ownership for notifications and emergency updates. If every package update goes to an unmonitored inbox, the pinned build may remain reproducible while becoming operationally neglected. The owner should know who evaluates an advisory, who approves an update and which evidence is required before release.

The upgradeable contract audit guide covers the proxy-specific checks that sit beside this supply-chain work. Keep the upgrade authorization itself in view. A perfectly reproduced build offers little assurance about a later implementation change if nobody has recorded the authority that can make it.

What stays out of scope

An exclusion can be reasonable when it is explicit and someone owns the remaining risk. For example, a contract reviewer may receive a fixed external protocol interface without reviewing the dependency's entire codebase. The resulting report should describe the trust assumption and the application checks around it. It should not imply that the excluded protocol received an independent review.

Write exclusions as boundaries with consequences. Saying that oracles are excluded leaves too much ambiguity. Specify whether the engagement excludes the oracle network itself while reviewing adapter validation, deployment configuration and failure behavior. Those are separable tasks, and an integrator needs to know which were performed.

Exclusions need an owner and an acceptance condition
BoundaryResidual questionOwner evidence
External protocol internalsCan the external system change or fail in an unsafe way?Integration owner records accepted assumptions and monitoring
Compiler implementationDoes the chosen compiler have an applicable known issue?Build owner records version and advisory review
Front-end deliveryCan the interface direct users to a different target?Application security owner validates release and transaction construction
Privileged operationsWho can alter the configuration after launch?Operations owner records authority and change controls

Our Pharos Production member profile and the member directory help identify providers to question about these boundaries. Their presence in the directory does not establish that a quote includes every dependency layer. Ask for the actual engagement scope and the deliverables that will resolve your application's assumptions.

A scope that excludes an entire dependency may still include tests against a fixed local model of it. Label that evidence accurately. Passing a test against a mock shows behavior under the mock's rules; it does not prove that the live external system follows those rules under every condition. The report should identify where this substitution matters.

Questions for your auditor

Send a dependency map before asking for a final quote. A reviewer can then distinguish a small inheritance-based extension from an integration that crosses several live trust boundaries. The goal is an agreed coverage decision, not a larger file count for its own sake.

What exact source set will you review?
Ask for repository revisions, package versions and a treatment of transitive imports. Include modified vendor code in the answer.
Which inherited paths change because of our overrides?
Require the review plan to connect local overrides with the parent entry points that call them.
What external behavior do you assume?
Identify asset behavior, oracle conditions, proxy implementations and the actors able to change them.
What would trigger another review?
Agree how dependency updates, deployment changes and newly supported assets affect the original scope.

Request an acceptance example. For a vault adapter, the reviewer might identify a supported token behavior, the application check enforcing it and the response when the assumption fails. This is more informative than a general statement that integrations will be assessed.

Finally, give the reviewer the change history that explains why the dependency was introduced. A replacement library can preserve the same function names while changing rounding or initialization behavior. The reason for the change often points directly to the assumption that deserves attention.

Apply the model to a new integration: the source package stays fixed, but governance can replace the live target. Version pinning still helps reproduce the application build. The unresolved question is who controls the external behavior after that build is deployed, and which application control remains valid when the target changes.

Frequently asked questions

Does verified source prove that an installation environment was trustworthy?

No. Source verification addresses the relationship between source inputs and deployed code. It does not by itself establish the provenance or permissions of every tool that ran during installation.

Can a dependency update be reviewed without repeating the full application audit?

Sometimes. The decision depends on the actual diff and affected assumptions. A bounded review must identify the unchanged baseline, changed behavior and interfaces that remain outside the new engagement.

What if a vendor removes the old package download?

Use the preserved source bundle and checksums from the reviewed build. If those artifacts were not retained, record the reproducibility gap and recover the exact revision before treating a replacement as equivalent.