Open howlbot-integration[bot] opened 2 months ago
The Warden and its duplicates have demonstrated how the raise limitation per user can be effectively bypassed by transferring the NFT that the raise amounts are attached with to a different user, permitting one to circumvent the check and deposit as many funds as they wish.
Normally, a QA (L) severity rating would be assigned if the function was permissionless due to the ability of a user to use a secondary account to participate anyway. However, coupled with the fact that a whitelist may be enforced for raising operations, the impact of this submission has been properly assessed as medium-risk.
A subset of this duplicate set has been awarded a 75% reward due to describing an incorrect alleviation, such as imposing a whitelist on the NFT transfers. This is insufficient as a whitelisted user would be able to collude with another whitelisted user and transfer all NFTs to them (or even acquire whitelist access twice).
alex-ppg marked the issue as selected for report
alex-ppg marked the issue as satisfactory
confirmed this issue
Lines of code
https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOPool.sol#L143 https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOPool.sol#L151
Vulnerability details
Description
The
ILOPool
smart contract enables investors to acquire a locked liquidity position represented as an NFT. When an investor invokes thebuy()
function, they transfer a specified amount ofRAISE TOKENS
into the pool, thereby opening a position and receiving anILOPool NFT token
that signifies their ownership. The protocol enforces certain invariants related to the minimum and maximum amounts ofRAISE TOKENS
required for the sale. Furthermore, there is a restriction on the maximum number of tokens each investor can contribute per sale, defined bymaxCapPerUser
.Each subsequent call to
buy()
is intended to increase the investor's raised amount for their position, ensuring that the user's total raised amount does not surpass the sale'smaxCapPerUser
. However, this restriction can be circumvented by transferring an existingILOPool NFT token
to another account and invokingbuy()
again. This action results in the protocol minting a new NFT (thus creating a new position) for the investor. Consequently, themaxCapPerUser
check applies to the new position's raised amount, rather than the total amount contributed by the investor.Impact
This vulnerability allows an investor to:
maxCapPerUser
constraint by transferring their NFT to another account and purchasing additional tokens, thus minting new NFTs and opening new positions, which in turn breaks a core invariant.hardCap
.Proof of Concept
The described issue is illustrated in the below test, which can be added to the current test suit in
ILOPool.t.sol
file and run withforge test --mt testBypassUserCap
. I've added some helper-getter functions to be able to properly log what is happening in the workflow. I have used the defined set up parameters provided with the protocol's test suite.Tools Used
Manual review
Recommended Mitigation Steps
Implement an internal tracking mechanism to specify if the investor has bought an NFT, instead of using
balanceOf(recipient) == 0
. Another thing would be to implement a tracking mechanism to aggregate the total raised amount by an individual investor across all their positions.Assessed type
Token-Transfer