Reproduced Exploit
beraBTC mint and redeem fees are burned or never minted — the treasury stays at 0
custodianMint subtracts the fee from the amount it mints and does not mint the difference to anyone. redeem burns the full amount, then shrinks the reported payout by the fee, and does not keep that difference. A treasury address is never credited. On one mint and one redeem, 200 beraBTC of fees ar…
Chain
Other
Category
logic
Date
Apr 2025
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 55099-c-01-protocol-loses-mintredeem-fees-due-to-missing-treasury. Standalone Foundry PoC and full write-up: 55099-c-01-protocol-loses-mintredeem-fees-due-to-missing-treasury_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/fee-calculation · vuln/logic/state-update
Reproduction: self-contained Foundry PoC, no fork and no cheatcodes. Full trace: output.txt. Synthetic: test/55099-c-01-protocol-loses-mintredeem-fees-due-to-missing-treasury.sol.
Key info#
| Impact | HIGH — 200 beraBTC of fees never reach the treasury (100 burned, 100 never minted) |
| Protocol | Terplayer beraBTC |
| Vulnerable contract | BeraBitcoin.custodianMint / BeraBitcoin.redeem |
| Bug class | Fee is calculated and then dropped; no treasury credit |
| Finding | Shieldify — Terplayer beraBTC security review · C-01 · #55099 |
| Report | Terplayer-BeraBTC-Security-Review.md |
| Source | AuditVault finding |
| Audited code | batoshidao/bera-bitcoin @ 19d5099 BeraBitcoin.sol |
| Compiler | solc 0.8.27, Cancun, via IR |
Audit finding, not an on-chain incident. BASE_RATE is 10_000 and both fee rates are 1_000, so each fee on a 1,000 beraBTC action is exactly 100 beraBTC. The report's formulas are unchanged.
TL;DR#
custodianMint subtracts the fee from the amount it mints and does not mint the difference to anyone. redeem burns the full amount, then shrinks the reported payout by the fee, and does not keep that difference. A treasury address is never credited. On one mint and one redeem, 200 beraBTC of fees are lost and the treasury balance is 0. The control forwards each fee, and the treasury ends at 200 beraBTC.
The vulnerable code#
function custodianMint(address to, uint256 value) public {
if (mintFeeRate > 0) {
// @> VULN: mint fee is removed from the minted amount and never forwarded to a treasury
value -= (value * mintFeeRate) / BASE_RATE;
}
_mint(to, value);
emit CustodianMinted(to, value);
}
function redeem(uint256 value, string memory recipient) public {
uint256 burned = value;
// @> VULN: the full redeem amount, fee included, is burned and nothing is forwarded to a treasury
_burn(msg.sender, burned);
if (redeemFeeRate > 0) {
value -= (burned * redeemFeeRate) / BASE_RATE;
}
emit Redeemed(msg.sender, burned, value, recipient);
}
onlyRole and nonReentrant are omitted. They do not move the fee.
Root cause#
The fee is a local subtraction. Mint destroys the fee by never creating it. Redeem destroys it by burning the gross amount before the subtraction. There is no treasury balance update on either path, which is what the report asks for.
Preconditions#
mintFeeRateandredeemFeeRateare non-zero. Here both are 10% (1_000 / 10_000).- A user holds 1,000 beraBTC to redeem.
- A custodian calls
custodianMintfor another 1,000 gross.
Attack walkthrough#
- Seed the user with 1,000 beraBTC.
redeem(1000)._burndestroys all 1,000. The net payout stored afterwards is 900. The 100 fee was inside the burn. Treasury balance is still 0.custodianMint(1000). The fee line reducesvalueto 900 and_mintcreates only 900. Another 100 never exists. Treasury balance is still 0. The token contract's own balance is 0, so the fee is not sitting there either.- output.txt:
HARM redeem fee burned: 100000000000000000000,HARM mint fee never minted: 100000000000000000000,HARM treasury balance: 0.
Control#
BeraBitcoinFixed keeps the same rates. On mint it mints the fee to treasury. On redeem it transfers the fee to treasury and burns only the net.
output.txt: CONTROL treasury balance: 200000000000000000000. The treasury holds both 100 beraBTC fees.
Diagrams#
Impact#
Every mint and every redeem at a non-zero fee rate drops protocol revenue. In this reduction that is 200 beraBTC: 100 burned, 100 never minted, treasury balance 0. The report rates the missing collection critical.
Remediation#
Keep a treasury address. On mint, mint the fee to it. On redeem, transfer the fee to it and burn only the net the user is redeeming. That is what the control does.
How to reproduce#
cd 55099-c-01-protocol-loses-mintredeem-fees-due-to-missing-treasury_exp
forge test -vv
Sources#
- AuditVault finding 55099
- Shieldify Terplayer beraBTC review — published 2025-04-03
- bera-bitcoin @ 19d509911de64a062dfaeef33f14e26f08b23c10 —
custodianMint,redeem - Team response in the report: Fixed
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 55099-c-01-protocol-loses-mintredeem-fees-due-to-missing-treasury_exp (evm-hack-registry mirror).
- AuditVault finding: 55099-c-01-protocol-loses-mintredeem-fees-due-to-missing-treasury.
Alerts & third-party analyses
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.