F-2025-0008·precision-loss

Integer Division Truncation in LiquidStakingPool Leads to Loss of User Funds

Fixedliquid-stakinglststaking-poolsgithub.com/matchain/contracts
TL;DR

stake() computes stMATAmount = (amount * 1e18) / exchangeRate(). When exchangeRate is high relative to amount, the division truncates to zero so the contract takes the user's MAT but mints no stMAT in return.

Severity
MEDIUM
Impact
MEDIUM
Likelihood
MEDIUM
Method
MManual review
CAT.
Complexity
LOW
Exploitability
MEDIUM
02Section · Description

Description

The LiquidStakingPool contract contains an integer division truncation vulnerability in its staking mechanism. When users stake small amounts of MAT tokens while the exchange rate is high, the calculation of stMAT tokens can result in zero tokens due to solidity's integer division truncation. This causes users to lose their staked MAT tokens without receiving any stMAT tokens in return.

The issue occurs in the stake function when calculating how many stMAT tokens to mint:

solidity
uint256 stMATAmount = (amount * 1e18) / exchangeRate();

If exchangeRate() is significantly higher than amount, the division will result in a value less than 1, which gets truncated to 0 in integer division. The contract will then proceed to take the user's MAT tokens but mint 0 stMAT tokens to them.

As the protocol matures and accumulates rewards, the exchange rate naturally increases over time, making this vulnerability more likely to affect users with smaller stake amounts.

03Section · Impact

Impact

A test demonstrated the vulnerability:

  1. Established a high exchange rate (450:1) by simulating accumulated rewards
  2. A user attempted to stake 400 wei of MAT tokens
  3. The calculation (400 * 1e18) / 450e18 = 0 due to integer truncation
  4. The user's MAT tokens were transferred, but they received 0 stMAT tokens

The user pays MAT but receives nothing in return, a direct loss of funds proportional to the stake amount.

04Section · Recommendation

Recommendation

Implement a minimum stake amount check in the stake function:

solidity
function stake(uint256 amount) external {
if (amount == 0) revert InsufficientAmount();
uint256 currentRate = exchangeRate();
uint256 stMATAmount = (amount * 1e18) / currentRate;
// Add this check to prevent truncation loss
if (stMATAmount == 0) revert StakeTooSmall(amount, currentRate);
bool success = matToken.transferFrom(msg.sender, address(this), amount);
if (!success) revert TransferFailed();
stakingPool.stake(amount);
_mint(msg.sender, stMATAmount);
emit Staked(msg.sender, amount, stMATAmount);
}
F-2025-0008

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx