Reproduced Exploit
Remora Pledge: uint64 `calculatedPayout` overflows on high-decimal stablecoin payouts, permanently bricking claims
Chain
Other
Category
untagged
Date
Jul 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: 61173-distribution-of-payouts-will-revert-due-to-overflow-when-pay. The historical source/toolchain is unavailable; this entry is documentation only and claims no executable Forge PoC.
Vulnerability classes: integer-overflow, unsafe-downcast, denial-of-service
Reproduction: A faithful minimal reproduction.
DividendManager.payoutBalanceis reproduced VERBATIM (vulnerable line marked@>), deployed locally with no fork. A single$20distribution funded in an 18-decimal stablecoin is enough to make the holder'sSafeCast.toUint64cast revert, permanently DoSing every payout claim.
Root cause#
DividendManager accumulates each holder's owed dividend in a uint256 (payoutAmount) but stores it into a uint64 field, calculatedPayout, through a revert-on-overflow SafeCast.toUint64. Payouts were sized for a 6-decimal stablecoin (USDC), but the protocol can switch the payment stablecoin to one with 18 decimals (USDS). A $20 payout then becomes 20e18, which exceeds type(uint64).max (~1.844e19), so the cast reverts.
struct HolderStatus {
uint64 calculatedPayout; // @> BUG: uint64 cannot hold an 18-decimal payout
}
function payoutBalance(address holder) public returns (uint256) {
PayoutInfo memory pInfo = payout;
uint256 payoutAmount = (tokenBalance[holder] * pInfo.amount) / pInfo.totalSupply;
HolderStatus storage holderStatus_ = holderStatus[holder];
holderStatus_.calculatedPayout += SafeCast.toUint64(payoutAmount); // @> reverts: 20e18 > uint64.max
return payoutAmount;
}
Because payoutBalance walks every uncalculated distribution and folds them into the same calculatedPayout slot, one oversized entry reverts the whole loop — the holder cannot claim the offending payout or any earlier one still pending calculation. The DoS is irreversible: the stored decimals never shrink.
Why it's exploitable here#
- Trigger is ordinary protocol operation, not an attacker trick. Any admin switch to an 18-decimal stablecoin (a documented capability) turns a routine
$20distribution into a20e18value that overflowsuint64. - No guard on the cast.
SafeCast.toUint64is the only bound, and it reverts rather than saturating — there is no fallback path oncepayoutAmount > type(uint64).max. - Holders fund their own loss of access. The reverting call is the holder's own
payoutBalance; they are locked out of dividends they are already owed, including previously-accrued ones. - Systemic reach. Every holder with a pending high-decimal distribution hits the same revert — this is a protocol-wide claim freeze, not a single-account edge case.
Attack path#
Marked-line walkthrough (Playground)#
- Line 64 —
payout = PayoutInfo(amount, totalSupply)records a distribution funded in an 18-decimal stablecoin (amount = 20e18), the precondition the finding requires. - Line 69 — VULN: entry into
payoutBalance; the holder's pro-rata payout20e18(computed on line 70) is about to be stored viaSafeCast.toUint64(line 73), which reverts because20e18 > type(uint64).max— bricking this claim and every prior pending one.
PoC#
cd 61173-distribution-of-payouts-will-revert-due-to-overflow-when-p_exp
forge test -vv
The exploit test drives the verbatim DividendManager: the $20 (20e18) claim reverts inside SafeCast.toUint64, the catch branch fires, and the full 20000000000000000000 (20e18) blocked payout is recorded to the DoS probe — proving the holder is permanently frozen out. The fixed-variant control (FixedDividendManager, calculatedPayout widened to uint128) casts the same 20e18 without reverting, so the claim succeeds and no amount is blocked. Served at /hacks/61173-distribution-of-payouts-will-revert-due-to-overflow-when-p/.
Remediation#
Widen calculatedPayout to uint128 (which comfortably holds an 18-decimal payout) and, more robustly, normalize the internal accounting to a fixed number of decimals so it is independent of the configured stablecoin — the PaymentSettler converts to/from the stablecoin's actual decimals at the boundary.
struct HolderStatus {
- uint64 calculatedPayout;
+ uint128 calculatedPayout;
}
-holderStatus_.calculatedPayout += SafeCast.toUint64(payoutAmount);
+holderStatus_.calculatedPayout += SafeCast.toUint128(payoutAmount);
Fixed upstream in commits a0b277f and ced21ba.
References#
- AuditVault finding: https://github.com/Auditware/AuditVault/blob/main/findings/61173-distribution-of-payouts-will-revert-due-to-overflow-when-pay.md
- Cyfrin / Dacian report (Remora Pledge v2.0, 2025-07-04): https://github.com/solodit/solodit_content/blob/main/reports/Cyfrin/2025-07-04-cyfrin-remora-pledge-v2.0.md
- Vulnerable declaration (
calculatedPayoutasuint64): https://github.com/remora-projects/remora-smart-contracts/blob/audit/Dacian/contracts/RWAToken/DividendManager.sol#L42 - Overflowing
SafeCast.toUint64cast: https://github.com/remora-projects/remora-smart-contracts/blob/audit/Dacian/contracts/RWAToken/DividendManager.sol#L435-L440
Sources & further analysis#
Reproductions & code
- No executable Forge reproduction is claimed; the historical source/toolchain was unavailable for this finding.
- AuditVault finding: 61173-distribution-of-payouts-will-revert-due-to-overflow-when-pay.
- Upstream DeFiHackLabs PoC directory: src/test.
Alerts & third-party analyses
- DeFiHackLabs incident explorer: search "Remora Pledge: uint64
calculatedPayoutoverflows on high-decimal stablecoin payouts, permanently bricking claims". - Web3Sec X hacked database: search.
- Rekt leaderboard: search.
- Solodit incident search: search.
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.