Summary
The compound() function is designed to deposit Long tokens, Short tokens, or airdropped ARB tokens to the GMX for compounding. However, it will only work if there is ARB token in the trove. If there are only Long/Short tokens in the trove without any ARB, the function will not work.
Vulnerability Details
The compound()
function is intended to be called by the keeper once a day to deposit all the Long/Short or ARB tokens to the GMX for further compounding. However, the logic for depositing to the GMX is restricted by the condition that the trove must always hold an airdropped ARB token.
Here is the relevant code snippet from the GitHub repository:
if (_tokenInAmt > 0) {
self.refundee = payable(msg.sender);
self.compoundCache.compoundParams = cp;
ISwap.SwapParams memory _sp;
_sp.tokenIn = cp.tokenIn;
_sp.tokenOut = cp.tokenOut;
_sp.amountIn = _tokenInAmt;
_sp.amountOut = 0;
_sp.slippage = self.minSlippage;
_sp.deadline = cp.deadline;
GMXManager.swapExactTokensForTokens(self, _sp);
GMXTypes.AddLiquidityParams memory _alp;
_alp.tokenAAmt = self.tokenA.balanceOf(address(this));
_alp.tokenBAmt = self.tokenB.balanceOf(address(this));
self.compoundCache.depositValue = GMXReader.convertToUsdValue(
self,
address(self.tokenA),
self.tokenA.balanceOf(address(this))
)
+ GMXReader.convertToUsdValue(
self,
address(self.tokenB),
self.tokenB.balanceOf(address(this))
);
GMXChecks.beforeCompoundChecks(self);
self.status = GMXTypes.Status.Compound;
_alp.minMarketTokenAmt = GMXManager.calcMinMarketSlippageAmt(
self,
self.compoundCache.depositValue,
cp.slippage
);
_alp.executionFee = cp.executionFee;
self.compoundCache.depositKey = GMXManager.addLiquidity(
self,
_alp
);
}
The code checks if there is a positive _tokenInAmt
(representing ARB tokens) and proceeds with the depositing and compounding logic. However, if there is no ARB token but only tokenA and tokenB in the trove, the compounding will not occur and the tokens will remain in the compoundGMX contract indefinitely.
It is important to note that the airdrop of ARB tokens is a rare event, making it less likely for this condition to be met. Therefore, if there are no ARB tokens but a significant amount of tokenA and tokenB in the trove, the compounding will not take place.
Impact
If the compounding doesn’t happen this could lead to the indirect loss of funds to the user and loss of gas for the keeper who always calls this function just to transfer tokens and check the balance of ARB.
Tools Used
manual review
Recommendations
To mitigate this issue, it is important to always check if either tokenA/tokenB or ARB is present in the trove. If either of these is present, then proceed with the compound action. Otherwise, return.
if (_tokenInAmt > 0 || self.tokenA.balanceOf(address(this) > 0 || self.tokenB.balanceOf(address(this)) ) {
self.refundee = payable(msg.sender);
self.compoundCache.compoundParams = cp;
ISwap.SwapParams memory _sp;
_sp.tokenIn = cp.tokenIn;
_sp.tokenOut = cp.tokenOut;
_sp.amountIn = _tokenInAmt;
_sp.amountOut = 0;
_sp.slippage = self.minSlippage;
_sp.deadline = cp.deadline;
GMXManager.swapExactTokensForTokens(self, _sp);
GMXTypes.AddLiquidityParams memory _alp;
_alp.tokenAAmt = self.tokenA.balanceOf(address(this));
_alp.tokenBAmt = self.tokenB.balanceOf(address(this));
self.compoundCache.depositValue = GMXReader.convertToUsdValue(
self,
address(self.tokenA),
self.tokenA.balanceOf(address(this))
)
+ GMXReader.convertToUsdValue(
self,
address(self.tokenB),
self.tokenB.balanceOf(address(this))
);
GMXChecks.beforeCompoundChecks(self);
self.status = GMXTypes.Status.Compound;
_alp.minMarketTokenAmt = GMXManager.calcMinMarketSlippageAmt(
self,
self.compoundCache.depositValue,
cp.slippage
);
_alp.executionFee = cp.executionFee;
self.compoundCache.depositKey = GMXManager.addLiquidity(
self,
_alp
);
}