Dopex - LowK's results

A rebate system for option writers in the Dopex Protocol.

General Information

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

Dopex

Findings Distribution

Researcher Performance

Rank: 33/189

Findings: 1

Award: $638.64

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: LowK

Also found by: HHK, Inspecktor, ItsNio, Tendency, ak1

Labels

bug
2 (Med Risk)
primary issue
selected for report
sponsor confirmed
sufficient quality report
M-17

Awards

638.6354 USDC - $638.64

External Links

Lines of code

https://github.com/code-423n4/2023-08-dopex/blob/main/contracts/core/RdpxV2Core.sol#L1016

Vulnerability details

Impact

When the admin pauses the contract RdpxV3core locks anyone to use this contract such as a bond, bondWithDelegate, and withdraw. But a function redeem is still available to use. Any situations that occur like a hack accident or system are under maintenance then anyway, your smart contract is running to continue to get unexpected.

Proof of Concept

The code below does not call a function whenNotPause() to check whether this contract is paused or not.

function redeem( uint256 id, address to ) external returns (uint256 receiptTokenAmount) { // Validate bond ID _validate(bonds[id].timestamp > 0, 6); // Validate if bond has matured _validate(block.timestamp > bonds[id].maturity, 7); _validate( msg.sender == RdpxV2Bond(addresses.receiptTokenBonds).ownerOf(id), 9 ); // Burn the bond token // Note requires an approval of the bond token to this contract RdpxV2Bond(addresses.receiptTokenBonds).burn(id); // transfer receipt tokens to user receiptTokenAmount = bonds[id].amount; IERC20WithBurn(addresses.rdpxV2ReceiptToken).safeTransfer( to, receiptTokenAmount ); emit LogRedeem(to, receiptTokenAmount); }

To reproduce this vulnerability, let's modify a test called testRedeem() in a /test/rdpxV2-core/Unit.t.sol. After running the test, you will get everything passed. So it is proof of vulnerable

function testRedeem() public { rdpxV2Core.bond(1 * 1e18, 0, address(this)); // ... rdpxV2Core.pause(); // <- add this line to test // test redeem after expiry rdpxV2Bond.approve(address(rdpxV2Core), 0); rdpxV2Core.redeem(0, address(1)); assertEq(rdpxV2Bond.balanceOf(address(this)), 0); assertEq(rdpxV2ReceiptToken.balanceOf(address(1)), 25 * 1e16); // ... }

Tools Used

Manually

Adding the line _whenNotPaused(); into the function redeem().

Assessed type

Invalid Validation

#0 - c4-pre-sort

2023-09-10T07:02:35Z

bytes032 marked the issue as primary issue

#1 - bytes032

2023-09-10T07:02:43Z

I assuming this might be the intended behavior. Leaving for sponsor review.

#2 - c4-pre-sort

2023-09-10T07:03:11Z

bytes032 marked the issue as sufficient quality report

#3 - c4-sponsor

2023-09-25T16:44:47Z

psytama (sponsor) confirmed

#4 - c4-judge

2023-10-15T18:04:59Z

GalloDaSballo marked the issue as selected for report

#5 - 141345

2023-10-22T15:32:07Z

When a contract is paused, it means something goes wrong. redeem() function is a way for users to exit, otherwise the fund is locked. Why isn't this intended behavior?

#6 - GalloDaSballo

2023-10-25T09:13:23Z

https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/core/RdpxV2Core.sol#L835-L854

/**
     * @notice Lets the delegate withdraw unused WETH
     * @param  delegateId The ID of the delegate
     * @return amountWithdrawn The amount of WETH withdrawn
     *
     */
    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;

        IERC20WithBurn(weth).safeTransfer(msg.sender, amountWithdrawn);

        emit LogDelegateWithdraw(delegateId, amountWithdrawn);
    }

I think there's enough of a doubt to make the finding valid

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter