Skip to main content

Command Palette

Search for a command to run...

Breaking Down AlchemistV2: How Alchemix Handles Yield and Debt

Updated
6 min read
Breaking Down AlchemistV2: How Alchemix Handles Yield and Debt
A
Blockchain Developerr || Frontend Developer || Solidity || Frontend + Web3 || Technical Writer || Open to roles & collabs

Before diving in, you can follow along directly with the source code here:
https://github.com/alchemix-finance/v2-foundry/blob/master/src/AlchemistV2.sol

Overview

This article breaks down the internal architecture and accounting model behind AlchemistV2.sol. I’ll focus strictly on the core contract: how it structures debt, distributes yield, and enforces its invariants while intentionally leaving out adapter-specific implementations.

By the end of this article, you should have a clear understanding of how Alchemix turns yield into automatic debt repayment, and why its design is fundamentally different from traditional lending protocols.

How to Read a Contract Like This

A contract like AlchemistV2.sol isn’t something you read from top to bottom.At over 1,700 lines, trying to follow it sequentially quickly becomes confusing. The logic isn’t linear, it’s layered.

The better approach is to start from the outside:

  • What does the system do?

  • How is it structured?

  • What are the core invariants?

Once those are clear, the internal mechanics start to make sense.
That’s the approach we’ll take here.

What AlchemistV2 Actually Is

At its core, AlchemistV2 isn’t really a lending protocol. It’s a yield transformation engine.
Instead of borrowing and then manually repaying, users deposit yield-bearing assets and mint synthetic tokens against them. The key difference is what happens next:

The yield generated by the collateral is continuously used to pay down the debt.

There’s no “repay” button required.

A helpful way to think about it is this: you’re borrowing against an asset that is actively earning income and that income is automatically servicing the loan.
The system doesn’t wait for user intent. It just keeps reconciling incoming value against outstanding debt.

Architecture: Built Through Composition

The structure of the contract tells you a lot about how it’s meant to behave.

contract AlchemistV2 is IAlchemistV2, Initializable, Multicall, Mutex {
    using Limiters for Limiters.LinearGrowthLimiter;
    using Sets for Sets.AddressSet;
}

Each inherited component plays a specific role:

  • Initializable replaces constructor logic for upgradeable deployments

  • Multicall allows batching multiple operations into one transaction

  • Mutex protects against reentrancy

But the most important piece is IAlchemistV2.

It’s not just an interface, it’s a full definition of the protocol’s external shape. It bundles together actions, admin controls, events, errors, and even storage expectations.

By inheriting it, the contract is forced to implement everything the system promises, before any logic is written.

External Interfaces and Libraries

The contract doesn’t operate in isolation, it relies on a few key external abstractions:

  • Token adapters standardize how yield-bearing assets are priced and converted

  • The debt token extends ERC20 with minting and burning

  • Whitelist checks restrict certain contract interactions

Under the hood, helper libraries handle the messy details:

  • safe casting between integer types

  • managing dynamic sets of tokens

  • dealing with inconsistent ERC20 implementations

  • enforcing rate limits

These aren’t flashy, but they’re what make the system safe and predictable at the EVM level.

The Core Idea: Debt as a Signed Integer

One of the most elegant design choices in AlchemistV2 is how it represents debt.

struct Account {
    int256 debt;
    mapping(address => uint256) balances;
    mapping(address => uint256) lastAccruedWeights;
    Sets.AddressSet depositedTokens;
}

Instead of tracking borrowed amounts and repayments separately, everything is collapsed into a single int256.

  • Minting increases debt

  • Yield decreases it

  • Excess yield pushes it negative (meaning the user has net credit)

That’s it.

No separate repayment logic. No tracking “what’s left to pay.” Just one value that moves over time.

Lazy Accounting: Repayment Without Transactions

This is where things get really interesting. AlchemistV2 doesn’t actively distribute yield to every user. That would be too expensive.
Instead, it uses a lazy accounting model:

  • The system tracks a global “credit per share” value

  • Each user stores the last value they saw

  • When they interact, the difference is calculated

  • That difference is applied to their debt

So repayment isn’t something that happens

…it’s something that’s derived when needed.

This is what allows the system to scale without constantly updating every account.

What Happens When You Mint

Let’s look at the mint flow:

function _mint(address owner, uint256 amount, address recipient) internal {
    _checkMintingLimit(amount);
    _preemptivelyHarvestDeposited(owner);
    _distributeUnlockedCreditDeposited(owner);
    _poke(owner);
    _updateDebt(owner, SafeCast.toInt256(amount));
    _validate(owner);
    _mintingLimiter.decrease(amount);
    TokenUtils.safeMint(debtToken, recipient, amount);
}

The important thing here isn’t just what happens but the order in which it is arranged .
Before debt is increased:

  • Yield is harvested

  • Credit is distributed

  • The user’s position is synced

Only then is the new debt added.

And right after that, the system checks if the position is still safe.

If anything is off, the entire transaction reverts.

This ordering guarantees that users can’t “skip ahead” of the system and mint against outdated state.

Why Yield Isn’t Distributed Instantly

If yield were distributed immediately, the system would be vulnerable to flash loan attacks.

An attacker could:

  1. Deposit a large amount temporarily

  2. Trigger yield distribution

  3. Capture the rewards

  4. Exit in the same block

AlchemistV2 prevents this by spreading yield over time.

function _distributeCredit(address yieldToken, uint256 amount) internal {
    YieldTokenParams storage yieldTokenParams = _yieldTokens[yieldToken];
    uint256 lockedCredit = _calculateLockedProfit(yieldToken);

    yieldTokenParams.pendingCredit = amount + lockedCredit;
    yieldTokenParams.distributedCredit = 0;
    yieldTokenParams.lastDistributionBlock = block.number;
}

Yield is first locked, then gradually released across blocks. This ensures that only users who stay in the system actually benefit from it.

Where Things Can Break

Even with a strong design, there are a few pressure points.
Unbounded iteration There’s no hard limit on how many tokens a user can deposit. Over time, this could make some operations too expensive to execute.
Access control assumptions The contract distinguishes users using tx.origin. That works today, but may break with account abstraction and smart wallets.
Adapter trust (the biggest one) The system depends on external adapters for pricing. If those are wrong or manipulated, the entire collateral model fails.

Handling Catastrophic Failure

If a yield source fails completely, the protocol has a recovery mechanism:

function snap(address yieldToken) external override lock {
    _onlyAdmin();
    uint256 expectedValue = convertYieldTokensToUnderlying(
        yieldToken, _yieldTokens[yieldToken].activeBalance
    );

    _yieldTokens[yieldToken].expectedValue = expectedValue;
    emit Snap(yieldToken, expectedValue);
}

This allows governance to manually reset expectations and bring the system back into a consistent state.
It’s not pretty but instead, it is necessary.

Summary

AlchemistV2 works because it separates concerns cleanly:

  • Global state tracks how yield is distributed

  • User state tracks how that yield affects individual debt

Instead of pushing updates to every account, it pulls updates only when needed. That’s what makes it efficient. But it also means one thing:

The system is only as reliable as the data it receives.

Conclusion

To really understand AlchemistV2, you have to stop thinking in terms of borrowing and repaying.

Debt here isn’t static,it’s constantly evolving, repayment isn’t an action it’s an outcome and the entire system is built to enforce that idea through:

  • structured inheritance

  • strict execution ordering

  • and time based value distribution

If you’re going deeper into the protocol, the next place to look isn’t inside this contract, It’s the adapters because in a system like this, if the inputs are wrong, everything else follows.