USDs Feb 3 Exploit Report from Engineering Team
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…
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
The attacker pre-computed the address of the next to-be-created Gnosis Safe contract (0x5c978df5f8af72298fe1c2c8c2c05476a10f253911) and sent 11 USDs to this address from address 0x4afcd19bb978eaf4f993814298504ed285df1181. https://arbiscan.io/tx/0xe74641b4b7e9c9eb7ab46082f322efbc510b8d39af609d934f41c41d7057fe49
Feb-03–2023 11:26:54 PM +UTC
The attacker created a Gnosis Safe at address 0x5c..39 (with one signer 0x4a..81) and sent out the 11 USDs from this address in the same transaction. https://arbiscan.io/tx/0xfaf84cabc3e1b0cf1ff1738dace1b2810f42d98baeea17b146ae032f0bdf82d5
(The attacker used this contract to pre-compute the address and deploy a multi-sig https://arbiscan.io/address/0xfeedb6f87ab4b05dc6ab6ef75547c22235e7b642#code)
This token transfer action triggered a bug in USDs token contract and the balance of address 0x5c..39 jumped to 9797854205567803995021828646 (9.79 billion)
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:
Contract admin changes an account from rebasing to non-rebasing (or the other way around)
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!