All incidents

Olympus Teller Redemption Drain

Share
Oct 21, 2022 05:20 UTCAttackLoss: 30,437.08 OHMPending manual check3 exploit txWindow: 9h 8m
Estimated Impact
30,437.08 OHM
Label
Attack
Exploit Tx
3
Addresses
2
Attack Window
9h 8m
Oct 21, 2022 05:20 UTC → Oct 21, 2022 14:29 UTC

Exploit Transactions

TX 1Ethereum
0x05f548db9215621c49d845482f1b804d82697711ef691dd77d2a796f3881bd02
Oct 21, 2022 05:20 UTCExplorer
TX 2Ethereum
0x3ed75df83d907412af874b7998d911fdf990704da87c2b1a8cf95ca5d21504cf
Oct 21, 2022 05:22 UTCExplorer
TX 3Ethereum
0xd38c92dc3de78ad282c2403a434db28fc47d3bbecdaece08c2bf91bd333c918e
Oct 21, 2022 14:29 UTCExplorer

Victim Addresses

0x007fe7c498a2cf30971ad8f2cbc36bd14ac51156Ethereum
0x64aa3364f17a4d01c6f1751fd97c2bd3d7e7f1d5Ethereum

Loss Breakdown

30,437.08OHM

Similar Incidents

Root Cause Analysis

Olympus Teller Redemption Drain

1. Incident Overview TL;DR

An unprivileged attacker drained the OHM inventory held by Olympus Bond Protocol's BondFixedExpiryTeller at 0x007fe7c498a2cf30971ad8f2cbc36bd14ac51156 by redeeming an attacker-deployed fake bond token. The fake token contract was deployed in transaction 0x05f548db9215621c49d845482f1b804d82697711ef691dd77d2a796f3881bd02, the drain occurred in transaction 0x3ed75df83d907412af874b7998d911fdf990704da87c2b1a8cf95ca5d21504cf, and the attacker later withdrew the stolen OHM to its EOA in transaction 0xd38c92dc3de78ad282c2403a434db28fc47d3bbecdaece08c2bf91bd333c918e.

The root cause is that BondFixedExpiryTeller.redeem trusted an attacker-supplied ERC20BondToken interface instead of validating that the token address was a canonical teller-issued bond token. That let a fake contract choose a past expiry, return OHM as the underlying asset, no-op the burn, and trigger a transfer of 30437077948152 raw OHM units from the teller to the attacker.

2. Key Background

BondFixedExpiryTeller is intended to redeem only bond tokens that it previously created and tracked in its bondTokens[underlying][expiry] registry. A valid redemption therefore requires two conditions at the same time: the bond must be mature, and the bond token contract must be the teller-issued token for the specified underlying and expiry.

The verified teller source confirms that bond tokens are created through deploy(underlying_, expiry_), stored in the bondTokens mapping, and later minted during payout handling. That registry is the contract's canonical source of truth for bond-token identity.

The incident matters because the redeem path released real OHM held by the teller. At block 15794363, immediately before the exploit call, the teller held 30437077948152 raw OHM units, and the canonical registry for OHM at expiry 1337 was unset.

mapping(ERC20 => mapping(uint48 => ERC20BondToken)) public bondTokens;

function redeem(ERC20BondToken token_, uint256 amount_) external override nonReentrant {
    if (uint48(block.timestamp) < token_.expiry())
        revert Teller_TokenNotMatured(token_.expiry());
    token_.burn(msg.sender, amount_);
    token_.underlying().transfer(msg.sender, amount_);
}

The critical observation is that redeem never checks token_ == bondTokens[token_.underlying()][token_.expiry()].

3. Vulnerability Analysis & Root Cause Summary

This is an ATTACK-class ACT exploit caused by an authorization failure in redemption logic. The vulnerable function is interface-based instead of identity-based: it accepts any contract that responds to expiry(), burn(address,uint256), and underlying(). Because the maturity check relies entirely on token_.expiry(), a fake token can return any timestamp that is already in the past. Because the burn step relies entirely on token_.burn(...), a fake token can satisfy the call without destroying any real liability. Because the asset transfer target is chosen through token_.underlying(), a fake token can direct the teller to transfer OHM itself. The teller's real safety invariant should have been: only a matured, teller-issued bond token representing an extinguishable claim may unlock underlying. Instead, the code released inventory based on attacker-controlled interface responses.

4. Detailed Root Cause Analysis

The attacker first deployed helper contract 0xa29e4fe451ccfa5e7def35188919ad7077a4de8f from EOA 0x443cf223e209e5a2c08114a2501d8f0f9ec7d9be in tx 0x05f548db9215621c49d845482f1b804d82697711ef691dd77d2a796f3881bd02. The deployment bytecode hard-coded the teller address 0x007fe7c498a2cf30971ad8f2cbc36bd14ac51156, the OHM address 0x64aa3364f17a4d01c6f1751fd97c2bd3d7e7f1d5, and the deployer-owned control flow used later to trigger redemption and withdrawal.

In the exploit transaction 0x3ed75df83d907412af874b7998d911fdf990704da87c2b1a8cf95ca5d21504cf, the attacker-controlled contract invoked the teller. The collected trace shows the helper querying the teller's OHM balance and using the full amount 30437077948152 as the redeem amount. The same trace shows the teller calling back into the attacker helper to obtain the values it trusts.

The validator independently confirmed the vulnerable call sequence by executing the PoC on a mainnet fork. The validator run logged:

BondFixedExpiryTeller::redeem(fakeBondToken, 30437077948152)
  fakeBondToken::expiry() -> 1337
  fakeBondToken::burn(attacker, 30437077948152) -> return
  fakeBondToken::underlying() -> OHM
  OHM::transfer(attacker, 30437077948152)

Those calls are sufficient to explain the exploit. Since 1337 is already in the past at the exploit block, the maturity guard passes. Since burn is attacker-defined and can be a no-op, no legitimate claim is destroyed. Since underlying() returns OHM, the teller transfers real OHM inventory. The registry check that should have rejected the fake token never occurs.

The exploit predicate is therefore deterministic: if the teller holds OHM inventory and still lacks token identity validation, any unprivileged actor can deploy an interface-compatible helper, choose a past expiry with no registered bond token, and redeem the teller's balance.

5. Adversary Flow Analysis

The on-chain flow is a simple three-step sequence.

First, the attacker EOA 0x443cf223e209e5a2c08114a2501d8f0f9ec7d9be deployed helper contract 0xa29e4fe451ccfa5e7def35188919ad7077a4de8f. Second, that helper executed the unauthorized redemption against BondFixedExpiryTeller, causing the teller to transfer all 30437077948152 raw OHM units to the attacker-controlled path. Third, the attacker called selector 0xc402cd1a on the helper in tx 0xd38c92dc3de78ad282c2403a434db28fc47d3bbecdaece08c2bf91bd333c918e, which emitted an OHM Transfer from the helper contract to the attacker EOA for the same amount.

The withdrawal receipt proves realization of profit:

Transfer(
  from = 0xa29e4fe451ccfa5e7def35188919ad7077a4de8f,
  to   = 0x443cf223e209e5a2c08114a2501d8f0f9ec7d9be,
  value = 30437077948152
)

This is an ACT sequence because every step is permissionless: deploying the helper, calling the teller, and withdrawing from the attacker-owned helper require no privileged keys or protocol-specific authorization.

6. Impact & Losses

The teller lost its full OHM inventory at the time of the exploit. The measurable loss was:

  • OHM: 30437077948152 raw units with decimal = 9

That equals 30437.077948152 OHM. The affected victim component was BondFixedExpiryTeller at 0x007fe7c498a2cf30971ad8f2cbc36bd14ac51156, and the funds were transferred to attacker-controlled addresses without any legitimate teller-issued bond token being redeemed.

7. References

  • Verified source for BondFixedExpiryTeller at 0x007fe7c498a2cf30971ad8f2cbc36bd14ac51156
  • Seed exploit metadata for tx 0x3ed75df83d907412af874b7998d911fdf990704da87c2b1a8cf95ca5d21504cf
  • Seed execution trace for tx 0x3ed75df83d907412af874b7998d911fdf990704da87c2b1a8cf95ca5d21504cf
  • Attacker helper deployment tx 0x05f548db9215621c49d845482f1b804d82697711ef691dd77d2a796f3881bd02
  • Helper withdrawal tx 0xd38c92dc3de78ad282c2403a434db28fc47d3bbecdaece08c2bf91bd333c918e
  • Validator Forge execution log confirming the same exploit sequence on a mainnet fork