Lessons Learned from Exploit Incident: Postmortem.
By Nestor Coppi (Co-Founder 2Pi Network) and Franco Catena (Co-Founder 2Pi Network)
On Monday 16th of Jan, 2023, an attacker exploited one of our USDC vaults on top of Optimism “Curve” stable pool.
In total 70,758 USDC were stolen by the attacker.
When the development team was first notified of the attack, and after gathering more information and identifying the vulnerability, the decision was made to execute a protocol update which temporarily blocked all agreement invocations.
Incident & Response Timeline
8:53 pm GMT — members of the 2Pi team alerted a miss of funds on one of the Curve strategies
9:00 pm GMT — the exploited vault was identified, and the developers proceeded to immediately understand the impact. 2Pi team realised the impacted vault was used for own treasury funds and no customer funds was impacted
9:10 pm GMT — each Curve customer strategy was paused
9:30 pm GMT — each CEO partner was notified and a contingency and action plan was discussed between the parties
9:50 pm GMT — all strategies were paused
10:00 pm GMT — customers strategies were put to work under contracts via boosted vaults operating independently from any external protocol
10:28 pm GMT — the attacker was reached via blockchain message (0 transaction) & blockscan chat & comments in optimistic.etherscan with no further response
Simplicity was at the core of our smart contract design. We aimed to provide a seamless interaction with 2Pi contracts, therefore the deposit and withdrawal process was designed via single assets instead of LPs for the final protocol. The LP is built at the smart contract.
This brings user simplicity, however it adds a level of complexity at the smart contract level.
The smart contract which makes use of this “single-asset simplicity” always asks the underlying protocol “how much asset will it get” based on a withdrawal event. Unfortunately, this calculation can be “manipulated” by a pool imbalance. This is what the attacker took advantage of.
The attacker made the following operations:
- Ask for 6.75M of USDC, ~1.79M of DAI and ~831k of sUSD to AAVE using a flash loan
- Swapped ~5.85M of USDC for ~4.53M of sUSD using a Velodrome LP
- Swapped all remaining sUSD (~5.35M) for USDC using the sUSD Synthetix Curve pool as the strategy
- Swapped 1.79M of DAI for USDC using again the 3CRV Curve pool as the strategy. This basically drained the USDC of the pool (leaving about ~75k USDC on the 3CRV pool, originally having ~7M USDC) , making the “price” of USDC on this pool low for withdrawal
- Deposited ~900k USDC on our strategy. Since we use the Curve .calc_withdraw_one_coin to account for the USDC balance of the strategy and as the result was “poisoned” on the previous operations, the current balance was near 10.5k USDC (instead ~100k USDC) what results in a bunch of minted shares for the attacker:
// Strategy.sol balance
_beforeBalance = ICurvePool(pool).calc_withdraw_one_coin(metaPool, _amount, tokenIndex);
// Controller.sol shares logic
shares = (_deposit * totalSupply()) / _beforeBalance;
// shares = (900k * 100k / 10.5k) => 8.5M 2pi-shares
- Swapped back ~5.35M of USDC for sUSD using the same Curve pool as the strategy
- Swapped ~1.56M of USDC for DAI using again the same Curve pool
- At this moment the pool is “normalised”, then withdraws all shares from our protocol, which gives the attacker ~1.19M of USDC, at this point with the difference between deposit movement & withdrawal movement the strategy collected a 5% of total balances difference, giving back 13.8k USDC to 2Pi treasury
- Swap & pay flash-loans
The overall attack gave the executor a net gain of ~53k USDC and a total loss for 2Pi of ~71k USDC.
The team conducted an analysis based on blockchain events from the transaction attack.
Since all the funds hacked were part of the 2Pi treasury, and no customer was affected. We still have decided together with our customers to not jeopardise their users, and therefore contracts will run a dummy contract version via boost program together with our partners until all mitigation actions have been completed.
Meanwhile the next improvement will be done:
- Whitelist depositors on our main/entry-point contract
- Verify a minimum price per share prior deposits/withdrawals to prevent these “imbalance” scenarios
- Modify the shares calculation to use an unchangeable amount for the deposits in the underlying protocol
- Update the amounts of global and per user deposit limits closer to the real use of each customer vault
- Prior to the attack, 2Pi ran two different sets of audits. In an effort to strengthen security, we have reached out to many qualified audit firms to conduct a thorough audit again of all of our smart contracts which will include all necessary updates
We’ve also identified the original wallet which was used to fund the transaction:
Funded from: https://etherscan.io/address/0xe291cc3e5b9e0c9b37c9fbdd549abf3b5c0ad342 (this came out of a Binance hot wallet). We are in discussions with Binance to take the necessary legal actions on this matter.
The attack has been replicated as a test for future developments.
We’re also deeply grateful for the support of our customers. We contacted everyone of our partners immediately after the incident, and received overwhelming response and support. Thank you for working with us to find a path forward.
We will like to thank the team at https://www.ancilia.xyz/ who supported us while the incident.
We invite all white hackers to verify our contracts via https://immunefi.com/bounty/2pi/