Summary
The UpliftOnlyExample contract does not emit any event for the poolFeeData update in the important functions. The oversight could make difficulties for off-chain systems and tools to communicate with the contract.
Vulnerability Details
see the code snippet below:
UpliftOnlyExample::addLiquidityProportional:
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;
@>
}
UpliftOnlyExample::onAfterRemoveLiquidity:
function onAfterRemoveLiquidity(
address router,
address pool,
RemoveLiquidityKind,
uint256 bptAmountIn,
uint256[] memory,
uint256[] memory amountsOutRaw,
uint256[] memory,
bytes memory userData
) public override onlySelfRouter(router) returns (bool, uint256[] memory hookAdjustedAmountsOutRaw) {
@> FeeData[] storage feeDataArray = poolsFeeData[pool][userAddress];
localData.feeDataArrayLength = feeDataArray.length;
localData.amountLeft = bptAmountIn;
for (uint256 i = localData.feeDataArrayLength - 1; i >= 0; --i) {
.
.
...
if (feeDataArray[i].amount <= localData.amountLeft) {
uint256 depositAmount = feeDataArray[i].amount;
localData.feeAmount += (depositAmount * feePerLP);
localData.amountLeft -= feeDataArray[i].amount;
lpNFT.burn(feeDataArray[i].tokenID);
@> delete feeDataArray[i];
@> feeDataArray.pop();
if (localData.amountLeft == 0) {
break;
}
} else {
@> feeDataArray[i].amount -= localData.amountLeft;
localData.feeAmount += (feePerLP * localData.amountLeft);
break;
}
}
.
.
...
return (true, hookAdjustedAmountsOutRaw);
}
UpliftOnlyExample::afterUpdate:
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 (uint256 i = tokenIdIndex + 1; i < feeDataArrayLength; i++) {
@> delete feeDataArray[i - 1];
@> feeDataArray[i - 1] = feeDataArray[i];
}
}
@> delete feeDataArray[feeDataArrayLength - 1];
@> feeDataArray.pop();
}
}
}
Impact
The contract does not emit any event for the poolFeeData update in the important functions.
Off-Chain systems and tools will face difficulties to communicate with the contract.
Tools Used
Manual Review
Recommendations
Please emit an event for the poolFeeData update in the important functions. This will help off-chain systems and tools to communicate with the contract.