Summary
The feeDataArray[tokenIdIndex].blockTimestampDeposit is being recorded incorrectly during NFT transfers.
Vulnerability Details
UpliftOnlyExample.sol#L576-L628
In the UpliftOnlyExample.sol contract, the afterUpdate() function is implemented as follows:
function afterUpdate(address _from, address _to, uint256 _tokenID) public {
if (msg.sender != address(lpNFT)) {
revert TransferUpdateNonNft(_from, _to, msg.sender, _tokenID);
}
address poolAddress = nftPool[_tokenID];
if (poolAddress == address(0)) {
revert TransferUpdateTokenIDInvaid(_from, _to, _tokenID);
}
int256[] memory prices = IUpdateWeightRunner(_updateWeightRunner).getData(poolAddress);
uint256 lpTokenDepositValueNow = getPoolLPTokenValue(prices, poolAddress, MULDIRECTION.MULDOWN);
FeeData[] storage feeDataArray = poolsFeeData[poolAddress][_from];
uint256 feeDataArrayLength = feeDataArray.length;
uint256 tokenIdIndex;
bool tokenIdIndexFound = false;
for (uint256 i; i < feeDataArrayLength; ++i) {
if (feeDataArray[i].tokenID == _tokenID) {
tokenIdIndex = i;
tokenIdIndexFound = true;
break;
}
}
if (tokenIdIndexFound) {
if (_to != address(0)) {
feeDataArray[tokenIdIndex].lpTokenDepositValue = lpTokenDepositValueNow;
-> feeDataArray[tokenIdIndex].blockTimestampDeposit = uint32(block.number);
feeDataArray[tokenIdIndex].upliftFeeBps = upliftFeeBps;
poolsFeeData[poolAddress][_to].push(feeDataArray[tokenIdIndex]);
if (tokenIdIndex != feeDataArrayLength - 1) {
for (uint i = tokenIdIndex + 1; i < feeDataArrayLength; i++) {
delete feeDataArray[i - 1];
feeDataArray[i - 1] = feeDataArray[i];
}
}
delete feeDataArray[feeDataArrayLength - 1];
feeDataArray.pop();
}
}
}
In this function, feeDataArray[tokenIdIndex].blockTimestampDeposit is set using block.number.
However, in the addLiquidityProportional() function, the blockTimestampDeposit is set using block.timestamp:
function addLiquidityProportional(
address pool,
uint256[] memory maxAmountsIn,
uint256 exactBptAmountOut,
bool wethIsEth,
bytes memory userData
) external payable saveSender(msg.sender) returns (uint256[] memory amountsIn) {
if (poolsFeeData[pool][msg.sender].length > 100) {
revert TooManyDeposits(pool, msg.sender);
}
amountsIn = _addLiquidityProportional(
pool,
msg.sender,
address(this),
maxAmountsIn,
exactBptAmountOut,
wethIsEth,
userData
);
uint256 tokenID = lpNFT.mint(msg.sender);
uint256 depositValue = getPoolLPTokenValue(
IUpdateWeightRunner(_updateWeightRunner).getData(pool),
pool,
MULDIRECTION.MULDOWN
);
poolsFeeData[pool][msg.sender].push(
FeeData({
tokenID: tokenID,
amount: exactBptAmountOut,
lpTokenDepositValue: depositValue,
-> blockTimestampDeposit: uint40(block.timestamp),
upliftFeeBps: upliftFeeBps
})
);
nftPool[tokenID] = pool;
}
This discrepancy can lead to incorrect blockTimestampDeposit values, potentially causing issues for users and the protocol.
Recommendations
To resolve this issue, modify the UpliftOnlyExample.sol#afterUpdate() function as follows:
function afterUpdate(address _from, address _to, uint256 _tokenID) public {
...........................
- feeDataArray[tokenIdIndex].blockTimestampDeposit = uint32(block.number);
+ feeDataArray[tokenIdIndex].blockTimestampDeposit = uint32(block.timestamp);