20,000 USDC
View results
Submission Details
Severity: gas
Valid

Gas Report

GAS Summary

Gas Optimizations

Issue Contexts Estimated Gas Saved
GAS‑1 abi.encode() is less efficient than abi.encodepacked() 3 159
GAS‑2 Consider activating via-ir for deploying 1 250
GAS‑3 <array>.length Should Not Be Looked Up In Every Loop Of A For-loop 6 582
GAS‑4 Use assembly to emit events 9 342
GAS‑5 Avoid emitting event on every iteration 4 1500
GAS‑6 Setting the constructor to payable 5 65
GAS‑7 Counting down in for statements is more gas efficient 6 1542
GAS‑8 Using delete statement can save gas 3 24
GAS‑9 ++i Costs Less Gas Than i++, Especially When It’s Used In For-loops (--i/i-- Too) 6 36
GAS‑10 Functions guaranteed to revert when called by normal users can be marked payable 5 105
GAS‑11 It Costs More Gas To Initialize Variables To Zero Than To Let The Default Of Zero Be Applied 6 36
GAS‑12 Multiple accesses of a mapping/array should use a local variable cache 30 2400
GAS‑13 Multiple Address Mappings Can Be Combined Into A Single Mapping Of An Address To A Struct, Where Appropriate 3 1500
GAS‑14 Optimize names to save gas 5 110
GAS‑15 <x> += <y> Costs More Gas Than <x> = <x> + <y> For State Variables 14 84
GAS‑16 Using private rather than public for constants, saves gas 3 10200
GAS‑17 Help The Optimizer By Saving A Storage Variable’s Reference Instead Of Repeatedly Fetching It 41 2214
GAS‑18 Superfluous event fields 2 68
GAS‑19 Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead 4 24
GAS‑20 ++i/i++ Should Be unchecked{++i}/unchecked{i++} When It Is Not Possible For Them To Overflow, As Is The Case When Used In For- And While-loops 6 210
GAS‑21 Use assembly to check for address(0) 2 52
GAS‑22 Use do while loops instead of for loops 6 24
GAS‑23 Use of Custom Errors Instead of String 2 24
GAS‑24 Using XOR (^) and AND (&) bitwise equivalents 11 143

Total: 321 contexts over 24 issues

Gas Optimizations

[GAS‑1] abi.encode() is less efficient than abi.encodepacked()

See for more information: https://github.com/ConnorBlockchain/Solidity-Encode-Gas-Comparison

Proof Of Concept

File: Lender.sol
​
113: poolId = keccak256(abi.encode(lender, loanToken, collateralToken));

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L113

File: Lender.sol
​
571: abi.encode(loan.lender, loan.loanToken, loan.collateralToken)

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L571

File: Lender.sol
​
596: abi.encode(

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L596

Test Code

contract GasTest is DSTest {
    Contract0 c0;
    Contract1 c1;
    function setUp() public {
        c0 = new Contract0();
        c1 = new Contract1();
    }
    function testGas() public {
        c0.not_optimized();
        c1.optimized();
    }
}
contract Contract0 {
    string a = "Code4rena";
    function not_optimized() public returns(bytes32){
        return keccak256(abi.encode(a));
    }
}
contract Contract1 {
    string a = "Code4rena";
    function optimized() public returns(bytes32){
        return keccak256(abi.encodePacked(a));
    }
}

Gas Test Report

Contract0 contract
Deployment Cost Deployment Size
101871 683
Function Name min avg median max # calls
not_optimized 2661 2661 2661 2661 1
Contract1 contract
Deployment Cost Deployment Size
99465 671
Function Name min avg median max # calls
optimized 2608 2608 2608 2608 1

[GAS‑2] Consider activating via-ir for deploying

The IR-based code generator was introduced with an aim to not only allow code generation to be more transparent and auditable but also to enable more powerful optimization passes that span across functions.

You can enable it on the command line using --via-ir or with the option {"viaIR": true}.

This will take longer to compile, but you can just simple test it before deploying and if you got a better benchmark then you can add --via-ir to your deploy command

More on: https://docs.soliditylang.org/en/v0.8.17/ir-breaking-changes.html

[GAS‑3] <array>.length Should Not Be Looked Up In Every Loop Of A For-loop

The overheads outlined below are PER LOOP, excluding the first loop

storage arrays incur a Gwarmaccess (100 gas)
memory arrays use MLOAD (3 gas)
calldata arrays use CALLDATALOAD (3 gas)

Caching the length changes each of these to a DUP (3 gas), and gets rid of the extra DUP needed to store the stack offset

Proof Of Concept

File: Lender.sol
​
233: for (uint256 i = 0; i < borrows.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L233

File: Lender.sol
​
293: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L293

File: Lender.sol
​
359: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L359

File: Lender.sol
​
438: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L438

File: Lender.sol
​
549: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L549

File: Lender.sol
​
592: for (uint256 i = 0; i < refinances.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L592

[GAS‑4] Use assembly to emit events

We can use assembly to emit events efficiently by utilizing scratch space and the free memory pointer. This will allow us to potentially avoid memory expansion costs.
Note: In order to do this optimization safely, we will need to cache and restore the free memory pointer.

For example, for a generic emit event for eventSentAmountExample:

// uint256 id, uint256 value, uint256 amount
emit eventSentAmountExample(id, value, amount);

We can use the following assembly emit events:

assembly {
let memptr := mload(0x40)
mstore(0x00, calldataload(0x44))
mstore(0x20, calldataload(0xa4))
mstore(0x40, amount)
log1(
0x00,
0x60,
// keccak256("eventSentAmountExample(uint256,uint256,uint256)")
0xa622cf392588fbf2cd020ff96b2f4ebd9c76d7a4bc7f3e6b2f18012312e76bc3
)
mstore(0x40, memptr)
}

Proof Of Concept

File: Lender.sol
​
165: emit PoolBalanceUpdated(poolId, p.poolBalance);
169: emit PoolCreated(poolId, p);
172: emit PoolUpdated(poolId, p);
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L165

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L169

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L172

File: Lender.sol
​
214: emit PoolMaxLoanRatioUpdated(poolId, maxLoanRatio);
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L214

File: Lender.sol
​
225: emit PoolInterestRateUpdated(poolId, interestRate);
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L225

File: Lender.sol
​
533: emit LoanBought(loanId);
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L533

File: Lender.sol
​
708: emit Refinanced(loanId);
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L708

File: Lender.sol
​
734: emit PoolBalanceUpdated(poolId, newBalance);
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L734

File: Ownable.sol
​
21: emit OwnershipTransferred(msg.sender, _owner);
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/utils/Ownable.sol#L21

[GAS‑5] Avoid emitting event on every iteration

Expensive operations should always try to be avoided within loops. Such operations include: reading/writing to storage, heavy calculations, external calls, and emitting events. In this instance, an event is being emitted every iteration. Events have a base cost of Glog (375 gas) per emit and Glogdata (8 gas) * number of bytes in event. We can avoid incurring those costs each iteration by emitting the event outside of the loop.

Proof Of Concept

File: Lender.sol
​
293: for (uint256 i = 0; i < loanIds.length; i++) {
uint256 loanId = loanIds[i];
Loan memory loan = loans[loanId];
(
uint256 lenderInterest,
uint256 protocolInterest
) = _calculateInterest(loan);
​
bytes32 poolId = getPoolId(
loan.lender,
loan.loanToken,
loan.collateralToken
);
​
_updatePoolBalance(
poolId,
pools[poolId].poolBalance + loan.debt + lenderInterest
);
pools[poolId].outstandingLoans -= loan.debt;
​
IERC20(loan.loanToken).transferFrom(
msg.sender,
address(this),
loan.debt + lenderInterest
);
IERC20(loan.loanToken).transferFrom(
msg.sender,
feeReceiver,
protocolInterest
);
IERC20(loan.collateralToken).transfer(
loan.borrower,
loan.collateral
);
emit Repaid(
msg.sender,
loan.lender,
loanId,
loan.debt,
loan.collateral,
loan.interestRate,
loan.startTimestamp
);
delete loans[loanId];
}
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L293

File: Lender.sol
​
359: for (uint256 i = 0; i < loanIds.length; i++) {
uint256 loanId = loanIds[i];
bytes32 poolId = poolIds[i];
Loan memory loan = loans[loanId];
if (msg.sender != loan.lender) revert Unauthorized();
Pool memory pool = pools[poolId];
if (pool.loanToken != loan.loanToken) revert TokenMismatch();
if (pool.collateralToken != loan.collateralToken)
revert TokenMismatch();
if (pool.interestRate > loan.interestRate) revert RateTooHigh();
if (pool.auctionLength < loan.auctionLength) revert AuctionTooShort();
(
uint256 lenderInterest,
uint256 protocolInterest
) = _calculateInterest(loan);
uint256 totalDebt = loan.debt + lenderInterest + protocolInterest;
if (pool.poolBalance < totalDebt) revert PoolTooSmall();
if (totalDebt < pool.minLoanSize) revert LoanTooSmall();
uint256 loanRatio = (totalDebt * 10 ** 18) / loan.collateral;
if (loanRatio > pool.maxLoanRatio) revert RatioTooHigh();
_updatePoolBalance(poolId, pool.poolBalance - totalDebt);
pools[poolId].outstandingLoans += totalDebt;
​
bytes32 oldPoolId = getPoolId(
loan.lender,
loan.loanToken,
loan.collateralToken
);
_updatePoolBalance(
oldPoolId,
pools[oldPoolId].poolBalance + loan.debt + lenderInterest
);
pools[oldPoolId].outstandingLoans -= loan.debt;
​
IERC20(loan.loanToken).transfer(feeReceiver, protocolInterest);
​
emit Repaid(
loan.borrower,
loan.lender,
loanId,
loan.debt + lenderInterest + protocolInterest,
loan.collateral,
loan.interestRate,
loan.startTimestamp
);
​
loans[loanId].lender = pool.lender;
loans[loanId].interestRate = pool.interestRate;
loans[loanId].startTimestamp = block.timestamp;
loans[loanId].auctionStartTimestamp = type(uint256).max;
loans[loanId].debt = totalDebt;
​
emit Borrowed(
loan.borrower,
pool.lender,
loanId,
loans[loanId].debt,
loans[loanId].collateral,
pool.interestRate,
block.timestamp
);
}
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L359

File: Lender.sol
​
438: for (uint256 i = 0; i < loanIds.length; i++) {
uint256 loanId = loanIds[i];
Loan memory loan = loans[loanId];
if (msg.sender != loan.lender) revert Unauthorized();
if (loan.auctionStartTimestamp != type(uint256).max)
revert AuctionStarted();
​
loans[loanId].auctionStartTimestamp = block.timestamp;
emit AuctionStart(
loan.borrower,
loan.lender,
loanId,
loan.debt,
loan.collateral,
block.timestamp,
loan.auctionLength
);
}
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L438

File: Lender.sol
​
549: for (uint256 i = 0; i < loanIds.length; i++) {
uint256 loanId = loanIds[i];
Loan memory loan = loans[loanId];
if (loan.auctionStartTimestamp == type(uint256).max)
revert AuctionNotStarted();
if (
block.timestamp <
loan.auctionStartTimestamp + loan.auctionLength
) revert AuctionNotEnded();
uint256 govFee = (borrowerFee * loan.collateral) / 10000;
IERC20(loan.collateralToken).transfer(feeReceiver, govFee);
IERC20(loan.collateralToken).transfer(
loan.lender,
loan.collateral - govFee
);
​
bytes32 poolId = keccak256(
abi.encode(loan.lender, loan.loanToken, loan.collateralToken)
);
​
pools[poolId].outstandingLoans -= loan.debt;
​
emit LoanSiezed(
loan.borrower,
loan.lender,
loanId,
loan.collateral
);
delete loans[loanId];
}
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L549

[GAS‑6] Setting the constructor to payable

Saves ~13 gas per instance

Proof Of Concept

File: Beedle.sol
​
11: constructor() ERC20("Beedle", "BDL") ERC20Permit("Beedle") Ownable(msg.sender)

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Beedle.sol#L11

File: Fees.sol
​
19: constructor(address _weth, address _staking)

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Fees.sol#L19

File: Lender.sol
​
73: constructor() Ownable(msg.sender)

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L73

File: Staking.sol
​
31: constructor(address _token, address _weth) Ownable(msg.sender)

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L31

File: Ownable.sol
​
14: constructor(address _owner)

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/utils/Ownable.sol#L14

[GAS‑7] Counting down in for statements is more gas efficient

Counting down is more gas efficient than counting up because neither we are making zero variable to non-zero variable and also we will get gas refund in the last transaction when making non-zero to zero variable.

Proof Of Concept

File: Lender.sol
​
233: for (uint256 i = 0; i < borrows.length; i++) {
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L233

File: Lender.sol
​
293: for (uint256 i = 0; i < loanIds.length; i++) {
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L293

File: Lender.sol
​
359: for (uint256 i = 0; i < loanIds.length; i++) {
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L359

File: Lender.sol
​
438: for (uint256 i = 0; i < loanIds.length; i++) {
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L438

File: Lender.sol
​
549: for (uint256 i = 0; i < loanIds.length; i++) {
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L549

File: Lender.sol
​
592: for (uint256 i = 0; i < refinances.length; i++) {
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L592

Test Code

contract GasTest is DSTest {
    Contract0 c0;
    Contract1 c1;
    function setUp() public {
        c0 = new Contract0();
        c1 = new Contract1();
    }
    function testGas() public {
        c0.AddNum();
        c1.AddNum();
    }
}

contract Contract0 {
    uint256 num = 3;
    function AddNum() public {
        uint256 _num = num;
        for(uint i=0;i<=9;i++){
            _num = _num +1;
        }
        num = _num;
    }
}

contract Contract1 {
    uint256 num = 3;
    function AddNum() public {
        uint256 _num = num;
        for(uint i=9;i>=0;i--){
            _num = _num +1;
        }
        num = _num;
    }
}

Gas Test Report

Contract0 contract
Deployment Cost Deployment Size
77011 311
Function Name min avg median max # calls
AddNum 7040 7040 7040 7040 1
Contract1 contract
Deployment Cost Deployment Size
73811 295
Function Name min avg median max # calls
AddNum 3819 3819 3819 3819 1

[GAS‑8] Using delete statement can save gas

Proof Of Concept

File: Staking.sol
​
14: uint256 public balance = 0;
16: uint256 public index = 0;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L14

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L16

File: Staking.sol
​
56: claimable[msg.sender] = 0;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L56

[GAS‑9] ++i Costs Less Gas Than i++, Especially When It's Used In For-loops (--i/i-- Too)

Saves 6 gas per loop

Proof Of Concept

File: Lender.sol
​
233: for (uint256 i = 0; i < borrows.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L233

File: Lender.sol
​
293: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L293

File: Lender.sol
​
359: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L359

File: Lender.sol
​
438: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L438

File: Lender.sol
​
549: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L549

File: Lender.sol
​
592: for (uint256 i = 0; i < refinances.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L592

Recommended Mitigation Steps

For example, use ++i instead of i++

[GAS‑10] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier or require such as onlyOwner/onlyX is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2), DUP1(3), ISZERO(3), PUSH2(3), JUMPI(10), PUSH1(3), DUP1(3), REVERT(0), JUMPDEST(1), POP(2) which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost.

Proof Of Concept

File: Beedle.sol
​
36: function mint(address to, uint256 amount) external onlyOwner {
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Beedle.sol#L36

File: Lender.sol
​
84: function setLenderFee(uint256 _fee) external onlyOwner {
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L84

File: Lender.sol
​
92: function setBorrowerFee(uint256 _fee) external onlyOwner {
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L92

File: Lender.sol
​
100: function setFeeReceiver(address _feeReceiver) external onlyOwner {
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L100

File: Ownable.sol
​
19: function transferOwnership(address _owner) public virtual onlyOwner {
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/utils/Ownable.sol#L19

Recommended Mitigation Steps

Functions guaranteed to revert when called by normal users can be marked payable.

[GAS‑11] It Costs More Gas To Initialize Variables To Zero Than To Let The Default Of Zero Be Applied

Proof Of Concept

File: Lender.sol
​
233: for (uint256 i = 0; i < borrows.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L233

File: Lender.sol
​
293: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L293

File: Lender.sol
​
359: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L359

File: Lender.sol
​
438: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L438

File: Lender.sol
​
549: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L549

File: Lender.sol
​
592: for (uint256 i = 0; i < refinances.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L592

[GAS‑12] Multiple accesses of a mapping/array should use a local variable cache

Caching a mapping's value in a local storage or calldata variable when the value is accessed multiple times saves ~42 gas per access due to not having to perform the same offset calculation every time. Help the Optimizer by saving a storage variable's reference instead of repeatedly fetching it

To help the optimizer,declare a storage type variable and use it instead of repeatedly fetching the reference in a map or an array. As an example, instead of repeatedly calling someMap[someIndex], save its reference like this: SomeStruct storage someStruct = someMap[someIndex] and use it.

Proof Of Concept

File: Lender.sol
​
145: if (p.outstandingLoans != pools[poolId].outstandingLoans)
148: uint256 currentBalance = pools[poolId].poolBalance;
167: if (pools[poolId].lender == address(0)) {
175: pools[poolId] = p;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L145

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L148

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L167

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L175

File: Lender.sol
​
183: if (pools[poolId].lender != msg.sender) revert Unauthorized();
199: if (pools[poolId].lender != msg.sender) revert Unauthorized();
211: if (pools[poolId].lender != msg.sender) revert Unauthorized();
222: if (pools[poolId].lender != msg.sender) revert Unauthorized();
185: _updatePoolBalance(poolId, pools[poolId].poolBalance + amount);
187: IERC20(pools[poolId].loanToken).transferFrom(
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L183

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L199

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L211

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L222

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L185

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L187

File: Lender.sol
​
234: bytes32 poolId = borrows[i].poolId;
235: uint256 debt = borrows[i].debt;
236: uint256 collateral = borrows[i].collateral;
238: Pool memory pool = pools[poolId];
367: Pool memory pool = pools[poolId];
611: Pool memory pool = pools[poolId];
262: _updatePoolBalance(poolId, pools[poolId].poolBalance - debt);
636: _updatePoolBalance(poolId, pools[poolId].poolBalance - debt);
263: pools[poolId].outstandingLoans += debt;
637: pools[poolId].outstandingLoans += debt;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L234

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L235

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L236

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L238

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L367

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L611

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L262

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L636

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L263

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L637

File: Lender.sol
​
312: pools[poolId].poolBalance + loan.debt + lenderInterest
314: pools[poolId].outstandingLoans -= loan.debt;
575: pools[poolId].outstandingLoans -= loan.debt;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L312

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L314

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L575

File: Staking.sol
​
55: WETH.transfer(msg.sender, claimable[msg.sender]);
56: claimable[msg.sender] = 0;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L55-L56

File: Staking.sol
​
84: uint256 _supplyIndex = supplyIndex[recipient];
85: supplyIndex[recipient] = index;
92: supplyIndex[recipient] = index;
85: supplyIndex[recipient] = index;
92: supplyIndex[recipient] = index;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L84

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L85

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L92

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L85

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L92

[GAS‑13] Multiple Address Mappings Can Be Combined Into A Single Mapping Of An Address To A Struct, Where Appropriate

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot.

Proof Of Concept

File: Staking.sol
​
19: mapping(address => uint256) public supplyIndex;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L19

File: Staking.sol
​
22: mapping(address => uint256) public balances;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L22

File: Staking.sol
​
24: mapping(address => uint256) public claimable;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L24

[GAS‑14] Optimize names to save gas

Contracts most called functions could simply save gas by function ordering via Method ID. Calling a function at runtime will be cheaper if the function is positioned earlier in the order (has a relatively lower Method ID) because 22 gas are added to the cost of a function for every position that came before it. The caller can save on gas if you prioritize most called functions.

See more here

Proof Of Concept

All in-scope contracts

Recommended Mitigation Steps

Find a lower method ID name for the most called functions for example Call() vs. Call1() is cheaper by 22 gas
For example, the function IDs in the Gauge.sol contract will be the most used; A lower method ID may be given.

[GAS‑15] <x> += <y> Costs More Gas Than <x> = <x> + <y> For State Variables

Proof Of Concept

File: Lender.sol
​
263: pools[poolId].outstandingLoans += debt;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L263

File: Lender.sol
​
314: pools[poolId].outstandingLoans -= loan.debt;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L314

File: Lender.sol
​
388: pools[poolId].outstandingLoans += totalDebt;
400: pools[oldPoolId].outstandingLoans -= loan.debt;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L388

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L400

File: Lender.sol
​
490: pools[poolId].outstandingLoans += totalDebt;
502: pools[oldPoolId].outstandingLoans -= loan.debt;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L490

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L502

File: Lender.sol
​
575: pools[poolId].outstandingLoans -= loan.debt;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L575

File: Lender.sol
​
633: pools[oldPoolId].outstandingLoans -= loan.debt;
637: pools[poolId].outstandingLoans += debt;
698: pools[poolId].poolBalance -= debt;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L633

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L637

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L698

File: Lender.sol
​
726: interest -= fees;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L726

File: Staking.sol
​
41: balances[msg.sender] += _amount;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L41

File: Staking.sol
​
48: balances[msg.sender] -= _amount;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L48

File: Staking.sol
​
89: claimable[recipient] += _share;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Staking.sol#L89

[GAS‑16] Using private rather than public for constants, saves gas

If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table

Proof Of Concept

File: Fees.sol
​
16: ISwapRouter public constant swapRouter =
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Fees.sol#L16

File: Lender.sol
​
59: uint256 public constant MAX_INTEREST_RATE = 100000;
61: uint256 public constant MAX_AUCTION_LENGTH = 3 days;
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L59

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L61

Recommended Mitigation Steps

Set variable to private.

[GAS‑17] Help The Optimizer By Saving A Storage Variable's Reference Instead Of Repeatedly Fetching It

To help the optimizer, declare a storage type variable and use it instead of repeatedly fetching the reference in a map or an array.
The effect can be quite significant.
As an example, instead of repeatedly calling someMap[someIndex], save its reference like this: SomeStruct storage someStruct = someMap[someIndex] and use it.

Proof Of Concept

File: Lender.sol
​
238: Pool memory pool = pools[poolId];

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L238

File: Lender.sol
​
262: _updatePoolBalance(poolId, pools[poolId].poolBalance - debt);

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L262

File: Lender.sol
​
263: pools[poolId].outstandingLoans += debt;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L263

File: Lender.sol
​
296: Loan memory loan = loans[loanId];

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L296

File: Lender.sol
​
312: pools[poolId].poolBalance + loan.debt + lenderInterest

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L312

File: Lender.sol
​
314: pools[poolId].outstandingLoans -= loan.debt;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L314

File: Lender.sol
​
343: delete loans[loanId];

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L343

File: Lender.sol
​
363: Loan memory loan = loans[loanId];

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L363

File: Lender.sol
​
367: Pool memory pool = pools[poolId];

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L367

File: Lender.sol
​
388: pools[poolId].outstandingLoans += totalDebt;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L388

File: Lender.sol
​
398: pools[oldPoolId].poolBalance + loan.debt + lenderInterest

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L398

File: Lender.sol
​
400: pools[oldPoolId].outstandingLoans -= loan.debt;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L400

File: Lender.sol
​
416: loans[loanId].lender = pool.lender;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L416

File: Lender.sol
​
417: loans[loanId].interestRate = pool.interestRate;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L417

File: Lender.sol
​
418: loans[loanId].startTimestamp = block.timestamp;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L418

File: Lender.sol
​
419: loans[loanId].auctionStartTimestamp = type(uint256).max;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L419

File: Lender.sol
​
420: loans[loanId].debt = totalDebt;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L420

File: Lender.sol
​
426: loans[loanId].debt,

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L426

File: Lender.sol
​
427: loans[loanId].collateral,

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L427

File: Lender.sol
​
441: Loan memory loan = loans[loanId];

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L441

File: Lender.sol
​
448: loans[loanId].auctionStartTimestamp = block.timestamp;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L448

File: Lender.sol
​
552: Loan memory loan = loans[loanId];

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L552

File: Lender.sol
​
575: pools[poolId].outstandingLoans -= loan.debt;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L575

File: Lender.sol
​
584: delete loans[loanId];

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L584

File: Lender.sol
​
597: loans[loanId].lender,

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L597

File: Lender.sol
​
598: loans[loanId].loanToken,

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L598

File: Lender.sol
​
599: loans[loanId].collateralToken

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L599

File: Lender.sol
​
606: Loan memory loan = loans[loanId];

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L606

File: Lender.sol
​
611: Pool memory pool = pools[poolId];

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L611

File: Lender.sol
​
631: pools[oldPoolId].poolBalance + loan.debt + lenderInterest

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L631

File: Lender.sol
​
633: pools[oldPoolId].outstandingLoans -= loan.debt;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L633

File: Lender.sol
​
636: _updatePoolBalance(poolId, pools[poolId].poolBalance - debt);

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L636

File: Lender.sol
​
637: pools[poolId].outstandingLoans += debt;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L637

File: Lender.sol
​
659: loans[loanId].debt = debt;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L659

File: Lender.sol
​
686: loans[loanId].collateral = collateral;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L686

File: Lender.sol
​
688: loans[loanId].interestRate = pool.interestRate;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L688

File: Lender.sol
​
690: loans[loanId].startTimestamp = block.timestamp;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L690

File: Lender.sol
​
692: loans[loanId].auctionStartTimestamp = type(uint256).max;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L692

File: Lender.sol
​
694: loans[loanId].auctionLength = pool.auctionLength;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L694

File: Lender.sol
​
696: loans[loanId].lender = pool.lender;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L696

File: Lender.sol
​
698: pools[poolId].poolBalance -= debt;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L698

[GAS‑18] Superfluous event fields

block.number and block.timestamp are added to the event information by default, so adding them manually will waste additional gas.

Proof Of Concept

File: Lender.sol
​
22: event Borrowed(
address indexed borrower,
address indexed lender,
uint256 indexed loanId,
uint256 debt,
uint256 collateral,
uint256 interestRate,
uint256 startTimestamp
);

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L22

File: Lender.sol
​
31: event Repaid(
address indexed borrower,
address indexed lender,
uint256 indexed loanId,
uint256 debt,
uint256 collateral,
uint256 interestRate,
uint256 startTimestamp
);

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L31

[GAS‑19] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract's gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html
Each operation involving a uint8 costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8) as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed

Proof Of Concept

File: Lender.sol
​
724: interest = (l.interestRate * l.debt * timeElapsed) / 10000 / 365 days;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L724

File: Lender.sol
​
726: interest -= fees;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L726

File: ISwapRouter.sol
​
8: uint24 fee;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/interfaces/ISwapRouter.sol#L8

File: ISwapRouter.sol
​
13: uint160 sqrtPriceLimitX96;

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/interfaces/ISwapRouter.sol#L13

[GAS‑20] ++i/i++ Should Be unchecked{++i}/unchecked{i++} When It Is Not Possible For Them To Overflow, As Is The Case When Used In For- And While-loops

The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas PER LOOP

Proof Of Concept

File: Lender.sol
​
233: for (uint256 i = 0; i < borrows.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L233

File: Lender.sol
​
293: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L293

File: Lender.sol
​
359: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L359

File: Lender.sol
​
438: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L438

File: Lender.sol
​
549: for (uint256 i = 0; i < loanIds.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L549

File: Lender.sol
​
592: for (uint256 i = 0; i < refinances.length; i++) {

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L592

[GAS‑21] Use assembly to check for address(0)

Save 6 gas per instance if using assembly to check for address(0)

e.g.

assembly {
if iszero(_addr) {
mstore(0x00, "AddressZero")
revert(0x00, 0x20)
}
}

Proof Of Concept

File: Lender.sol
​
167: if (pools[poolId].lender == address(0)) {
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L167

File: Lender.sol
​
240: if (pool.lender == address(0)) revert PoolConfig();
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L240

[GAS‑22] Use do while loops instead of for loops

A do while loop will cost less gas since the condition is not being checked for the first iteration.

Proof Of Concept

File: Lender.sol
​
232: function borrow
​
for (uint256 i = 0; i < borrows.length; i++) {
bytes32 poolId = borrows[i].poolId;
uint256 debt = borrows[i].debt;
uint256 collateral = borrows[i].collateral;
Pool memory pool = pools[poolId];
if (pool.lender == address(0)) revert PoolConfig();
if (debt < pool.minLoanSize) revert LoanTooSmall();
if (debt > pool.poolBalance) revert LoanTooLarge();
if (collateral == 0) revert ZeroCollateral();
uint256 loanRatio = (debt * 10 ** 18) / collateral;
if (loanRatio > pool.maxLoanRatio) revert RatioTooHigh();
Loan memory loan = Loan({
lender: pool.lender,
borrower: msg.sender,
loanToken: pool.loanToken,
collateralToken: pool.collateralToken,
debt: debt,
collateral: collateral,
interestRate: pool.interestRate,
startTimestamp: block.timestamp,
auctionStartTimestamp: type(uint256).max,
auctionLength: pool.auctionLength
}
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L232

File: Lender.sol
​
292: function repay
​
for (uint256 i = 0; i < loanIds.length; i++) {
uint256 loanId = loanIds[i];
Loan memory loan = loans[loanId];
(
uint256 lenderInterest,
uint256 protocolInterest
) = _calculateInterest(loan);
​
bytes32 poolId = getPoolId(
loan.lender,
loan.loanToken,
loan.collateralToken
);
​
_updatePoolBalance(
poolId,
pools[poolId].poolBalance + loan.debt + lenderInterest
);
pools[poolId].outstandingLoans -= loan.debt;
​
IERC20(loan.loanToken).transferFrom(
msg.sender,
address(this),
loan.debt + lenderInterest
);
IERC20(loan.loanToken).transferFrom(
msg.sender,
feeReceiver,
protocolInterest
);
IERC20(loan.collateralToken).transfer(
loan.borrower,
loan.collateral
);
emit Repaid(
msg.sender,
loan.lender,
loanId,
loan.debt,
loan.collateral,
loan.interestRate,
loan.startTimestamp
);
delete loans[loanId];
}
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L292

File: Lender.sol
​
355: function giveLoan
​
for (uint256 i = 0; i < loanIds.length; i++) {
uint256 loanId = loanIds[i];
bytes32 poolId = poolIds[i];
Loan memory loan = loans[loanId];
if (msg.sender != loan.lender) revert Unauthorized();
Pool memory pool = pools[poolId];
if (pool.loanToken != loan.loanToken) revert TokenMismatch();
if (pool.collateralToken != loan.collateralToken)
revert TokenMismatch();
if (pool.interestRate > loan.interestRate) revert RateTooHigh();
if (pool.auctionLength < loan.auctionLength) revert AuctionTooShort();
(
uint256 lenderInterest,
uint256 protocolInterest
) = _calculateInterest(loan);
uint256 totalDebt = loan.debt + lenderInterest + protocolInterest;
if (pool.poolBalance < totalDebt) revert PoolTooSmall();
if (totalDebt < pool.minLoanSize) revert LoanTooSmall();
uint256 loanRatio = (totalDebt * 10 ** 18) / loan.collateral;
if (loanRatio > pool.maxLoanRatio) revert RatioTooHigh();
_updatePoolBalance(poolId, pool.poolBalance - totalDebt);
pools[poolId].outstandingLoans += totalDebt;
​
bytes32 oldPoolId = getPoolId(
loan.lender,
loan.loanToken,
loan.collateralToken
);
_updatePoolBalance(
oldPoolId,
pools[oldPoolId].poolBalance + loan.debt + lenderInterest
);
pools[oldPoolId].outstandingLoans -= loan.debt;
​
IERC20(loan.loanToken).transfer(feeReceiver, protocolInterest);
​
emit Repaid(
loan.borrower,
loan.lender,
loanId,
loan.debt + lenderInterest + protocolInterest,
loan.collateral,
loan.interestRate,
loan.startTimestamp
);
​
loans[loanId].lender = pool.lender;
loans[loanId].interestRate = pool.interestRate;
loans[loanId].startTimestamp = block.timestamp;
loans[loanId].auctionStartTimestamp = type(uint256).max;
loans[loanId].debt = totalDebt;
​
emit Borrowed(
loan.borrower,
pool.lender,
loanId,
loans[loanId].debt,
loans[loanId].collateral,
pool.interestRate,
block.timestamp
);
}
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L356

File: Lender.sol
​
437: function startAuction
​
for (uint256 i = 0; i < loanIds.length; i++) {
uint256 loanId = loanIds[i];
Loan memory loan = loans[loanId];
if (msg.sender != loan.lender) revert Unauthorized();
if (loan.auctionStartTimestamp != type(uint256).max)
revert AuctionStarted();
​
loans[loanId].auctionStartTimestamp = block.timestamp;
emit AuctionStart(
loan.borrower,
loan.lender,
loanId,
loan.debt,
loan.collateral,
block.timestamp,
loan.auctionLength
);
}
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L437

File: Lender.sol
​
548: function seizeLoan
​
for (uint256 i = 0; i < loanIds.length; i++) {
uint256 loanId = loanIds[i];
Loan memory loan = loans[loanId];
if (loan.auctionStartTimestamp == type(uint256).max)
revert AuctionNotStarted();
if (
block.timestamp <
loan.auctionStartTimestamp + loan.auctionLength
) revert AuctionNotEnded();
uint256 govFee = (borrowerFee * loan.collateral) / 10000;
IERC20(loan.collateralToken).transfer(feeReceiver, govFee);
IERC20(loan.collateralToken).transfer(
loan.lender,
loan.collateral - govFee
);
​
bytes32 poolId = keccak256(
abi.encode(loan.lender, loan.loanToken, loan.collateralToken)
);
​
pools[poolId].outstandingLoans -= loan.debt;
​
emit LoanSiezed(
loan.borrower,
loan.lender,
loanId,
loan.collateral
);
delete loans[loanId];
}
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L548

File: Lender.sol
​
591: function refinance
​
for (uint256 i = 0; i < refinances.length; i++) {
uint256 loanId = refinances[i].loanId;
bytes32 poolId = refinances[i].poolId;
bytes32 oldPoolId = keccak256(
abi.encode(
loans[loanId].lender,
loans[loanId].loanToken,
loans[loanId].collateralToken
)
);
uint256 debt = refinances[i].debt;
uint256 collateral = refinances[i].collateral;
​
Loan memory loan = loans[loanId];
if (msg.sender != loan.borrower) revert Unauthorized();
​
Pool memory pool = pools[poolId];
if (pool.loanToken != loan.loanToken) revert TokenMismatch();
if (pool.collateralToken != loan.collateralToken)
revert TokenMismatch();
if (pool.poolBalance < debt) revert LoanTooLarge();
if (debt < pool.minLoanSize) revert LoanTooSmall();
uint256 loanRatio = (debt * 10 ** 18) / collateral;
if (loanRatio > pool.maxLoanRatio) revert RatioTooHigh();
​
(
uint256 lenderInterest,
uint256 protocolInterest
) = _calculateInterest(loan);
uint256 debtToPay = loan.debt + lenderInterest + protocolInterest;
​
_updatePoolBalance(
oldPoolId,
pools[oldPoolId].poolBalance + loan.debt + lenderInterest
);
pools[oldPoolId].outstandingLoans -= loan.debt;
​
_updatePoolBalance(poolId, pools[poolId].poolBalance - debt);
pools[poolId].outstandingLoans += debt;
​
if (debtToPay > debt) {
IERC20(loan.loanToken).transferFrom(
msg.sender,
address(this),
debtToPay - debt
);
}
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L591

[GAS‑23] Use of Custom Errors Instead of String

To save some gas the use of custom errors leads to cheaper deploy time cost and run time cost. The run time cost is only relevant when the revert condition is met.

Proof Of Concept

File: Fees.sol
​
require(_profits != WETH, "not allowed")

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Fees.sol#L27

File: Ownable.sol
​
require(msg.sender == owner, "UNAUTHORIZED")

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/utils/Ownable.sol#L11

Recommended Mitigation Steps

Use Custom Errors instead of strings.

[GAS‑24] Using XOR (^) and AND (&) bitwise equivalents

Given 4 variables a, b, c and d represented as such:

0 0 0 0 0 1 1 0 <- a
0 1 1 0 0 1 1 0 <- b
0 0 0 0 0 0 0 0 <- c
1 1 1 1 1 1 1 1 <- d

To have a == b means that every 0 and 1 match on both variables. Meaning that a XOR (operator ^) would evaluate to 0 ((a ^ b) == 0), as it excludes by definition any equalities.Now, if a != b, this means that there’s at least somewhere a 1 and a 0 not matching between a and b, making (a ^ b) != 0.Both formulas are logically equivalent and using the XOR bitwise operator costs actually the same amount of gas.However, it is much cheaper to use the bitwise OR operator (|) than comparing the truthy or falsy values.These are logically equivalent too, as the OR bitwise operator (|) would result in a 1 somewhere if any value is not 0 between the XOR (^) statements, meaning if any XOR (^) statement verifies that its arguments are different.

Proof Of Concept

File: Lender.sol
​
134: p.minLoanSize == 0 ||
135: p.maxLoanRatio == 0 ||
136: p.auctionLength == 0 ||
167: if (pools[poolId].lender == address(0)) {
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L134

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L135

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L136

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L167

File: Lender.sol
​
184: if (amount == 0) revert PoolConfig();
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L184

File: Lender.sol
​
200: if (amount == 0) revert PoolConfig();
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L200

File: Lender.sol
​
212: if (maxLoanRatio == 0) revert PoolConfig();
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L212

File: Lender.sol
​
240: if (pool.lender == address(0)) revert PoolConfig();
244: if (collateral == 0) revert ZeroCollateral();
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L240

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L244

File: Lender.sol
​
469: if (loan.auctionStartTimestamp == type(uint256).max)
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L469

File: Lender.sol
​
554: if (loan.auctionStartTimestamp == type(uint256).max)
​

https://github.com/Cyfrin/2023-07-beedle/tree/main/src/Lender.sol#L554

Test Code

contract GasTest is DSTest {
    Contract0 c0;
    Contract1 c1;
    function setUp() public {
        c0 = new Contract0();
        c1 = new Contract1();
    }
    function testGas() public {
        c0.not_optimized(1,2);
        c1.optimized(1,2);
    }
}

contract Contract0 {
    function not_optimized(uint8 a,uint8 b) public returns(bool){
        return ((a==1) || (b==1));
    }
}

contract Contract1 {
    function optimized(uint8 a,uint8 b) public returns(bool){
        return ((a ^ 1) & (b ^ 1)) == 0;
    }
}

Gas Test Report

Contract0 contract
Deployment Cost Deployment Size
46099 261
Function Name min avg median max # calls
not_optimized 456 456 456 456 1
Contract1 contract
Deployment Cost Deployment Size
42493 243
Function Name min avg median max # calls
optimized 430 430 430 430 1

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!