When Collateral Is a Live Market: Auditing Lending Protocols on Bittensor

A Bittensor lending market is not just Solidity lending with a different ticker. The collateral is staked, its TAO value comes from a live subnet market, and core asset movement crosses an EVM-to-runtime boundary. This is how I approached that audit surface.

0xTheBlackPanther 14 min read Bittensor, EVM, Lending, Security
Private engagement, public lessons

I completed this review as part of a private engagement with BurraSec. The client, protocol name, code, findings, severities, proof-of-concepts, and remediation details remain confidential. The scenarios in this article are audit lenses for this protocol category, not disclosures about the reviewed system.

I have audited lending systems on EVM and Move before, but Bittensor changes the shape of the problem in a useful way. The familiar pieces are still there: lenders, borrowers, collateral shares, debt shares, interest, health checks, liquidations, and bad-debt handling.

Then Bittensor adds another financial system underneath them.

Each subnet has its own alpha token and market. Staking TAO buys alpha; unstaking alpha sells it back. Solidity reaches that state through Bittensor-specific precompiles. A borrower's collateral can earn staking rewards while its executable TAO value changes with AMM depth, fees, emissions, and market activity.

The key mental model: collateral is simultaneously a staked balance, a share of a growing pool, and an inventory position in a live market.

That combination creates a niche audit surface. A standard lending checklist helps, but it is not enough.


Why Bittensor Lending Is Different

In a typical ERC-20 lending market, the protocol asks an oracle for a price and transfers collateral with a token call. On Bittensor, the protocol may need to ask much harder questions:

The public mechanics are worth learning first. Bittensor's dTAO whitepaper introduces subnet alpha markets, while the current EVM chain-state guide documents the live precompile-facing data. It also highlights an immediate unit boundary: EVM native value uses 18 decimals, while runtime amounts are commonly exposed in RAO, where one TAO is one billion RAO.

the system I draw before reading implementation details TAO-denominated liquidity <---- lending accounting ----> borrower debt | | | v lender shares health checks ^ | subnet alpha stake <---- staking precompile ----> executable TAO value | v collateral shares + staking yield + subnet lifecycle

The attack surface lives in the arrows. Every arrow crosses a unit, accounting model, trust boundary, or execution environment.

The First Artifact: A Unit Ledger

Before reviewing formulas, I label every important value with its unit. This sounds basic. In this category, it is one of the highest-signal things an auditor can do.

Value Meaning Main audit question
EVM wei Native TAO at 18 decimals Where is conversion to runtime precision performed?
RAO Runtime TAO unit, 1e9 per TAO Which remainder is rounded away, and who absorbs it?
Alpha Subnet-specific staked asset Is the code moving alpha or valuing alpha in TAO?
Protocol shares Claim on a changing asset balance Does conversion round for or against the pool?
Debt shares Claim on accrued borrow assets Has interest been accrued before conversion?

From there, I expand every equation into a dimensionally annotated version. A health check should not merely "look right":

dimension-first health model collateral_alpha [alpha] executable_value(collateral_alpha) [RAO] debt_assets [RAO] lltv [basis points] healthy := debt_assets <= executable_value(collateral_alpha) * lltv / 10_000

Then I trace every conversion feeding that expression. Is alpha converted using multiplication or inverse pricing? Does a helper return TAO-per-alpha or alpha-per-TAO? Does a fee apply before or after curve math? Is the result rounded down for borrowing but up for seizure?

Unit mistakes often survive review because each individual line uses a uint256. The compiler sees matching types. The economy does not.

Price Is an Execution Path, Not a Number

A subnet's spot quote can describe the marginal price of alpha. A lending protocol needs more than that. It needs to know what a particular collateral amount can actually recover.

There is a second Bittensor-specific trap here: do not assume the pool engine described in an older paper is the engine deployed today. Pool mechanics can evolve with the runtime. I treat the deployed simulation interface and current runtime code as the source of truth, then pin that behavior in tests.

DISPLAY PRICE spot / reserve state

Useful for orientation
Ignores trade size
Can overstate recoverable value
EXECUTABLE VALUE simulated alpha sale

Includes market depth
Includes fee and rounding
Matches liquidation economics

This changes how I audit both borrowing and liquidation. I do not stop at "the oracle is manipulation resistant." I ask whether the value definition matches the action:

  1. Borrowing: Can a user borrow against a high reference value that could not be realized on the live market?
  2. Liquidation eligibility: Which price view decides that a position is unhealthy, and how quickly can that view move?
  3. Liquidation payment: Is collateral seized using the same economic assumptions as debt repayment?
  4. Capacity: Is aggregate debt bounded by the market depth available to unwind collateral?

I test these properties at multiple sizes. A formula that is conservative for one RAO can become optimistic for a large sale because slippage is nonlinear. The reverse can also happen near zero, where fee floors and integer division create discontinuities.

Audit rule: Whenever a protocol converts alpha to TAO, write down whether the code wants a quote, a safety bound, or an executable result. Those are different values.

Stored State vs Effective State

Lending protocols already use lazy accounting: interest is accrued globally, while individual debt is derived from shares. Bittensor collateral can add another lazy layer because staked balances change through rewards and runtime-side accounting.

Some designs also defer expensive per-position bookkeeping. That can be safe, but only if every economically important read sees the same effective state.

the read-through model effective collateral = collateral represented by shares + earned staking growth - pending economic adjustments effective debt = debt represented by shares + accrued interest - repayments already recognized globally

I build a read matrix for every entry point: deposit, withdraw, borrow, repay, add collateral, remove collateral, liquidate, emergency settlement, and view functions. For each one I record:

The dangerous state is accounting split-brain: one path sees the post-event world while another still prices claims from the pre-event world. That is how timing becomes value transfer.

The defense is not necessarily eager settlement. A well-designed read-through model can make deferred settlement economically transparent. But "transparent" is a property to prove across every consumer, not a comment to assume.

Liquidation Must Work in the Real Runtime

A position being mathematically liquidatable is not enough. The entire liquidation pipeline has to complete:

liquidation liveness pipeline 1. update interest and effective collateral 2. prove the position is unhealthy 3. calculate repay and seize amounts 4. move or unstake alpha through the runtime 5. collect TAO-denominated repayment 6. update debt, collateral, and pool totals 7. handle empty collateral and residual debt

I attack every step independently. What if the amount is below a runtime minimum? What if integer math produces zero repayment but non-zero shares? What if the precompile returns unexpected behavior? What if the final recipient rejects native value? What if the last collateral share has no executable TAO value?

These are not just dust questions. A tiny remainder can prevent the state machine from reaching its bad-debt write-off branch. Once that happens, an economically dead receivable may continue to sit inside lender accounting.

The robust design pattern is to separate safety-critical accounting from best-effort external delivery. A failed transfer should not silently erase a claim, but a non-essential delivery leg should not permanently block liquidation either.

The Runtime Is Part of the Trusted Computing Base

Solidity source is only half the implementation. Staking precompiles dispatch into the Bittensor runtime, and their real behavior is defined by the deployed runtime version.

For each precompile call, I record:

Mocks are useful for Solidity control flow, but they are weak evidence for runtime semantics. We combined code reading with targeted chain probes and integration tests against a real local Bittensor node. When the local runtime and documentation disagreed, the right response was not to pick the convenient answer. It was to identify the worst safe behavior, test it explicitly, and document the version dependency.

A precompile is not a friendly library. Audit it like an external protocol whose implementation can change underneath immutable Solidity.

Lifecycle States Matter as Much as Price States

Most lending reviews spend their time on healthy and unhealthy positions. On Bittensor, I add a second axis: the lifecycle of the collateral subnet and the runtime features around it.

The state-space worksheet looks like this:

Position Normal subnet Changed/unavailable subnet
collateral only must have a normal exit must have a recovery exit
debt + collateral repay or liquidate settle without relying on missing alpha
debt, no useful collateral write off deterministically socialize once, not twice
lender only withdraw at current share price withdraw after losses are reflected

Every cell needs a reachable terminal state. Recovery logic should also be order independent: processing borrower A before borrower B must not change how much value each party receives.

The Most Valuable Moment: Disproving Our Own Lead

One of the best lessons from this engagement came from a candidate issue that initially looked convincing. A continuous-math model suggested that two accounting quantities could cross at a dangerous boundary. The code path existed. The impact story was plausible.

But plausible is not exploitable.

We rebuilt the path with Solidity's exact integer rounding, modeled the external amount conversion, and searched the boundary under the protocol's documented operating assumptions. The dangerous state was not reachable in the valid regime. It only reappeared after violating an operational precondition or assuming abnormal external behavior.

So we narrowed the conclusion instead of pushing the burden onto the client.

The lesson: A good audit is not measured by how many suspicious paths survive into the report. It is measured by how aggressively the auditors try to kill their own findings before the client has to.

This also exposed a documentation lesson. If safety depends on an operating regime, that regime belongs next to the code. Teams should document the precondition, explain the invariant it enables, state whether it is enforced on-chain, and define behavior when the system leaves that regime.

The Testing Stack That Produced Signal

1. Exact arithmetic models

For AMM and share math, I use small independent models with the same floor and ceiling directions as Solidity. They are fast enough to sweep reserve depth, debt, collateral, fees, and one-unit boundaries.

2. Real-runtime integration tests

Anything depending on staking, address mapping, subnet state, or runtime error behavior is tested against a real node. Pure EVM forks and mocks cannot prove precompile semantics.

3. Stateful scenario tests

I compose operations instead of testing them in isolation: accrue, borrow, change market conditions, add yield, partially repay, liquidate, withdraw, and enter recovery states. Many accounting bugs require two individually valid actions in a specific order.

4. Invariant checks

The most useful invariants are economic:

5. Hypothesis falsification

For every lead, we ask what defense would make it false: access control, conservative rounding, read-through accounting, an upstream runtime guarantee, an unreachable precondition, or a bounded loss. Then we test that defense directly.

A Reusable Bittensor Lending Checklist

architecture and units [ ] Map every TAO, RAO, alpha, asset-share, and debt-share conversion [ ] Record floor/ceil direction and who benefits from each rounding choice [ ] Separate displayed price, reference price, and executable value [ ] Trace custody across Solidity and the Bittensor runtime accounting [ ] Accrue interest before every share-sensitive operation [ ] Compare global totals with the sum of individual positions [ ] Identify every stored, virtual, pending, and effective balance [ ] Verify all user paths consume the same effective state [ ] Check first/last lender, first/last borrower, and zero-supply states liquidation and recovery [ ] Prove unhealthy positions are operationally liquidatable [ ] Test zero-output, minimum-amount, and all-collateral boundaries [ ] Ensure residual debt is written off once collateral is exhausted [ ] Confirm external delivery failures cannot brick protocol accounting [ ] Enumerate position state x subnet lifecycle state precompile boundary [ ] Verify amount units and caller identity against official/runtime code [ ] Measure actual balance deltas around asset-moving calls [ ] Test rate limits, partial execution, revert data, and gas behavior [ ] Pin the tested runtime/image and maintain re-runnable chain probes [ ] Reassess assumptions after Bittensor runtime upgrades finding quality [ ] Reproduce with exact integer math, not only continuous formulas [ ] Distinguish in-regime behavior from violated operational assumptions [ ] Prove attacker permissions, reachability, and measurable impact [ ] Search for compensating controls before reporting

What I Would Ask a Bittensor Lending Team For

A strong audit package makes this review dramatically faster and more reliable. At minimum, I want:

  1. A units and rounding table. Every public amount and internal conversion should have an explicit dimension.
  2. An operation-by-operation accounting spec. Show which totals, shares, runtime balances, and pending values change.
  3. A precompile behavior matrix. Include version, minimums, failure behavior, and links to reproducible probes.
  4. An oracle threat model. Explain why each price view is appropriate for borrowing, liquidation, and capacity.
  5. A lifecycle matrix. Cover live, paused, degraded, and unavailable collateral states.
  6. Named invariants with tests. Comments are useful; executable properties are better.
  7. Accepted risks and off-chain assumptions. If an operator must preserve a safety regime, say exactly how.

That documentation is not ceremony. It is part of the security boundary. In a system spanning Solidity, AMM economics, staking shares, and an upgradeable runtime, undocumented assumptions become invisible dependencies.


Final Thoughts

Bittensor lending is a fascinating audit niche because it combines three disciplines that are often reviewed separately: lending accounting, AMM economics, and runtime integration.

The hardest bugs are rarely isolated arithmetic mistakes. They emerge when a value changes meaning as it crosses a boundary: wei becomes RAO, TAO becomes alpha, alpha becomes collateral shares, a spot quote becomes a liquidation valuation, or a deferred update becomes a lender share price.

My approach is to make those boundaries explicit, reduce the protocol to economic invariants, test the real runtime, and treat every finding as guilty of being a false positive until it survives exact execution.

On Bittensor, the collateral does not merely have a price. It has a market, a staking lifecycle, and a runtime. Audit all three.

Context: Private Bittensor lending protocol review with BurraSec
Public scope shared: Methodology and protocol-category lessons only; no client findings or identifying details
Core audit lenses: Unit safety, executable valuation, lazy accounting, liquidation liveness, precompile semantics, runtime drift
Related: How to Audit a Lending Protocol on Sui Move
Follow: @thepantherplus