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: 43/127
Findings: 2
Award: $70.36
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: trustindistrust
Also found by: 0xbepresent, Jujic, Lambda, RaoulSchaffranek, c7e7eff, catchup, codexploder, cryptonue, d3e4, eierina, jwood, pashov, peanuts, pedroais, simon135
33.634 USDC - $33.63
https://github.com/code-423n4/2022-10-inverse/blob/main/src/Market.sol#L203-L206
Market.sol
contract contains a rug vector in plain sight, the recall()
function. If Market.sol
contract's DOLA token is being drained by lender
, then any of borrow or replenish function will reverted because no DOLA token exist.
Even though this is not likely happen (the process of draining DOLA token from Market contract, since it need the lender
to be compromised or bad behaviour of lender address), but the power of lender is big enough to stop the contract functionalities.
File: Market.sol 203: function recall(uint amount) public { 204: require(msg.sender == lender, "Only lender can recall"); 205: dola.transfer(msg.sender, amount); 206: }
Manual analysis
Maybe better accounting of token allocation, for example create variables to store how many can recall
#0 - c4-judge
2022-11-05T23:02:57Z
0xean marked the issue as duplicate
#1 - Simon-Busch
2022-12-05T15:37:18Z
Issue marked as satisfactory as requested by 0xean
#2 - c4-judge
2022-12-07T08:22:05Z
Simon-Busch marked the issue as duplicate of #301
🌟 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
setGov()
and setting Operator setOperator()
In Market.sol
there is a modifier named onlyGov
which control the market crucial function. To update this onlyGov
address, there is only one function setGov()
without two step transfer pattern. This can cause a problem if the function is being set with wrong gov address because it can't revert the value. Consider to use two step transfer patter for this kind of ownership
function.
there is also an instance of this issue, setOperator
inside BorrowController.sol
contract
File: Market.sol 130: function setGov(address _gov) public onlyGov { gov = _gov; } File: BorrowController.sol 26: function setOperator(address _operator) public onlyOperator { operator = _operator; }
In Market.sol
contract, borrowInternal()
function, the amount value is not being check if it's greater than 0. Thus if function call with 0 amount, the transfer will process with 0 transfer token, which is a waste of gas, it's better to check condition and revert.
File: Market.sol 389: function borrowInternal(address borrower, address to, uint amount) internal { 390: require(!borrowPaused, "Borrowing is paused"); 391: if(borrowController != IBorrowController(address(0))) { 392: require(borrowController.borrowAllowed(msg.sender, borrower, amount), "Denied by borrow controller"); 393: } 394: uint credit = getCreditLimitInternal(borrower); 395: debts[borrower] += amount; 396: require(credit >= debts[borrower], "Exceeded credit limit"); 397: totalDebt += amount; 398: dbr.onBorrow(borrower, amount); 399: dola.transfer(to, amount); 400: emit Borrow(borrower, amount); 401: }
replenishmentPriceBps
in DBR.sol
contractFile: DBR.sol 62: function setReplenishmentPriceBps(uint newReplenishmentPriceBps_) public onlyOperator { 63: require(newReplenishmentPriceBps_ > 0, "replenishment price must be over 0"); 64: replenishmentPriceBps = newReplenishmentPriceBps_; 65: }
Pay
function is not check for availability of tokenIn GovTokenEscrow.sol
and SimpleERC20Escrow.sol
contracts, the pay()
function is not checking if the balance of the token inside the contract is enough to transfer based on the amount
input. (Unlike INVEscrow.sol)
File: SimpleERC20Escrow.sol 36: function pay(address recipient, uint amount) public { 37: require(msg.sender == market, "ONLY MARKET"); 38: token.transfer(recipient, amount); 39: } File: GovTokenEscrow.sol 43: function pay(address recipient, uint amount) public { 44: require(msg.sender == market, "ONLY MARKET"); 45: token.transfer(recipient, amount); 46: }
For production, there should not be any TODO
left on the code
File: INVEscrow.sol 34: constructor(IXINV _xINV) { 35: xINV = _xINV; // TODO: Test whether an immutable variable will persist across proxies 36: }
Even though both is similar but it's good to have standard for quality code example:
File: DBR.sol 14: uint256 public _totalSupply; 15: address public operator; 16: address public pendingOperator; 17: uint public totalDueTokensAccrued; 18: uint public replenishmentPriceBps; File: DBR.sol 121: uint debt = debts[user]; 122: uint accrued = (block.timestamp - lastUpdated[user]) * debt / 365 days;
There are some of code > 120 characters per line in (Market.sol, Oracle.sol)
setReplenishmentPriceBps
It's best to emit an event if function change some settings
#0 - c4-judge
2022-11-08T00:34:18Z
0xean marked the issue as grade-b