Platform: Code4rena
Start Date: 21/08/2023
Pot Size: $125,000 USDC
Total HM: 26
Participants: 189
Period: 16 days
Judge: GalloDaSballo
Total Solo HM: 3
Id: 278
League: ETH
Rank: 55/189
Findings: 5
Award: $208.46
π Selected for report: 0
π Solo Findings: 0
π Selected for report: klau5
Also found by: 0x3b, 0xCiphky, 0xDING99YA, 0xWaitress, 0xbranded, 0xc0ffEE, 0xklh, 0xsurena, 0xvj, ABA, AkshaySrivastav, Anirruth, Aymen0909, Baki, Blockian, BugzyVonBuggernaut, DanielArmstrong, Evo, GangsOfBrahmin, HChang26, Inspex, Jiamin, Juntao, Kow, Krace, KrisApostolov, LFGSecurity, LokiThe5th, Mike_Bello90, Norah, Nyx, QiuhaoLi, RED-LOTUS-REACH, SBSecurity, Snow24, SpicyMeatball, T1MOH, Tendency, Toshii, Udsen, Yanchuan, __141345__, ak1, asui, auditsea, ayden, bart1e, bin2chen, blutorque, carrotsmuggler, chaduke, chainsnake, circlelooper, clash, codegpt, crunch, degensec, dirk_y, ge6a, gjaldon, grearlake, jasonxiale, juancito, ke1caM, kodyvim, kutugu, ladboy233, lanrebayode77, mahdikarimi, max10afternoon, mert_eren, nirlin, nobody2018, oakcobalt, parsely, peakbolt, pks_, pontifex, ravikiranweb3, rokinot, rvierdiiev, said, savi0ur, sces60107, sh1v, sl1, spidy730, tapir, tnquanghuy0512, ubermensch, visualbits, volodya, wintermute
0.0098 USDC - $0.01
https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/perp-vault/PerpetualAtlanticVault.sol#L359-L361 https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L199-L205
The PerpetualAtlanticVault.settle
function does call PerpetualAtlanticVaultLP.subtractLoss
function to account for the funds sent to RdpxV2Core
. But due to the fact that the subtractLoss
function requires strict equality between the contract collateral balance collateral.balanceOf(address(this))
and the actual collateral amount, a malicious user could transfer collateral tokens to the PerpetualAtlanticVaultLP
contract to mess up the token balance (make it bigger than the actual contract accounting) and to make sure that subtractLoss
will always revert causing a DOS of settle
function.
The issue occurs in the settle
function below :
function settle( uint256[] memory optionIds ) external nonReentrant onlyRole(RDPXV2CORE_ROLE) returns (uint256 ethAmount, uint256 rdpxAmount) { ... // Transfer collateral token from perpetual vault to rdpx rdpxV2Core collateralToken.safeTransferFrom( addresses.perpetualAtlanticVaultLP, addresses.rdpxV2Core, ethAmount ); // Transfer rdpx from rdpx rdpxV2Core to perpetual vault IERC20WithBurn(addresses.rdpx).safeTransferFrom( addresses.rdpxV2Core, addresses.perpetualAtlanticVaultLP, rdpxAmount ); // @audit will call subtractLoss IPerpetualAtlanticVaultLP(addresses.perpetualAtlanticVaultLP) .subtractLoss(ethAmount); IPerpetualAtlanticVaultLP(addresses.perpetualAtlanticVaultLP) .unlockLiquidity(ethAmount); IPerpetualAtlanticVaultLP(addresses.perpetualAtlanticVaultLP).addRdpx( rdpxAmount ); emit Settle(ethAmount, rdpxAmount, optionIds); }
As you can see after transferring ethAmount
from PerpetualAtlanticVaultLP
to RdpxV2Core
the function will call subtractLoss
to update the value of _totalCollateral
in PerpetualAtlanticVaultLP
.
The subtractLoss
function is as follow :
function subtractLoss(uint256 loss) public onlyPerpVault { // @audit collateral.balanceOf(address(this)) can be changed by anyone require( collateral.balanceOf(address(this)) == _totalCollateral - loss, "Not enough collateral was sent out" ); _totalCollateral -= loss; }
The function uses a check statement that requires a strict equality between the contract collateral balance collateral.balanceOf(address(this))
and the actual collateral amount subtracted _totalCollateral - loss
.
The problem here is that any address can change the value of collateral.balanceOf(address(this))
by sending a certain amount of WETH token to the PerpetualAtlanticVaultLP
contract (simple ERC20 transfer which can be done by any user, even a very small amount) and thus any malicious user can use this trick (keep transferring small collateral funds to the contract) to make sure that the subtractLoss
function always reverts resulting in the DOS of the settle
function which will have a big negative impact on the protocol as the function is called by the admin to settle certain options which will not be possible.
Manual review
To solve this issue i recommend to use the operator >=
in the subtractLoss
function to avoid the DOS even if a malicious user changes collateral.balanceOf(address(this))
:
function subtractLoss(uint256 loss) public onlyPerpVault { require( collateral.balanceOf(address(this)) >= _totalCollateral - loss, "Not enough collateral was sent out" ); _totalCollateral -= loss; }
DoS
#0 - c4-pre-sort
2023-09-09T09:54:30Z
bytes032 marked the issue as duplicate of #619
#1 - c4-pre-sort
2023-09-11T16:14:21Z
bytes032 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-21T07:16:09Z
GalloDaSballo marked the issue as satisfactory
π Selected for report: bart1e
Also found by: 0x3b, 0xCiphky, Aymen0909, HHK, Inspex, bin2chen, chaduke, gkrastenov, jasonxiale, josephdara, kodyvim, peakbolt, pep7siup, rokinot, rvierdiiev, tapir
181.367 USDC - $181.37
https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/amo/UniV3LiquidityAmo.sol#L324-L336
In the UniV3LiquidityAMO
contract there is a recoverERC721
function that allows the admin to recover the uni v3 NonfungiblePosition ERC721 tokens but the function will transfer the token to the RdpxV2Core
contract instead of the admin as intended, because the RdpxV2Core
contract has no way to interact with the ERC721 token and has no function to transfer it back to the admin this will lead the NonfungiblePosition ERC721 to be stuck forever in the RdpxV2Core
contract and can never be transferred again.
The issue occurs in the recoverERC721
function below :
function recoverERC721( address tokenAddress, uint256 token_id ) external onlyRole(DEFAULT_ADMIN_ROLE) { // Only the owner address can ever receive the recovery withdrawal // INonfungiblePositionManager inherits IERC721 so the latter does not need to be imported // @audit ERC721 sent to rdpxV2Core not to admin => will be stuck in rdpxV2Core for ever INonfungiblePositionManager(tokenAddress).safeTransferFrom( address(this), rdpxV2Core, token_id ); emit RecoveredERC721(tokenAddress, token_id); }
As we can see the recoverERC721
function does transfer the ERC721 token to the rdpxV2Core
address (RdpxV2Core
contract) even if the function comments states that "only the owner address can ever receive the recovery withdrawal".
By looking at the RdpxV2Core
contract code we can easily notice that there is no way for the contract to interact or transfer ERC721 tokens (no call to ERC721.transferFrom/ERC721.safeTransferFrom), so any NonfungiblePosition ERC721 recovered will be stuck there and the admin will never get it back.
Manual review
Transfer the recovered NonfungiblePosition ERC721 to the admin (which is msg.sender
) :
function recoverERC721( address tokenAddress, uint256 token_id ) external onlyRole(DEFAULT_ADMIN_ROLE) { // Only the owner address can ever receive the recovery withdrawal // INonfungiblePositionManager inherits IERC721 so the latter does not need to be imported // @audit Send token to admin == msg.sender INonfungiblePositionManager(tokenAddress).safeTransferFrom( address(this), msg.sender, token_id ); emit RecoveredERC721(tokenAddress, token_id); }
Token-Transfer
#0 - c4-pre-sort
2023-09-09T06:42:29Z
bytes032 marked the issue as duplicate of #106
#1 - c4-pre-sort
2023-09-12T06:10:09Z
bytes032 marked the issue as sufficient quality report
#2 - c4-pre-sort
2023-09-12T06:12:23Z
bytes032 marked the issue as duplicate of #935
#3 - c4-judge
2023-10-20T18:05:28Z
GalloDaSballo changed the severity to 3 (High Risk)
#4 - c4-judge
2023-10-20T18:05:37Z
GalloDaSballo marked the issue as satisfactory
π Selected for report: 0xrafaelnicolau
Also found by: 0x111, 0xCiphky, 0xMosh, 0xWaitress, 0xc0ffEE, 0xkazim, 0xnev, 0xvj, ABAIKUNANBAEV, Aymen0909, Baki, ElCid, HChang26, HHK, Inspex, Jorgect, Kow, Krace, KrisApostolov, LFGSecurity, MiniGlome, Nyx, QiuhaoLi, RED-LOTUS-REACH, Talfao, Toshii, Vagner, Viktor_Cortess, Yanchuan, _eperezok, asui, atrixs6, bart1e, bin2chen, carrotsmuggler, chaduke, chainsnake, deadrxsezzz, degensec, dethera, dimulski, dirk_y, ether_sky, gizzy, glcanvas, grearlake, gumgumzum, halden, hals, kodyvim, koo, ladboy233, lanrebayode77, max10afternoon, minhtrng, mussucal, nobody2018, peakbolt, pontifex, qbs, ravikiranweb3, rvierdiiev, said, tapir, ubermensch, volodya, wintermute, yashar, zaevlad, zzebra83
0.0734 USDC - $0.07
https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L975-L990
The withdraw
function within the RdpxV2Core
contract permits delegates to retrieve unutilized WETH that they have previously supplied. However, although this function correctly transfers the designated amount to the user, it fails to decrement the totalWethDelegated
balance. This discrepancy could lead to issues with the contract's functionalities, as it would appear to have more delegated funds than it actually possesses resulting in massive accounting error.
The issue occurs in the withdraw
function below :
function withdraw( uint256 delegateId ) external returns (uint256 amountWithdrawn) { _whenNotPaused(); _validate(delegateId < delegates.length, 14); Delegate storage delegate = delegates[delegateId]; _validate(delegate.owner == msg.sender, 9); amountWithdrawn = delegate.amount - delegate.activeCollateral; _validate(amountWithdrawn > 0, 15); delegate.amount = delegate.activeCollateral; // @audit Did not decrease totalWethDelegated amount IERC20WithBurn(weth).safeTransfer(msg.sender, amountWithdrawn); emit LogDelegateWithdraw(delegateId, amountWithdrawn); }
As you can see the function calculate the amount that can be withdrawn amountWithdrawn
by subtracting the active collateral delegate.activeCollateral
from the user total delegated amount delegate.amount
, the function then updates the value of delegate.amount
and transfer the WETH amount to the user.
But the function did not decrease the totalWethDelegated
balance which is supposed to account for all the WETH delegated to RdpxV2Core
, and even this balance is increased when a user add delegated amount it is not decreased here when he is withdrawing, this will result is bad WETH balance accounting as the contract would appear to have more delegated funds than it actually possesses which will affect the protocol whenever totalWethDelegated
is used.
For example if we take a look as the sync()
function it has the following lines of code :
if (weth == reserveAsset[i].tokenAddress) { balance = balance - totalWethDelegated; } reserveAsset[i].tokenBalance = balance;
Because of the issue that as explained above, totalWethDelegated
will be wrong and thus the contract WETH asset balance will be update incorrectly and will never reflect the real contract balance.
Manual review
Update the totalWethDelegated
balance in the withdraw
function :
function withdraw( uint256 delegateId ) external returns (uint256 amountWithdrawn) { _whenNotPaused(); _validate(delegateId < delegates.length, 14); Delegate storage delegate = delegates[delegateId]; _validate(delegate.owner == msg.sender, 9); amountWithdrawn = delegate.amount - delegate.activeCollateral; _validate(amountWithdrawn > 0, 15); delegate.amount = delegate.activeCollateral; // @audit subtract withdrawn amount from total weth delegated totalWethDelegated -= amountWithdrawn; IERC20WithBurn(weth).safeTransfer(msg.sender, amountWithdrawn); emit LogDelegateWithdraw(delegateId, amountWithdrawn); }
Error
#0 - c4-pre-sort
2023-09-07T07:43:54Z
bytes032 marked the issue as duplicate of #2186
#1 - c4-pre-sort
2023-09-07T07:44:00Z
bytes032 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-20T17:53:12Z
GalloDaSballo marked the issue as satisfactory
#3 - c4-judge
2023-10-20T17:55:32Z
GalloDaSballo changed the severity to 2 (Med Risk)
#4 - c4-judge
2023-10-21T07:38:54Z
GalloDaSballo changed the severity to 3 (High Risk)
#5 - c4-judge
2023-10-21T07:42:41Z
GalloDaSballo marked the issue as partial-50
π Selected for report: Vagner
Also found by: 0Kage, 0xCiphky, 0xnev, ABAIKUNANBAEV, Aymen0909, Evo, KmanOfficial, MohammedRizwan, T1MOH, Viktor_Cortess, Yanchuan, ak1, alexzoid, bin2chen, codegpt, hals, ladboy233, mrudenko, nemveer, oakcobalt, peakbolt, pep7siup, qbs, said, savi0ur, tapir, wintermute, zaevlad, zzebra83
7.8372 USDC - $7.84
Judge has assessed an item in Issue #1553 as 2 risk. The relevant finding follows:
3- Funds not transferred to correct recipient in RdpxDecayingBonds.emergencyWithdraw : Risk : Low The function RdpxDecayingBonds.emergencyWithdraw is used by the admin to pull funds from the contract in case of emergency situation, the function allows the admin to specify a recipient to address as input to the function, this address should receive all the extracted funds (as explained in function Natspec comments). But due to an error in function code only the native token is transferred to to address and the other tokens are transferred to admin address.
This issue has a Low impact because there is no loss of funds and the tokens are transferred to admin which can later give them to the to address but it should be corrected.
Proof of Concept File: decaying-bonds/RdpxDecayingBonds.sol Line 89-107
function emergencyWithdraw( address[] calldata tokens, bool transferNative, address payable to, // @audit should be the recipient uint256 amount, uint256 gas ) external onlyRole(DEFAULT_ADMIN_ROLE) { _whenPaused(); if (transferNative) { (bool success, ) = to.call{value: amount, gas: gas}(""); require(success, "RdpxReserve: transfer failed"); } IERC20WithBurn token;
for (uint256 i = 0; i < tokens.length; i++) { token = IERC20WithBurn(tokens[i]); // @audit funds not transferred to `to` address token.safeTransfer(msg.sender, token.balanceOf(address(this))); }
} Mitigation Transfer all the funds to the correct recipient address to.
#0 - c4-judge
2023-10-24T07:00:43Z
GalloDaSballo marked the issue as duplicate of #250
#1 - GalloDaSballo
2023-10-24T07:01:10Z
Pasted the wrong description, the issue is: 2 Missing sync() after transferring funds to RdpxV2Core Low 2 M
#2 - c4-judge
2023-10-24T07:01:21Z
GalloDaSballo marked the issue as satisfactory
#3 - GalloDaSballo
2023-10-24T07:01:41Z
2- Missing sync() after transferring funds to RdpxV2Core : Risk : Low In the RdpxV2Core contract there a sync() function which allows the update of the state variables tracking the tokens balances (ETH, DPX,...) by setting them equal to the actual contract token balance (with the use of token.balanceOf(address(this))).
The issue is that there are some instances where this function must be called after transferring funds to RdpxV2Core but it is not which could result in a temporarily incorrect balance accounting.
Proof of Concept There 2 instances of this issue :
File: amo/UniV2LiquidityAmo.sol Line 160-178
function _sendTokensToRdpxV2Core() internal { uint256 tokenABalance = IERC20WithBurn(addresses.tokenA).balanceOf( address(this) ); uint256 tokenBBalance = IERC20WithBurn(addresses.tokenB).balanceOf( address(this) ); // transfer token A and B from this contract to the rdpxV2Core IERC20WithBurn(addresses.tokenA).safeTransfer( addresses.rdpxV2Core, tokenABalance ); IERC20WithBurn(addresses.tokenB).safeTransfer( addresses.rdpxV2Core, tokenBBalance );
// @audit did not call IRdpxV2Core(rdpxV2Core).sync() emit LogAssetsTransfered(msg.sender, tokenABalance, tokenBBalance);
} The _sendTokensToRdpxV2Core() function is used many times in the UniV2LiquidityAmo to transfer tokens to RdpxV2Core but it doesn't call the sync() function after doing so, we can see that a similar function is present in the other AMO contract UniV3LiquidityAmo._sendTokensToRdpxV2Core and this one does call the sync() function after the transfers.
File: amo/UniV3LiquidityAmo.sol Line 119-133
function collectFees() external onlyRole(DEFAULT_ADMIN_ROLE) { for (uint i = 0; i < positions_array.length; i++) { Position memory current_position = positions_array[i]; INonfungiblePositionManager.CollectParams memory collect_params = INonfungiblePositionManager.CollectParams( current_position.token_id, rdpxV2Core, type(uint128).max, type(uint128).max );
// Send to custodian address univ3_positions.collect(collect_params); }
} In the second instance the call to univ3_positions.collect will transfer tokens to the RdpxV2Core contract but after the loop there no call to the sync() function to update the tokens accounting in RdpxV2Core.
Mitigation Add the IRdpxV2Core(rdpxV2Core).sync() call to the instances aforementioned to make sure that the accounting is always correct after a transfer from the AMOs contracts.
π Selected for report: juancito
Also found by: 0xDING99YA, 0xTiwa, 0xkazim, 0xnev, ABA, ArmedGoose, Aymen0909, Bauchibred, Evo, IceBear, KrisApostolov, MohammedRizwan, Nikki, QiuhaoLi, T1MOH, Toshii, WoolCentaur, Yanchuan, __141345__, asui, bart1e, carrotsmuggler, catellatech, chaduke, codegpt, deadrxsezzz, degensec, dethera, dirk_y, erebus, ether_sky, gjaldon, glcanvas, jasonxiale, josephdara, klau5, kodyvim, ladboy233, lsaudit, minhquanym, parsely, peakbolt, pep7siup, rvierdiiev, said, savi0ur, sces60107, tapir, ubermensch, volodya, zzebra83
19.1724 USDC - $19.17
Issue | Risk | Instances | |
---|---|---|---|
1 | Potential underflow in RdpxV2Core.calculateBondCost will cause DOS of bond operations | Low | 1 |
2 | Missing sync() after transferring funds to RdpxV2Core | Low | 2 |
3 | Funds not transferred to correct recipient in RdpxDecayingBonds.emergencyWithdraw | Low | 1 |
4 | UniV2LiquidityAMO.approveContractToSpend can't approve USDT transfer | Low | 1 |
5 | PerpetualAtlanticVaultLP can never pull funds from PerpetualAtlanticVault | Low | |
6 | MANAGER_ROLE not used in PerpetualAtlanticVault | Low | |
7 | underlyingSymbol not set in PerpetualAtlanticVaultLP | NC | |
8 | Unused state variable collateralPrecision in the PerpetualAtlanticVault contract | NC |
RdpxV2Core.calculateBondCost
will cause DOS of bond operations :The function RdpxV2Core.calculateBondCost
is used to calculate the amount of weth and rdpx tokens required to create a bond, in the case where _rdpxBondId == 0
the function has to calculate the value of bondDiscount
and it validate that its value is less than 100e8
.
The issue is that in the following lines of code there is this substraction operation :
rdpxRequired = ((RDPX_RATIO_PERCENTAGE - (bondDiscount / 2)) * _amount * DEFAULT_PRECISION) / (DEFAULT_PRECISION * rdpxPrice * 1e2);
where RDPX_RATIO_PERCENTAGE = 25 * DEFAULT_PRECISION = 25e8
As you can see if we have bondDiscount / 2 > RDPX_RATIO_PERCENTAGE
the operation will revert due to an underflow, and because the check present in the function only ensures that bondDiscount < 100e8
, it means that there are many valid values for bondDiscount
that will make the function revert. For example if we take bondDiscount = 60e8
(which is valid as it's less than 100e8) when the subtraction op goes on we will get :
RDPX_RATIO_PERCENTAGE - (bondDiscount / 2) = 25e8 - (60e8 / 2) = 25e8 - 30e8 < 0
And thus the operation will underflow causing the call to revert, and we can easily find that any value for bondDiscount
greater than 50e8
will make the function revert.
When the calculateBondCost
function revert because of the mentioned issue it will cause all the bonding functions (bond
, bondWithDelegate
) to revert hence blocking all the bonding operations (DOS) when _rdpxBondId == 0
.
I submit this issue as Low risk for the protocol because we can see that the admin can change the value of bondDiscountFactor
at any time to avoid this problem, but it could be a valid medium and the devs should take a look at it as if they are assuming in their logic that all values of bondDiscount
less than 100e8
are possible (and hence the check : _validate(bondDiscount < 100e8, 14)
) it will cause the protocol problems.
Because we can see that bondDiscount
depends on the RDPX treasury reserve which will continue to grow meaning that the admin must be monitoring at each time if the value of bondDiscount
is getting bigger than 50e8
in order to change the bondDiscountFactor
to avoid the DOS of bonding operation (revert of the call due to underflow), this is not really an ideal scenario.
File: core/RdpxV2Core.sol Line 1156-1199
function calculateBondCost( uint256 _amount, uint256 _rdpxBondId ) public view returns (uint256 rdpxRequired, uint256 wethRequired) { uint256 rdpxPrice = getRdpxPrice(); if (_rdpxBondId == 0) { uint256 bondDiscount = (bondDiscountFactor * Math.sqrt(IRdpxReserve(addresses.rdpxReserve).rdpxReserve()) * 1e2) / (Math.sqrt(1e18)); // 1e8 precision // @audit allow values of bondDiscount < 100e8 _validate(bondDiscount < 100e8, 14); // @audit can underflow if bondDiscount > 50e8 rdpxRequired = ((RDPX_RATIO_PERCENTAGE - (bondDiscount / 2)) * _amount * DEFAULT_PRECISION) / (DEFAULT_PRECISION * rdpxPrice * 1e2); wethRequired = ((ETH_RATIO_PERCENTAGE - (bondDiscount / 2)) * _amount) / (DEFAULT_PRECISION * 1e2); } else { ... }
There are many options the solve this issue but the simpler is to modify the check on the amount bondDiscount
and make it always less tha 50e8
: _validate(bondDiscount < 50e8, 14)
sync()
after transferring funds to RdpxV2Core
:In the RdpxV2Core
contract there a sync()
function which allows the update of the state variables tracking the tokens balances (ETH, DPX,...) by setting them equal to the actual contract token balance (with the use of token.balanceOf(address(this))
).
The issue is that there are some instances where this function must be called after transferring funds to RdpxV2Core
but it is not which could result in a temporarily incorrect balance accounting.
There 2 instances of this issue :
File: amo/UniV2LiquidityAmo.sol Line 160-178
function _sendTokensToRdpxV2Core() internal { uint256 tokenABalance = IERC20WithBurn(addresses.tokenA).balanceOf( address(this) ); uint256 tokenBBalance = IERC20WithBurn(addresses.tokenB).balanceOf( address(this) ); // transfer token A and B from this contract to the rdpxV2Core IERC20WithBurn(addresses.tokenA).safeTransfer( addresses.rdpxV2Core, tokenABalance ); IERC20WithBurn(addresses.tokenB).safeTransfer( addresses.rdpxV2Core, tokenBBalance ); // @audit did not call IRdpxV2Core(rdpxV2Core).sync() emit LogAssetsTransfered(msg.sender, tokenABalance, tokenBBalance); }
The _sendTokensToRdpxV2Core()
function is used many times in the UniV2LiquidityAmo
to transfer tokens to RdpxV2Core
but it doesn't call the sync()
function after doing so, we can see that a similar function is present in the other AMO contract UniV3LiquidityAmo._sendTokensToRdpxV2Core and this one does call the sync()
function after the transfers.
File: amo/UniV3LiquidityAmo.sol Line 119-133
function collectFees() external onlyRole(DEFAULT_ADMIN_ROLE) { for (uint i = 0; i < positions_array.length; i++) { Position memory current_position = positions_array[i]; INonfungiblePositionManager.CollectParams memory collect_params = INonfungiblePositionManager.CollectParams( current_position.token_id, rdpxV2Core, type(uint128).max, type(uint128).max ); // Send to custodian address univ3_positions.collect(collect_params); } }
In the second instance the call to univ3_positions.collect
will transfer tokens to the RdpxV2Core
contract but after the loop there no call to the sync()
function to update the tokens accounting in RdpxV2Core
.
Add the IRdpxV2Core(rdpxV2Core).sync()
call to the instances aforementioned to make sure that the accounting is always correct after a transfer from the AMOs contracts.
RdpxDecayingBonds.emergencyWithdraw
:The function RdpxDecayingBonds.emergencyWithdraw
is used by the admin to pull funds from the contract in case of emergency situation, the function allows the admin to specify a recipient to
address as input to the function, this address should receive all the extracted funds (as explained in function Natspec comments). But due to an error in function code only the native token is transferred to to
address and the other tokens are transferred to admin address.
This issue has a Low impact because there is no loss of funds and the tokens are transferred to admin which can later give them to the to
address but it should be corrected.
File: decaying-bonds/RdpxDecayingBonds.sol Line 89-107
function emergencyWithdraw( address[] calldata tokens, bool transferNative, address payable to, // @audit should be the recipient uint256 amount, uint256 gas ) external onlyRole(DEFAULT_ADMIN_ROLE) { _whenPaused(); if (transferNative) { (bool success, ) = to.call{value: amount, gas: gas}(""); require(success, "RdpxReserve: transfer failed"); } IERC20WithBurn token; for (uint256 i = 0; i < tokens.length; i++) { token = IERC20WithBurn(tokens[i]); // @audit funds not transferred to `to` address token.safeTransfer(msg.sender, token.balanceOf(address(this))); } }
Transfer all the funds to the correct recipient address to
.
UniV2LiquidityAMO.approveContractToSpend
can't approve USDT transfer :In the UniV2LiquidityAMO
contract, the function approveContractToSpend
is used to approve other contracts to transfer funds outside UniV2LiquidityAMO
, for doing so the function uses the approve
function and there is a check ensuring that the approved amount is always greater than zero, this won't allow the approval of USDT token transfers as in that case it must first reset the approval amount to zero before setting a new one.
File: amo/UniV2LiquidityAmo.sol Line 126-135
function approveContractToSpend( address _token, address _spender, uint256 _amount ) external onlyRole(DEFAULT_ADMIN_ROLE) { require(_token != address(0), "reLPContract: token cannot be 0"); require(_spender != address(0), "reLPContract: spender cannot be 0"); // @audit reverts if amount == 0 require(_amount > 0, "reLPContract: amount must be greater than 0"); // @audit rcan not be used for USDT IERC20WithBurn(_token).approve(_spender, _amount); }
Use the safeApprove
function to allow USDT tokens approval, the contract should implement a function similair to UniV3LiquidityAMO.approveTarget
.
PerpetualAtlanticVaultLP
can never pull funds from PerpetualAtlanticVault
:The PerpetualAtlanticVaultLP
contract is approved to pull collateral tokens from PerpetualAtlanticVault
and there is even a function setLpAllowance
that can be called by the admin to increase/decrease the allowance approved for PerpetualAtlanticVaultLP
, but by looking at the PerpetualAtlanticVaultLP
contract code we can notice that there no call to pull funds from PerpetualAtlanticVault
which means that PerpetualAtlanticVaultLP
can never take the funds that are approved from PerpetualAtlanticVault
.
This does not make sense to approve a contract to spend funds but in its code there no way to do so, this could be a critical issue and should be looked at, if the PerpetualAtlanticVaultLP
does need to pull funds from PerpetualAtlanticVault
then the whole contract code should be reviewed to enable it (in the other case there no need to have setLpAllowance
function or to approve PerpetualAtlanticVaultLP
).
MANAGER_ROLE
not used in PerpetualAtlanticVault
:The MANAGER_ROLE
is set in the constructor of the PerpetualAtlanticVault
contract but this role is never used inside the contract for any access control task, If this role is not intended to be used in the contract then it should be removed, but if it was supposed to have a certain power for accessing some functions inside the contract then the code should be reviewed to add include this role.
underlyingSymbol
not set in PerpetualAtlanticVaultLP
:The underlyingSymbol
variable is not set in the constructor of PerpetualAtlanticVaultLP
, there is a string variable that is computed from the concatenation of the collateral token symbol and the suffix "-LP" but this is assigned to the inherited ERC20 symbol variable and underlyingSymbol
will remain empty as there is no way to set it after deployment.
collateralPrecision
in the PerpetualAtlanticVault contract :The value of collateralPrecision
is set in the constructor of the PerpetualAtlanticVault contract but its value is never used afterwards in any parts of the contract, if this variable is not intended to be used in the contract then it should be removed, but if it was supposed to used in some calculation in a certain function (as far as i saw it has no use as only 18 decimals tokens are intended to be used) then the code should be reviewed to add this features.
#0 - c4-pre-sort
2023-09-10T11:45:23Z
bytes032 marked the issue as sufficient quality report
#1 - GalloDaSballo
2023-10-10T11:21:18Z
1 Potential underflow in RdpxV2Core.calculateBondCost will cause DOS of bond operations Low 1 L
2 Missing sync() after transferring funds to RdpxV2Core Low 2 M
3 Funds not transferred to correct recipient in RdpxDecayingBonds.emergencyWithdraw Low 1 L
4 UniV2LiquidityAMO.approveContractToSpend canβt approve USDT transfer Low 1 OOS
5 PerpetualAtlanticVaultLP can never pull funds from PerpetualAtlanticVault Low L
6 MANAGER_ROLE not used in PerpetualAtlanticVault Low R
7 underlyingSymbol not set in PerpetualAtlanticVaultLP NC R
8 Unused state variable collateralPrecision in the PerpetualAtlanticVault contract NC R
3L 3R
#2 - c4-judge
2023-10-20T10:21:46Z
GalloDaSballo marked the issue as grade-b