code-423n4 / 2023-08-dopex-findings

3 stars 3 forks source link

Decaying bonds are forced to expire when contract are paused #1113

Open code423n4 opened 1 year ago

code423n4 commented 1 year ago

Lines of code

https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/decaying-bonds/RdpxDecayingBonds.sol#L122 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/core/RdpxV2Core.sol#L636 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/core/RdpxV2Core.sol#L144 https://github.com/code-423n4/2023-08-dopex/blob/eb4d4a201b3a75dd4bddc74a34e9c42c71d0d12f/contracts/core/RdpxV2Core.sol#L899

Vulnerability details

Impact

Decaying bond is forced to expire when contract are paused

Proof of Concept

when decaying bond is minted, an expiration time is set

  function mint(
    address to,
    uint256 expiry,
    uint256 rdpxAmount
  ) external onlyRole(MINTER_ROLE) {
    _whenNotPaused();
    require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter");
    uint256 bondId = _mintToken(to);
    bonds[bondId] = Bond(to, expiry, rdpxAmount);

    emit BondMinted(to, bondId, expiry, rdpxAmount);
  }

but when user bond or bond with delegate, the expiry time is checked

  function _transfer(
    uint256 _rdpxAmount,
    uint256 _wethAmount,
    uint256 _bondAmount,
    uint256 _bondId
  ) internal {
    if (_bondId != 0) {
      (, uint256 expiry, uint256 amount) = IRdpxDecayingBonds(
        addresses.rdpxDecayingBonds
      ).bonds(_bondId);

      _validate(amount >= _rdpxAmount, 1);
      _validate(expiry >= block.timestamp, 2);
      _validate(
        IRdpxDecayingBonds(addresses.rdpxDecayingBonds).ownerOf(_bondId) ==
          msg.sender,
        9
      );

      IRdpxDecayingBonds(addresses.rdpxDecayingBonds).decreaseAmount(
        _bondId,
        amount - _rdpxAmount
      );

      IRdpxReserve(addresses.rdpxReserve).withdraw(_rdpxAmount);

      reserveAsset[reservesIndex["RDPX"]].tokenBalance += _rdpxAmount;
    }

note the expiry check

(, uint256 expiry, uint256 amount) = IRdpxDecayingBonds(
addresses.rdpxDecayingBonds
).bonds(_bondId);

_validate(amount >= _rdpxAmount, 1);
_validate(expiry >= block.timestamp, 2);

if the a time decay bond expires, the bond / bondWithDelegate can revert

however, the admin can pause the contract,

and when contract are paused, the decaying bond is forced to expire with no other option because when calling bond / bondWithdDelegate during the pause state,

transaction revert in this line of code

Tools Used

Manual Review

Recommended Mitigation Steps

Does not count the paused time towards expiration and leave sufficient timelock for user to bond / bond with delegate using a bond id that is about to expire

Assessed type

Timing

c4-pre-sort commented 1 year ago

bytes032 marked the issue as duplicate of #2094

c4-judge commented 1 year ago

GalloDaSballo changed the severity to QA (Quality Assurance)