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.
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:
- Is the collateral balance stored by Solidity, reported by the runtime, or derived from protocol shares?
- Is "price" a spot quote, a smoothed reference, or the TAO actually obtainable by selling this exact amount of alpha?
- Does a staking precompile revert, partially execute, round, rate-limit, or change behavior after a runtime upgrade?
- Can liquidation still complete when the collateral is thin, the position is tiny, or an external staking leg fails?
- What happens to debt and lender claims when a subnet changes lifecycle state?
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 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":
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.
Useful for orientation
Ignores trade size
Can overstate recoverable value
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:
- Borrowing: Can a user borrow against a high reference value that could not be realized on the live market?
- Liquidation eligibility: Which price view decides that a position is unhealthy, and how quickly can that view move?
- Liquidation payment: Is collateral seized using the same economic assumptions as debt repayment?
- 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.
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:
- whether interest is accrued first;
- whether pending position accounting is settled or read through;
- whether health uses stored or effective collateral;
- whether total assets includes debt that may later be written off;
- whether shares are priced before or after external state changes.
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:
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:
- the input and output units;
- who the runtime treats as the coldkey or owner;
- minimum amounts and rate limits;
- whether failure returns data or consumes the forwarded gas;
- whether success guarantees the full requested amount moved;
- which state can change between a simulation and execution;
- which assumptions may change in a runtime upgrade.
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:
- lender share price rises through interest and yield, except when a real loss is socialized;
- aggregate debt matches the sum of borrower claims within defined rounding bounds;
- collateral cannot leave a position unless the owner authorizes it or liquidation is valid;
- bad debt is recognized exactly once;
- processing order does not change aggregate recovery;
- no critical exit depends on an economically worthless dust amount being transferable.
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
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:
- A units and rounding table. Every public amount and internal conversion should have an explicit dimension.
- An operation-by-operation accounting spec. Show which totals, shares, runtime balances, and pending values change.
- A precompile behavior matrix. Include version, minimums, failure behavior, and links to reproducible probes.
- An oracle threat model. Explain why each price view is appropriate for borrowing, liquidation, and capacity.
- A lifecycle matrix. Cover live, paused, degraded, and unavailable collateral states.
- Named invariants with tests. Comments are useful; executable properties are better.
- 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.
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