Description

The vulnerability stems from an intricate implementation of a RebaseToken mechanism in the Abracadabra protocol:

  • The protocol used a RebaseToken with two key components:

    • Elastic value: Represents the actual assets in debt
    • Base value: Represents total borrowed shares
  • The User borrow mapping only stored borrowed shares per user, not the actual assets

  • The design appeared to intentionally allow the protocol (or anyone) to modify the assets owed by users by changing the elastic value of the RebaseToken

  • The repayForAll() function made this feature accessible to everyone

Attack Mechanism:

  1. A critical rounding error allowed the attacker to inflate the base value without correspondingly adjusting the elastic value
  2. This rounding error only manifests when elastic and base values significantly deviate from each other
  3. The repayForAll() function was instrumental in creating this deviation

Exploitation Steps:

  1. Inflate the base value (getting to elastic = 1, base = infinite)
  2. Repay the outstanding assets (elastic = 0)
  3. Leverage the first depositor problem, when elastic = 0, shares minted will be base = elastic
  4. Borrow all assets in the protocol, because of first depositor problem, shares minted will be 1:1.
  5. The attacker now has all assets in the protocol, but only owes a negligble share ratio to all outstanding shares.

Vulnerability Details:

  • The solvency check _isSolvent() relied on comparing the attacker’s ratio of total borrowed shares
  • The solvency check implicitly assumed elastic and base values would remain closely aligned
  • With base set to infinite, the attacker’s debt ratio becomes negligible

Proposed Solution

Assuming the original implementation was well-intentioned but flawed, a simple invariant check could have prevented the attack:

function assertionNoDebtSharesIfNoDebt() public view {
    if (elastic == 0) {
        require(base == 0, "No debt shares if no debt");
    }
}

This assertion ensures that when no assets are owed (elastic = 0), there should be no debt shares (base = 0), thereby preventing the exploit.