Light ModeLight
Light ModeDark

One Bug Per Day

One H/M every day from top Wardens

Checkmark

Join over 1015 wardens!

Checkmark

Receive the email at any hour!

Ad

User can grief bootstrap process by sending the cap amount of unlocked tokens to it.

mediumCode4rena

Lines of code

https://github.com/code-423n4/2024-03-abracadabra-money/blob/main/src/blast/BlastOnboarding.sol#L101-L121

Vulnerability details

Impact

The bootstrap process relies on users locking up all their tokens as it will only use tokens that are marked locked in totals[MIM].locked and totals[USDB].locked.

BlastOnboardingBoot.sol#L96-L127

solidity
function bootstrap(uint256 minAmountOut) external onlyOwner onlyState(State.Closed) returns (address, address, uint256) { if (pool != address(0)) { revert ErrAlreadyBootstrapped(); } => uint256 baseAmount = totals[MIM].locked; => uint256 quoteAmount = totals[USDB].locked; MIM.safeApprove(address(router), type(uint256).max); USDB.safeApprove(address(router), type(uint256).max); (pool, totalPoolShares) = router.createPool(MIM, USDB, FEE_RATE, I, K, address(this), baseAmount, quoteAmount); if (totalPoolShares < minAmountOut) { revert ErrInsufficientAmountOut(); } // Create staking contract // 3x boosting for locker, 7 days reward duration, 13 weeks lp locking // make this contract temporary the owner the set it as an operator // for permissionned `stakeFor` during the claiming process and then // transfer the ownership to the onboarding owner. staking = new LockingMultiRewards(pool, 30_000, 7 days, 13 weeks, address(this)); staking.setOperator(address(this), true); staking.transferOwnership(owner); // Approve staking contract pool.safeApprove(address(staking), totalPoolShares); emit LogLiquidityBootstrapped(pool, address(staking), totalPoolShares); return (pool, address(staking), totalPoolShares); }

However, it is possible that these values can be zero because there is a cap on the amount of tokens that can be stored in the contract.

BlastOnboarding.sol#L101-L121

solidity
function deposit(address token, uint256 amount, bool lock_) external whenNotPaused onlyState(State.Opened) onlySupportedTokens(token) { token.safeTransferFrom(msg.sender, address(this), amount); if (lock_) { totals[token].locked += amount; balances[msg.sender][token].locked += amount; } else { totals[token].unlocked += amount; balances[msg.sender][token].unlocked += amount; } totals[token].total += amount; if (caps[token] > 0 && totals[token].total > caps[token]) { revert ErrCapReached(); } balances[msg.sender][token].total += amount; emit LogDeposit(msg.sender, token, amount, lock_); }

In the code above, notice that there is a cap on the amount of tokens that can be sent to the contract caps[token]. The problem here is that it is checked against the total token amount totals[token].total rather than the locked amount totals[token].locked.

Therefore a griefer can deposit tokens up to the caps[token] with lock_ = false. The result, is that no one else can deposit tokens into the contract.

During bootstrap, since these tokens are still considered unlocked, then totals[MIM].locked = 0 and totals[USDB].locked = 0, therefore there won't be any locked tokens available for the bootstrapping process.

Tools Used

Manual Review

Recommended Mitigation Steps

Instead of checking caps[token] against totals[token].total, it should be checked against totals[token].locked.

Assessed type

Other