The contract fails to properly account for multiple token outputs during position decreases, particularly when GMX decrease position swaps fail. This leads to incorrect withdrawal amounts and corrupted accounting states.
contract TokenBalanceAccountingTest is Test {
PerpetualVault public vault;
MockERC20 public tokenA;
MockERC20 public tokenB;
function setUp() public {
vault = new PerpetualVault();
tokenA = new MockERC20("Token A", "TKNA");
tokenB = new MockERC20("Token B", "TKNB");
tokenA.mint(address(vault), 1000e18);
tokenB.mint(address(vault), 1000e18);
}
function testMultiTokenAccountingFailure() public {
address user = address(0x1);
vm.prank(user);
vault.deposit(tokenA, 100e18);
bytes32 positionId = vault.createPosition({
token: address(tokenA),
size: 50e18,
isLong: true
});
vm.mockCall(
address(gmx),
abi.encodeWithSelector(IGmx.decreasePosition.selector),
abi.encode(
tokenA, 25e18,
tokenB, 25e18
)
);
vm.prank(user);
vault.closePosition(positionId);
assertEq(vault.userBalance(user, tokenA), 75e18);
assertEq(vault.userBalance(user, tokenB), 0);
}
}
contract PerpetualVault {
struct TokenReturn {
address token;
uint256 amount;
}
function _handlePositionClose(bytes32 positionId) internal returns (TokenReturn[] memory) {
Position storage position = positions[positionId];
TokenReturn[] memory returns = new TokenReturn[](2);
(address token1, uint256 amount1) = _closePositionPrimary(position);
returns[0] = TokenReturn(token1, amount1);
(address token2, uint256 amount2) = _closePositionSecondary(position);
if (amount2 > 0) {
returns[1] = TokenReturn(token2, amount2);
}
for (uint256 i = 0; i < returns.length; i++) {
if (returns[i].amount > 0) {
_updateUserBalance(
position.owner,
returns[i].token,
returns[i].amount
);
}
}
return returns;
}
}