Open Dollar - twicek's results

A floating $1.00 pegged stablecoin backed by Liquid Staking Tokens with NFT controlled vaults.

General Information

Platform: Code4rena

Start Date: 18/10/2023

Pot Size: $36,500 USDC

Total HM: 17

Participants: 77

Period: 7 days

Judge: MiloTruck

Total Solo HM: 5

Id: 297

League: ETH

Open Dollar

Findings Distribution

Researcher Performance

Rank: 17/77

Findings: 4

Award: $341.16

QA:
grade-b

🌟 Selected for report: 1

πŸš€ Solo Findings: 0

Findings Information

🌟 Selected for report: falconhoof

Also found by: 0x6d6164616e, Baki, Beosin, Krace, T1MOH, bitsurfer, immeas, pep7siup, tnquanghuy0512, twicek

Labels

bug
3 (High Risk)
satisfactory
sufficient quality report
upgraded by judge
duplicate-26

Awards

180.637 USDC - $180.64

External Links

Lines of code

https://github.com/open-dollar/od-contracts/blob/f4f0246bb26277249c1d5afe6201d4d9096e52e6/src/contracts/AccountingEngine.sol#L199

Vulnerability details

Proof of Concept

The function auctionSurplus has the following checks that _params.surplusTransferPercentage is not greater than WAD:

AccountingEngine.sol#L199

    if(_params.surplusTransferPercentage > WAD) revert AccEng_surplusTransferPercentOverLimit();

The check has the consequence of only allowing 99% of the _params.surplusAmount to be auctioned out, with ONE_HUNDRED_WAD being 100%:

AccountingEngine.sol#L212-L217

    // auction surplus percentage
    if (_params.surplusTransferPercentage < ONE_HUNDRED_WAD) {
      _id = surplusAuctionHouse.startAuction({
        _amountToSell: _params.surplusAmount.wmul(ONE_HUNDRED_WAD - _params.surplusTransferPercentage),
        _initialBid: 0
      });

Preventing any range of surplus amount to be auctioned is an implementation error as discussed with the sponsor:

auctionSurplus should run when _params.surplusTransferPercentage is greater than 0% and less than 100% in terms of WAD.

Impact

It will not be possible to auction less than 99% of the surplus amount.

Tool used

Manual Review

Consider modifying the check like this:

-   if(_params.surplusTransferPercentage > WAD) revert AccEng_surplusTransferPercentOverLimit();
+   if(_params.surplusTransferPercentage > ONE_HUNDRED_WAD) revert AccEng_surplusTransferPercentOverLimit();

Or you could also remove the check altogether because this line will already revert if _params.surplusTransferPercentage is greater than 100%: AccountingEngine.sol#L215

        _amountToSell: _params.surplusAmount.wmul(ONE_HUNDRED_WAD - _params.surplusTransferPercentage),

Assessed type

Error

#0 - c4-pre-sort

2023-10-26T04:39:26Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-10-26T04:39:52Z

raymondfam marked the issue as duplicate of #26

#2 - c4-judge

2023-11-03T14:07:02Z

MiloTruck changed the severity to 3 (High Risk)

#3 - c4-judge

2023-11-03T14:08:32Z

MiloTruck marked the issue as satisfactory

Findings Information

🌟 Selected for report: twicek

Also found by: 0xMosh, 0xhacksmithh, Arz, bitsurfer, btk, kutugu, ni8mare, pep7siup, spark, xAriextz

Labels

bug
2 (Med Risk)
low quality report
primary issue
satisfactory
selected for report
M-11

Awards

70.4484 USDC - $70.45

External Links

Lines of code

https://github.com/open-dollar/od-contracts/blob/f4f0246bb26277249c1d5afe6201d4d9096e52e6/src/contracts/oracles/CamelotRelayer.sol#L20 https://github.com/open-dollar/od-contracts/blob/f4f0246bb26277249c1d5afe6201d4d9096e52e6/src/contracts/oracles/UniV3Relayer.sol#L18 https://github.com/open-dollar/od-contracts/blob/f4f0246bb26277249c1d5afe6201d4d9096e52e6/src/contracts/oracles/CamelotRelayer.sol#L41-L42

Vulnerability details

Proof of Concept

Testing addresses for Camelot and UniswapV3 factories are still used in the code:

CamelotRelayer.sol#L20

  address internal constant _CAMELOT_FACTORY = GOERLI_CAMELOT_V3_FACTORY;

UniV3Relayer.sol#L18

  address internal constant _UNI_V3_FACTORY = GOERLI_UNISWAP_V3_FACTORY;

Additionally, the correct interface to get the camelotPair is commented out:

CamelotRelayer.sol#L41-L42

    // camelotPair = ICamelotFactory(_CAMELOT_FACTORY).getPair(_baseToken, _quoteToken);
    camelotPair = IAlgebraFactory(_CAMELOT_FACTORY).poolByPair(_baseToken, _quoteToken);

Impact

It will prevent integration with UniswapV3 and Camelot.

Tool used

Manual Review

Make the changes necessary to be compatible with Arbitrum One.

Assessed type

Error

#0 - c4-pre-sort

2023-10-26T04:40:05Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-10-26T04:40:34Z

raymondfam marked the issue as low quality report

#2 - c4-pre-sort

2023-10-26T04:40:56Z

raymondfam marked the issue as duplicate of #119

#3 - c4-judge

2023-11-02T06:21:17Z

MiloTruck marked the issue as selected for report

#4 - MiloTruck

2023-11-02T06:23:47Z

While the protocol team is most likely aware of this and these addresses are probably set to testnet ones currently for testing purposes, it is an undeniable fact that if the current code was to be deployed without any modifications, the CamelotRelayer and UniV3Relayer contracts would not function as expected.

As such, I believe medium severity is appropriate.

#5 - c4-judge

2023-11-02T08:46:41Z

MiloTruck marked the issue as satisfactory

#6 - pi0neerpat

2023-11-10T07:09:21Z

The problem stated is that the Camelot and Uniswap factory addresses set in the Registry are set to Goerli addresses, not Mainnet. In production, we will swap Goerli addresses for Mainnet. Perhaps there is a better way to manage this than manually changing the constant variable in the Registry, however the recommendation does provide a helpful suggestion.

#7 - mcgrathcoutinho

2023-11-10T20:03:07Z

Hi @MiloTruck, I believe this issue is QA/Low-severity at best since the team is clearly aware of this and has intentionally used Goerli addresses for testnet purposes. Additionally as the sponsor mentioned above In production, we will swap Goerli addresses for Mainnet further clarifies this. The only advice/value provided to the sponsor is to not hardcode the addresses and use another way to manage setting these addresses.

#8 - MiloTruck

2023-11-11T01:54:40Z

Hi @mcgrathcoutinho, all findings have to judged with the assumption that the code in-scope will be deployed without modifications, unless stated otherwise by the sponsor beforehand in the contest's README.

While I do agree that the sponsor was probably aware of this, this issue is still a valid medium severity bug according to C4 rules.

#9 - daopunk

2023-11-13T20:46:49Z

Okay, agreed. Although we were previously aware of this issue, we did not explicitly state that these variables would be updated before mainnet deployment, so therefore it is a valid find. Since this issue would prevent the liquidity pool relayers from functioning and prevent a price reading, it seems fair to label it medium severity.

Findings Information

🌟 Selected for report: lsaudit

Also found by: 0xPsuedoPandit, 0xhacksmithh, Stormreckson, ZanyBonzy, klau5, tnquanghuy0512, twicek, xAriextz

Labels

2 (Med Risk)
satisfactory
duplicate-142

Awards

81.7698 USDC - $81.77

External Links

Judge has assessed an item in Issue #300 as 2 risk. The relevant finding follows:

[L-01] transferSAFEOwnership does not reset users allowed to modify a safe on behalf of an owner transferSAFEOwnership is not deleting potential allowed users in safeCan upon transferring a safe ownership. This could very much be an intended design (keeping in β€œmemory” the allowed users (delegatees) for a given owner), in case an owner wants to send an NFT-Vault and receive it back, the allowed users would remain the same. But it would also mean that if a NFT-Vault owner allows Bob to execute actions on his safe, then send his NFT to someone and wait 1 year before getting it back, he will still allow Bob automatically after receiving the NFT which could be detrimental if the owner does not trust Bob anymore.

ODSafeManager.sol#L136-L152

function transferSAFEOwnership(uint256 _safe, address _dst) external { require(msg.sender == address(vault721), 'SafeMngr: Only Vault721');

if (_dst == address(0)) revert ZeroAddress(); SAFEData memory _sData = _safeData[_safe]; if (_dst == _sData.owner) revert AlreadySafeOwner(); _usrSafes[_sData.owner].remove(_safe); _usrSafesPerCollat[_sData.owner][_sData.collateralType].remove(_safe); _usrSafes[_dst].add(_safe); _usrSafesPerCollat[_dst][_sData.collateralType].add(_safe); _safeData[_safe].owner = _dst; emit TransferSAFEOwnership(msg.sender, _safe, _dst);

#0 - c4-judge

2023-11-03T17:06:50Z

MiloTruck marked the issue as duplicate of #142

#1 - c4-judge

2023-11-03T17:06:55Z

MiloTruck marked the issue as satisfactory

Awards

8.3007 USDC - $8.30

Labels

bug
grade-b
QA (Quality Assurance)
sufficient quality report
Q-09

External Links

[L-01] transferSAFEOwnership does not reset users allowed to modify a safe on behalf of an owner

transferSAFEOwnership is not deleting potential allowed users in safeCan upon transferring a safe ownership. This could very much be an intended design (keeping in "memory" the allowed users (delegatees) for a given owner), in case an owner wants to send an NFT-Vault and receive it back, the allowed users would remain the same. But it would also mean that if a NFT-Vault owner allows Bob to execute actions on his safe, then send his NFT to someone and wait 1 year before getting it back, he will still allow Bob automatically after receiving the NFT which could be detrimental if the owner does not trust Bob anymore.

ODSafeManager.sol#L136-L152

  function transferSAFEOwnership(uint256 _safe, address _dst) external {
    require(msg.sender == address(vault721), 'SafeMngr: Only Vault721');


    if (_dst == address(0)) revert ZeroAddress();
    SAFEData memory _sData = _safeData[_safe];
    if (_dst == _sData.owner) revert AlreadySafeOwner();


    _usrSafes[_sData.owner].remove(_safe);
    _usrSafesPerCollat[_sData.owner][_sData.collateralType].remove(_safe);


    _usrSafes[_dst].add(_safe);
    _usrSafesPerCollat[_dst][_sData.collateralType].add(_safe);


    _safeData[_safe].owner = _dst;


    emit TransferSAFEOwnership(msg.sender, _safe, _dst);
  }

[L-02 ] ODGovernor contract does not allow for cancelling proposals

ODGovernor does not expose _cancel externally nor calls it internally, therefore it will not be possible to cancel proposals in the current implementation. However, I would advise great care if modifications are made to allow canceling proposals in the future as it could lead to this issue.

ODGovernor.sol#L117-L124

  function _cancel(
    address[] memory targets,
    uint256[] memory values,
    bytes[] memory calldatas,
    bytes32 descriptionHash
  ) internal override(Governor, GovernorTimelockControl) returns (uint256) {
    return super._cancel(targets, values, calldatas, descriptionHash);
  }

[N-01] Redundant check in auctionSurplus

The following check in auctionSurplus is redundant: AccountingEngine.sol#L225

      if (extraSurplusReceiver == address(0)) revert AccEng_NullSurplusReceiver();

It is already done at the beginning of the function:

AccountingEngine.sol#L198-L202

  function auctionSurplus() external returns (uint256 _id) {
    if(_params.surplusTransferPercentage > WAD) revert AccEng_surplusTransferPercentOverLimit();
    if (_params.surplusAmount == 0) revert AccEng_NullAmount();
    if (extraSurplusReceiver == address(0)) revert AccEng_NullSurplusReceiver();
    if (block.timestamp < lastSurplusTime + _params.surplusDelay) revert AccEng_SurplusCooldown();

[N-02] Unnecessary check

This condition ODProxy(_userRegistry[_user]).OWNER() != _user in the following code will never happen because the only way to fill the _userRegistry[_user] is via _build and in this function ODProxy is correctly initialized with _user. Vault721.sol#L154-L156

  function _isNotProxy(address _user) internal view returns (bool) {
    return _userRegistry[_user] == address(0) || ODProxy(_userRegistry[_user]).OWNER() != _user;
  }

#0 - c4-pre-sort

2023-10-27T00:40:33Z

raymondfam marked the issue as sufficient quality report

#1 - c4-judge

2023-11-03T16:41:02Z

MiloTruck marked the issue as grade-b

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