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.