Platform: Code4rena
Start Date: 25/10/2022
Pot Size: $50,000 USDC
Total HM: 18
Participants: 127
Period: 5 days
Judge: 0xean
Total Solo HM: 9
Id: 175
League: ETH
Rank: 81/127
Findings: 1
Award: $36.73
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0x1f8b
Also found by: 0xNazgul, 0xSmartContract, Aymen0909, B2, Bnke0x0, Deivitto, Diana, Dinesh11G, ElKu, JC, Josiah, Rahoz, RaymondFam, ReyAdmirado, Rolezn, Waze, __141345__, adriro, aphak5010, brgltd, c3phas, c7e7eff, carlitox477, cducrest, ch0bu, chrisdior4, cryptonue, cryptostellar5, cylzxje, d3e4, delfin454000, enckrish, evmwanderer, fatherOfBlocks, gogo, hansfriese, horsefacts, immeas, leosathya, lukris02, neumo, oyc_109, pedr02b2, rbserver, robee, rotcivegaf, rvierdiiev, sakshamguruji, shark, simon135, tnevler, trustindistrust, wagmi
36.7345 USDC - $36.73
Non-critical Issues
Issue | Instances | |
---|---|---|
1 | latestAnswer() is deprecated | 2 |
2 | Open TODOs | 1 |
3 | Proxy implementation contract not initialized | 3 |
4 | User could mint DBR for free | 1 |
5 | Oracle lows are observational not actual | 1 |
Code Style
Issue | Instances | |
---|---|---|
1 | Rename function | 1 |
latestAnswer()
is deprecatedAs per Chainlink's documentation, the latestAnswer
 function is deprecated.
There are 2 instances of this issue:
File: src/Oracle.sol #1 82: uint price = feeds[token].feed.latestAnswer();
File: src/Oracle.sol #2 116: uint price = feeds[token].feed.latestAnswer();
Open questions should be resolved before deployment
There is one instance of this issue:
File: src/escrows/INVEscrow.sol #1 35: xINV = _xINV; // TODO: Test whether an immutable variable will persist across proxies
The escrow implementation contracts can be initialised by anyone calling the initialize function. Not critical in this case.
[Reference 1](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializing_the_implementation_contract](https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializing_the_implementation_contract)
There are 3 instances of this issue:
File: src/escrows/GovTokenEscrow.sol #1 30: function initialize(IERC20 _token, address _beneficiary) public {
File: src/escrows/INVEscrow.sol #2 44: function initialize(IERC20 _token, address _beneficiary) public {
File: src/escrows/SimpleERC20Escrow.sol #3 25: function initialize(IERC20 _token, address _beneficiary) public {
Due to loss of precision no cost is accrued in the following function if
$amount < 10000/ replenishmentPriceBps$
File: src/Market.sol function forceReplenish(address user, uint amount) public { uint deficit = dbr.deficitOf(user); require(deficit > 0, "No DBR deficit"); require(deficit >= amount, "Amount > deficit"); uint replenishmentCost = amount * dbr.replenishmentPriceBps() / 10000; uint replenisherReward = replenishmentCost * replenishmentIncentiveBps / 10000; debts[user] += replenishmentCost; uint collateralValue = getCollateralValueInternal(user); require(collateralValue >= debts[user], "Exceeded collateral value"); totalDebt += replenishmentCost; dbr.onForceReplenish(user, amount); dola.transfer(msg.sender, replenisherReward); emit ForceReplenish(user, msg.sender, amount, replenishmentCost, replenisherReward); }
DBR could be minted in the called function if a user is in deficit.
File: src/DBR.sol function onForceReplenish(address user, uint amount) public { require(markets[msg.sender], "Only markets can call onForceReplenish"); uint deficit = deficitOf(user); require(deficit > 0, "No deficit"); require(deficit >= amount, "Amount > deficit"); uint replenishmentCost = amount * replenishmentPriceBps / 10000; accrueDueTokens(user); debts[user] += replenishmentCost; _mint(user, amount); }
This is dust amount not worth the gas unless token's decimals are very low and replenishmentPriceBps
is also very low.
The oracle design relies on the assumption that low prices are observed by the smart contract. There is no guarantee nor incentives that this would happen. In rarely used markets this could defy the original dampening intention.
File: src/Oracle.sol #1 function getPrice(address token, uint collateralFactorBps) external returns (uint) { if(fixedPrices[token] > 0) return fixedPrices[token]; if(feeds[token].feed != IChainlinkFeed(address(0))) { // get price from feed uint price = feeds[token].feed.latestAnswer(); require(price > 0, "Invalid feed price"); // normalize price uint8 feedDecimals = feeds[token].feed.decimals(); uint8 tokenDecimals = feeds[token].tokenDecimals; uint8 decimals = 36 - feedDecimals - tokenDecimals; uint normalizedPrice = price * (10 ** decimals); // potentially store price as today's low uint day = block.timestamp / 1 days; uint todaysLow = dailyLows[token][day]; if(todaysLow == 0 || normalizedPrice < todaysLow) { dailyLows[token][day] = normalizedPrice; todaysLow = normalizedPrice; emit RecordDailyLow(token, normalizedPrice); } // get yesterday's low uint yesterdaysLow = dailyLows[token][day - 1]; // calculate new borrowing power based on collateral factor uint newBorrowingPower = normalizedPrice * collateralFactorBps / 10000; uint twoDayLow = todaysLow > yesterdaysLow && yesterdaysLow > 0 ? yesterdaysLow : todaysLow; if(twoDayLow > 0 && newBorrowingPower > twoDayLow) { uint dampenedPrice = twoDayLow * 10000 / collateralFactorBps; return dampenedPrice < normalizedPrice ? dampenedPrice: normalizedPrice; } return normalizedPrice; } revert("Price not found"); }
Although this name is used in a popular library, it is not expressive enough;
calculateEscrowAddress
would be a better name.
File: src/Market.sol #1 292: function predictEscrow(address user) public view returns (IEscrow predicted)
#0 - c4-judge
2022-11-07T19:52:39Z
0xean marked the issue as grade-b