#34978 [SC-Low] protocol runs insolvent due to incorrect reliance on depositbalance which doesn t match holder balances
#34978 [SC-Low] Protocol runs insolvent due to incorrect reliance on depositBalance which doesn't match holder balances
Submitted on Sep 2nd 2024 at 06:28:51 UTC by @styphoiz for Audit Comp | Acre
Report ID: #34978
Report Type: Smart Contract
Report severity: Low
Target: https://sepolia.etherscan.io/address/0x7e184179b1F95A9ca398E6a16127f06b81Cb37a3
Impacts:
Protocol insolvency
Description
Brief/Intro
Currently, if all/sufficient holders of Acre Staked Bitcoin (stBTC) attempt to withdraw their tokens, the protocol may become insolvent due to an issue with the depositBalance function.
Vulnerability Details
This issue is logged separately from https://bugs.immunefi.com/dashboard/submission/34672, the flow is similar however this is a different function been called on the contract. The withdraw function in contract 0x7e184179b1F95A9ca398E6a16127f06b81Cb37a3 is designed to work with the balance of tBTC held within the contract. When this balance is insufficient to cover redemptions, the contract attempts to withdraw additional funds from 0xd5EbDD6fF384a465D56562D3a489c8CCE1B92dd0 using the depositBalance function. However, the issue arises when depositBalance returns a value lower than expected, leading to a shortfall of tBTC in the contract. As a result, even though there may be sufficient overall funds in the system, the reliance on depositBalance causes the contract to fail in meeting redemption requests, rendering the protocol insolvent.
Impact Details
Acre Staked Bitcoin (stBTC) holders attempting to withdraw their tokens after the value of depositBalance plus the tokens in 0x7e184179b1F95A9ca398E6a16127f06b81Cb37a3 falls below their redemption amount will be unable to redeem their tBTC tokens, potentially causing significant losses and undermining trust in the protocol.
References
Acre Staked Bitcoin (stBTC) - https://sepolia.etherscan.io/address/0x7e184179b1F95A9ca398E6a16127f06b81Cb37a3 Mezo Allocator - https://sepolia.etherscan.io/address/0xd5ebdd6ff384a465d56562d3a489c8cce1b92dd0 Mezo Portal - https://sepolia.etherscan.io/address/0x6978e3e11b8bc34ea836c1706fc742ac4cb6b0db
Proof of Concept
Proof of Concept
Steps are provided below on what I had done to build this code.
I had first went to https://sepolia.etherscan.io/token/0x7e184179b1F95A9ca398E6a16127f06b81Cb37a3#balances to get a list of holders of Acre Staked Bitcoin (stBTC)
``` const ownerAddresses = [ "0xCE06a2D105559C633451971ab1f843D667597265", "0x512f4f3a02862b0A7e7F1D796B885ce3D4EaB5cf", "0xB66ab5A4596250Ce20ff62262935CAB5E8A17695", "0x54567d825cE85a4E9C4314984CEe4D9253458B1B", "0x5476A06f08CD1F9669Ae6643C5eF9cc4F1848970", "0x16610D47659373cEE43F6983D46b02256c03F7C1", "0xD0B9584c57B6fFDeD640130232735388737dE251", "0x88744F5da4308317B459EFB205028AED77B1ae2C", "0x0763DfC2fb8b060e0629928B5D77466D1C4Ca379", // "0x21F071bd9Ed020fb6E3e9A661Ca547E94f713467", "0x6F1a421573082BE1BEAe22551259D4D793EfD2cE", "0x9B55cDe4d96aAa9CCCc4fC9Fd12Ab43292750294", "0x18361d831C81384fBd8c5BaCa1727cae64212B9d", "0x8b63f664eC49bA2AbCb24ACCC76E3Ee1522ddB9e", "0x8d951Dfe12e12ea4549e18382D7e4c9188046851", "0x247c356466D139Df16231E576eF52B1168528B6F", "0x18c3D37A85b4e44A5619d62Ee4900Bcc18b3bd5a", "0x719743739BD4E5154248705BF9bF67ac2D85b52F", "0x0483cD12aC9758e530dc184a1b542439BA6cDB8f", "0x82d930246C2e0F2a383d893E1F1DeB45CE602d1B", "0xA4761081d9Cb672d911d7df25E5a30D7925608CE", // "0xd2C6168Fd106908Df71Ab639f8b7e2F971Ab8205", "0x857173e7c7d76e051e80d30FCc3EA6A9C2b53756", "0x3df087df73576CA02f5f2D10ce95b00355482a51", "0x6e80164ea60673D64d5d6228beb684a1274Bb017", ]; ```
After getting this list, you can currently see that the value of tBTC at the time of writing this held at 0x7e184179b1F95A9ca398E6a16127f06b81Cb37a3 (referred to as stBTC) is 1387265314680000000 (1.38726531468), the total Supply of stBTC is 4452802992110739292 (4.452802992110739292), this means that the stBTC's contract's tBTC tokens should match the totalSupply which if there is not enough tBTC in the stBTC contract to align to the user's deposits.
When a withdraw() occurs
In the event that stBTC doesn't contain enough tBTC tokens, stBTC will go to dispatcher to withdraw funds to top itself up to allow the withdraw. We see that here in the withdraw function in stBTC after checking that it doesn't have sufficient tBTC tokens, it calls dispatcher.withdraw to top itself up.
The dispatcher at this time is 0xd5EbDD6fF384a465D56562D3a489c8CCE1B92dd0 (referred to as MezoAllocator).
When MezoAllocator does a withdrawal
We see within the code of MezoAllocator, we can see that MezoAllocator does calls to 0x6978E3e11b8Bc34ea836C1706fC742aC4Cb6b0Db (referred to as MezoPortal) We also see that MezoAllocator contains no tBTC.
``` function withdraw(uint256 amount) external { if (msg.sender != address(stbtc)) revert CallerNotStbtc();
``` Within the function we see that there are 2 types of withdraws been used to call MezoPortal. This process does a comparison of the amount against the depositBalance, at this time is currently 752030076580000000 (0.75203007658), we also see that upon a success withdrawal results in the depositBalance been lowered by the amount. This means that MezoAllocator's depositBalance value and the actual tBTC in the stBTC contract should be greater than the total Supply of tBTC to allow users to call redeem and withdraw successfully. Here we see that this is not the case
Total Supply stBTC: 4.452802992110739292 Balance of tBTC in stBTC : 1.38726531468 depositBalance value in MezoAllocator : 0.75203007658 With sufficient redeems to decrease the balance of tBTC in stBTC and then start decrease the depositBalance value in MezoAllocator, when we do a comparison here of these amounts, we see that (Total Supply stBTC) - (Balance of tBTC in stBTC) - (depositBalance value in MezoAllocator) now gives us an amount that is greater than depositBalance pushing us into the below code. ``` else { mezoPortal.withdraw(address(tbtc), depositId); } ```
MezoPortal withdraw explanation
We see that the function withdraw uses 2 parameters address(tbtc), depositId, let's look through the code here. ``` function withdraw(address token, uint256 depositId) external { TokenAbility ability = tokenAbility[token];
``` In this code, we see that the value used by the contract sits under deposits[msg.sender][token][depositId]; Adding in our values and interacting with the contract on Sepolia Etherscan, we see that the image (Acre_Deposit_509.png) has a balance of 752030076580000000 (0.75203007658) During the process of withdrawals, we had at this point run the stBTC contract and the depositBalance down, this means that the MezoPortal amount transferred will now be much less that what the user requested on the redeem. Back within the withdraw function on MezoAllocator, MezoAllocator has received whatever MezoPortal has provided which in the POC is now less than the depositBalance value. However mezoPortal decides to send the amount value provided by the user from the number of shares provided by the stBTC token holder on in the redeem function on the stBTC contract.
function withdraw(uint256 amount) external { ... depositBalance -= uint96(amount); ... } This will not work and breaks the contract as MezoPortal doesn't have sufficient tokens to honor the request and then fails. A maintainer can then intervene at this point by depositing tBTC into stBTC to continue the withdrawals, the protocol however remains at reputational risk of been seen as insolvent.
Full code for the POC as ran under npx hardhat test to simulate the details above. ``` const { expect } = require("chai"); const { ethers } = require("hardhat");
describe("Acre Bug Bounty", function () { it("Redeem runs out of funds?", async function () { this.timeout(300000);
}); }); ```