Boost _ Folks Finance 33684 - [Smart Contract - Critical] Lack of available liquidity check when sending token back from Hub leads to first deposit and inflation attack
Submitted on Fri Jul 26 2024 10:31:34 GMT-0400 (Atlantic Standard Time) by @nnez for Boost | Folks Finance
Report ID: #33684
Report type: Smart Contract
Report severity: Critical
Target: https://testnet.snowtrace.io/address/0xaE4C62510F4d930a5C8796dbfB8C4Bc7b9B62140
Impacts:
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Description
Description
When users perform borrow
or withdraw
operation, tokens are sent back via sendTokenToUser
in Hub
contract. The Hub
contract then decides on how it should be sent (on which adapters to use).
However, there is no validation whether the actual available liquidity is sufficient on the destination Spoke endpoint in any steps of the execution flow.
To better illustrate, consider this situation:
ALICE deposits 1_000 USDC from Spoke Chain
BOB borrows 1_000 USDC from Spoke Chain
As a result, the total balanceOf USDC on Spoke Chain is 0
.
depositData.totalAmount
is 1_000 and variableBorrowData.totalAmount
is 1_000, stored on Hub Chain.
However, due to the lack of checking on available liquidity on Spoke Chain.
EVE can proceed to call borrow
function from Spoke Chain, let's say EVE call borrow
with amount=1_000
.
Although the message being executed on Spoke Chain will fail, the state is already changed and written on Hub Chain. So, depositData.totalAmount
is 1_000 and variableBorrowData.totalAmount
is 1_500, stored on Hub Chain.
Also, note that EVE can retry the fail message to get her token back.
As a consequence, this breaks the utilisation ratio invariance (0 <= U <= 1), as it is evidenced from the above situation.
The utilisation ratio after EVE transaction would be 1500/1000=1.5
.
And if the utilisation ratio breaks, it in turn breaks the interest rate.
This opens up an attack vector for first deposit attack and inflation attack. Imagine this attack scenario when the pool is just deployed:
ALICE deposits 100 USDC to get some borrowing power
ALICE forced transfers 1 AVAX to SpokeGasToken endpoint.
ALICE borrows 1 AVAX
BOB deposits 1 wei of AVAX (BOB gets 1 wei of fAVAX)
The final state from these transactions would look like this:
fAVAX | |
deposit.totalAmount | 1 |
borrow.totalAmount | 1e18 |
Utilisation ratio would be 1e18/1=1e18
which is astronomically larger than maxiumum 1
This would in turn largely inflate deposit interest rate. Even within the next block on the Avalanche Chain, which has a block time of 2 seconds, the value of BOB's fAVAX in 1 wei will become extremely expensive from deposit interest, enabling him to borrow anything.
And due to the lack of avaialble liquidity validation, BOB can also preemptively borrow other tokens on Spoke Chain and subsequently withdraw them by calling retryMessage
upon a Spoke Chain new deposit.
Note that in this case BOB and ALICE are the same person
Impact
Attacker can leverage this vulnerability to drain all tokens in the pool.
Recommended Mitigations
Make use of
MathUtils#calcAvailableLiquidity
before sending token back.
Proof of concept
Proof-of-Concept
A test file in the secret gist demonstrates the attack scenario mentioned in Description
section.
The test is done on the old block (exactly after pools are set up) in order to accurately simulate first deposit attack.
Steps
Run
forge init --no-commit --no-git --vscode
.Create a new test file,
FolksBorrow-same.t.sol
intest
directory.Put the test from secret gist in the file: https://gist.github.com/nnez/615d62aea2e0137e03cb44c18646113e
Run
forge t --match-contract FolksBorrowSameTest -vv
Observe that BOB ends up with 100_000 USDC in his pocket
Expected result
Last updated