DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: low
Invalid

Ambiguous event emission parameter in Minted event

Summary

The Minted event emits the ambiguous amount under certain conditions

Vulnerability Details

The PerpetualVault::_mint function will emit the Minted event with amount of indexToken or collateralToken. However, the emitted event not tracked the amount is indexToken or collateralToken which may lead to confusion and affect the traceability.

function _mint(uint256 depositId, uint256 amount, bool refundFee, MarketPrices memory prices) internal {
uint256 _shares;
if (totalShares == 0) {
_shares = depositInfo[depositId].amount * 1e8;
} else {
uint256 totalAmountBefore;
if (positionIsClosed == false && _isLongOneLeverage(beenLong)) {
totalAmountBefore = IERC20(indexToken).balanceOf(address(this)) - amount;
} else {
totalAmountBefore = _totalAmount(prices) - amount;
}
if (totalAmountBefore == 0) totalAmountBefore = 1;
_shares = amount * totalShares / totalAmountBefore;
}
depositInfo[depositId].shares = _shares;
totalShares = totalShares + _shares;
if (refundFee) {
uint256 usedFee = callbackGasLimit * tx.gasprice;
if (depositInfo[counter].executionFee > usedFee) {
try IGmxProxy(gmxProxy).refundExecutionFee(depositInfo[counter].owner, depositInfo[counter].executionFee - usedFee) {} catch {}
}
}
@> emit Minted(depositId, depositInfo[depositId].owner, _shares, amount);
}

Impact

The emitted Minted event with ambiguous amount may lead to confusion and affect the traceability.

PoC

This PoC shown the issue with the Minted event across different position statuses: even when users deposit the same amount, the amount may be emitted in different tokens.

  1. Copy the following test case to the test/PerpetualVault.t.sol file

  2. Run tests with forge test --mt test_Minted_Event_With_Ambiguous_Amount --rpc-url arbitrum

function test_Minted_Event_With_Ambiguous_Amount() external {
address keeper = PerpetualVault(vault).keeper();
address alice = makeAddr("alice");
// alice's deposit when position is closed
IERC20 collateralToken = PerpetualVault(vault).collateralToken();
uint256 executionFeeGasLimit = PerpetualVault(vault).getExecutionGasLimit(true);
uint256 executionFee = executionFeeGasLimit * tx.gasprice;
deal(alice, executionFee);
deal(address(collateralToken), alice, 1e10);
vm.startPrank(alice);
collateralToken.approve(vault, 1e10);
vm.expectEmit();
emit PerpetualVault.Minted(1, alice, 1000000000000000000, 10000000000); // amount is emitted in term of collateral token
PerpetualVault(vault).deposit{value: executionFee}(1e10); // alice deposit 1e10 collateral token
vm.stopPrank();
MarketPrices memory prices = mockData.getMarketPrices();
bytes memory paraSwapData = mockData.getParaSwapData(vault);
bytes[] memory swapData = new bytes[](1);
swapData[0] = abi.encode(PROTOCOL.DEX, paraSwapData);
vm.prank(keeper);
PerpetualVault(vault).run(true, true, prices, swapData);
executionFeeGasLimit = PerpetualVault(vault).getExecutionGasLimit(true);
executionFee = executionFeeGasLimit * tx.gasprice;
// alice's deposit when position is opened
deal(alice, executionFee);
deal(address(collateralToken), alice, 1e10);
vm.startPrank(alice);
collateralToken.approve(vault, 1e10);
PerpetualVault(vault).deposit{value: executionFee}(1e10); // alice deposit 1e10 collateral token
vm.stopPrank();
vm.prank(keeper);
vm.expectEmit();
emit PerpetualVault.Minted(2, alice, 1000000000000000000, 2916781326862221805); // amount is emitted in term of index token even the deposit amount is the same
PerpetualVault(vault).runNextAction(prices, swapData);
}

Tools Used

Manual Review

Recommendations

Adding a new parameter to the Minted event to indicate the amount is in term of collateral token or index token. And then update the PerpetualVault::_mint function to emit the Minted event with the new parameter.

- event Minted(uint256 depositId, address depositor, uint256 shareAmount, uint256 depositAmount);
+ event Minted(uint256 depositId, address depositor, uint256 shareAmount, uint256 depositAmount, bool isIndexToken);
function _mint(uint256 depositId, uint256 amount, bool refundFee, MarketPrices memory prices, bool isCollateralToken) internal {
uint256 _shares;
if (totalShares == 0) {
_shares = depositInfo[depositId].amount * 1e8;
} else {
uint256 totalAmountBefore;
if (positionIsClosed == false && _isLongOneLeverage(beenLong)) {
totalAmountBefore = IERC20(indexToken).balanceOf(address(this)) - amount;
} else {
totalAmountBefore = _totalAmount(prices) - amount;
}
if (totalAmountBefore == 0) totalAmountBefore = 1;
_shares = amount * totalShares / totalAmountBefore;
}
depositInfo[depositId].shares = _shares;
totalShares = totalShares + _shares;
if (refundFee) {
uint256 usedFee = callbackGasLimit * tx.gasprice;
if (depositInfo[counter].executionFee > usedFee) {
try IGmxProxy(gmxProxy).refundExecutionFee(depositInfo[counter].owner, depositInfo[counter].executionFee - usedFee) {} catch {}
}
}
- emit Minted(depositId, depositInfo[depositId].owner, _shares, amount);
+ emit Minted(depositId, depositInfo[depositId].owner, _shares, amount, positionIsClosed == false && _isLongOneLeverage(beenLong));
}
Updates

Lead Judging Commences

n0kto Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

n0kto Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.