USDs Feb 3 Exploit Report from Engineering Team

Sperax Team
Sperax
Published in
4 min readFeb 6, 2023

On Feb 3, an attacker manipulated USDs balance on a multi-sig wallet and changed it to 9.7 billion by exploiting an internal bug in the USDs token contract. Before Sperax team paused the contract, the attacker exchanged ~309k USDs to assets including USDT, USDC and WETH.

Timeline Traceback

Feb-03–2023 11:25:59 PM +UTC

Feb-03–2023 11:26:54 PM +UTC

Feb-03–2023 11:53:33 PM +UTC

  • The attacker started to sell USDs on Arbitrum, mostly 10k at a time (with 0x4a..81 being the direct receiver in most of the transaction)

Feb-04–2023 02:59:24 AM +UTC

  • Sperax team paused USDs contract action. In total 309k USDs were sold.

USDs Token Contract Bug

Context

USDs is a rebasing token with two types of holders: rebasing and non-rebasing.

  • A rebasing USDs holder’s USDs balance will increase automatically upon a rebase (which is triggered weekly); whereas the non-rebasing holder’s USDs balance stays unchanged after a rebase.
  • By default, all EOAs are rebasing holders and all smart contracts are non-rebasing holders.

USDs token contract keeps track of balances by keeping track of each individual’s individual’s credit and creditsPerToken.

balance = credit / creditsPerToken

  • Every holder has its own credit
  • All rebasing holders share the same creditsPerToken
  • Every non-rebasing holders have its own creditsPerToken

When and how was the bug introduced

On Dec 14, Sperax upgraded USDs token contract to fix a precision issue in calculating balance of the accounts, which was causing incompatibility issues with DEXs.

This upgrade also simplified how USDs token contract tracks the balance of non-rebasing holders. It added a process to update all non-rebasing creditsPerToken to 1 (and update the credits accordingly). This eliminated the necessity of keeping track of every non-rebasing holders’ own creditsPerToken and tracking only the credit for the holder as its balance, hence treating USDs as a usual ERC20 contract for these holders.

But a bug is introduced here, in the _ensureRebasingMigration() function.

Bug Details

_ensureRebasingMigration() functions as a guard to ensure that the rebasing / non-rebasing accounting is correct in the following scenarios:

  1. Contract admin changes an account from rebasing to non-rebasing (or the other way around)
  2. When there is an USDs action (transfer, mint or burn) on a smart contract that has not been explicitly set to non-rebasing, the smart contract should be set to non-rebasing
  • a smart contract interacting with USDs for the very first time
  • a smart contract that has USDs balance as a rebasing holder (but was not set to rebasing by admin)

A bug in _ensureRebasingMigration() makes the function fail to work in scenario 2b. nonRebaseCreditsPerToken is set to 1 too early, such that the _balanceOf(_account) returned credits instead of credits / creditPerToken.

The attacker’s transaction constructed scenario (2b) by sending USDs to the address first then making it a Gnosis Safe (a smart contract). He then triggered a USDs transfer which triggered _ensureRebasingMigration() in which _balanceOf(_account) returns credits instead of credits / creditPerToken. The balance of the address jumped to 9.7b.

*The test cases covered all scenarios in 1 and 2a, but failed to cover edge case 2b.

Next Steps

Even though all the contracts that we develop go through multiple rounds of reviews and thorough testing, we still missed this edge case. We feel the attacker was just experimenting with the contract since the upgraded code is not published, however he/she did uncover a novel bug, it could have been an even worse situation (if it were planned).

We are truly sorry for that and all the inconvenience that we have caused. Unfortunately we cannot undo this, but we will fix the issue in hand and relook into our development process to ensure it is even more rigid and our smart contracts are robust. We are also reaching out to our partners for peer review.

  • Update the hacker account (Multisig) credits back to 0.
  • Update the other accounting errors induced by the exploit back to normal.
  • Fix the bug
  • Review the contract so as to ensure that all the accounting logic is correct.
  • Test the upgrade.
  • Refill the vault with collateral to match the inflated USDs supply.
  • Resume the USDs contract.

About Us

Sperax USD is a stablecoin and yield automator on Arbitrum. Hold $USDs and earn Auto-Yield. Stake $SPA to govern the collateral investment strategy.

Read more at sperax.io and join the Sperax community!

Twitter | Discord | Telegram| Telegram Korea| Medium

--

--