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

Gas Optimizations

Number Issue Instances
[G-01] Setting the constructor to payable 5
[G-02] Using delete statement can save gas 1
[G-03] Functions guaranteed to revert when called by normal users can be marked payable 5
[G-04] Use hardcode address instead address(this) 13
[G-05] += Costs More Gas Than = + For State Variables 14
[G-06] Public Functions To External 5
[G-07] Using unchecked blocks to save gas 30
[G-08] Use assembly to emit events 20
[G-09] Use assembly to validate msg.sender 7
[G-10] Multiple address mappings can be combined into a single mapping of an address to a struct, where appropriate 1
[G-11] Using storage instead of memory for structs/arrays saves gas 10
[G-12] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead 1
[G-13] Internal/Private functions only called once can be inlined to save gas Gas saved: 20 * 20 = 400 2
[G-14] Use assembly to write address storage values 3
[G-15] .length should not be looked up in every loop of a for-loop 6
[G-16] Using > 0 costs more gas than != 0 when used on a uint in a require() statement 5
[G-17] Use custom errors rather than revert()/require() strings to save gas 2
[G-18] Using private rather than public for constants, saves gas 3
[G-19] Use assembly to check for address(0) 3
[G-20] Don’t initialize variables with default value 9
[G-21] Amounts should be checked for 0 before calling a transfer 3
[G-22] abi.encode() is less efficient than abi.encodePacked() 3

[G-01] Setting the constructor to payable

Saves ~13 gas per instance.

File: src/utils/Ownable.sol
​
14 constructor(address _owner) {
owner = _owner;
emit OwnershipTransferred(address(0), _owner);
}
​

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

File: src/Staking.sol
31 constructor(address _token, address _weth) Ownable(msg.sender) {
TKN = IERC20(_token);
WETH = IERC20(_weth);
}

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

File: src/Lender.sol
73 constructor() Ownable(msg.sender) {
feeReceiver = msg.sender;
}
​

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

File: src/Fees.sol
​
19 constructor(address _weth, address _staking) {
WETH = _weth;
staking = _staking;
}
​

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

File: src/Beedle.sol
​
11 constructor() ERC20("Beedle", "BDL") ERC20Permit("Beedle") Ownable(msg.sender) {
_mint(msg.sender, 1_000_000_000 * 1e18);
}
​

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

[G-02] Using delete statement can save gas

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

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

[G-03] 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.

File: src/Beedle.sol
​
36 function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
​

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

File: src/Lender.sol
​
84 function setLenderFee(uint256 _fee) external onlyOwner {
if (_fee > 5000) revert FeeTooHigh();
lenderFee = _fee;
}
92 function setBorrowerFee(uint256 _fee) external onlyOwner {
if (_fee > 500) revert FeeTooHigh();
borrowerFee = _fee;
}
​
100 function setFeeReceiver(address _feeReceiver) external onlyOwner {
feeReceiver = _feeReceiver;
}

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

File: src/utils/Ownable.sol
​
19 function transferOwnership(address _owner) public virtual onlyOwner {
owner = _owner;
emit OwnershipTransferred(msg.sender, _owner);
}
​

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

[G-04] Use hardcode address instead address(this)

Instead of using address(this), it is more gas-efficient to pre-calculate and use the hardcoded address. Foundry’s script.sol and solmate’s LibRlp.sol contracts can help achieve this.

References:

https://book.getfoundry.sh/reference/forge-std/compute-create-address
https://twitter.com/transmissions11/status/1518507047943245824

file: src/Staking.sol
​
39 TKN.transferFrom(msg.sender, address(this), _amount);
​
57 balance = WETH.balanceOf(address(this));
​
62 uint256 totalSupply = TKN.balanceOf(address(this));
​
64 uint256 _balance = WETH.balanceOf(address(this));
​

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

File: src/Fees.sol
​
28 uint256 amount = IERC20(_profits).balanceOf(address(this));
​
35 recipient: address(this),
​
43 IERC20(WETH).transfer(staking, IERC20(WETH).balanceOf(address(this)));
​

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

File: src/Lender.sol
​
154 address(this),
​
189 address(this),
​
273 address(this),
​
319 address(this),
​
644 address(this),
​
665 address(this),
​

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

[G-05] += Costs More Gas Than = + For State Variables

File: src/Lender.sol
​
263 pools[poolId].outstandingLoans += debt;
​
314 pools[poolId].outstandingLoans -= loan.debt;
​
388 pools[poolId].outstandingLoans += totalDebt;
​
400 pools[oldPoolId].outstandingLoans -= loan.debt;
​
490 pools[poolId].outstandingLoans += totalDebt;
​
502 pools[oldPoolId].outstandingLoans -= loan.debt;
​
575 pools[poolId].outstandingLoans -= loan.debt;
​
633 pools[oldPoolId].outstandingLoans -= loan.debt;
​
637 pools[poolId].outstandingLoans += debt;
​
698 pools[poolId].poolBalance -= debt;
​
726 interest -= fees;
​

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

File: src/Staking.sol
​
41 balances[msg.sender] += _amount;
​
48 balances[msg.sender] -= _amount;
​
89 claimable[recipient] += _share;
​

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

[G-06] Public Functions To External

The following functions could be set external to save gas and improve code quality.
External call cost is less expensive than of public functions.

File: src/Fees.sol
​
26 function sellProfits(address _profits) public {
​

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

File: src/Lender.sol
​
292 function repay(uint256[] calldata loanIds) public {
​
437 function startAuction(uint256[] calldata loanIds) public {
​
548 function seizeLoan(uint256[] calldata loanIds) public {
​

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

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

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

[G-07] Using unchecked blocks to save gas

Solidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isn’t possible (as an example, when a comparison is made before the arithmetic operation), some gas can be saved by using an unchecked block.

File: src/Lender.sol
​
120 debt = loan.debt + interest + fees;
​
155 p.poolBalance - currentBalance
​
161 currentBalance - p.poolBalance
​
185 _updatePoolBalance(poolId, pools[poolId].poolBalance + amount);
​
201 _updatePoolBalance(poolId, pools[poolId].poolBalance - amount);
​
262 _updatePoolBalance(poolId, pools[poolId].poolBalance - debt);
​
269 IERC20(loan.loanToken).transfer(msg.sender, debt - fees);
​
280 loans.length - 1,
​
312 pools[poolId].poolBalance + loan.debt + lenderInterest
​
320 loan.debt + lenderInterest
​
381 uint256 totalDebt = loan.debt + lenderInterest + protocolInterest;
​
387 _updatePoolBalance(poolId, pool.poolBalance - totalDebt);
​
398 pools[oldPoolId].poolBalance + loan.debt + lenderInterest
​
409 loan.debt + lenderInterest + protocolInterest,
​
471 if (block.timestamp > loan.auctionStartTimestamp + loan.auctionLength)
​
474 uint256 timeElapsed = block.timestamp - loan.auctionStartTimestamp;
​
485 uint256 totalDebt = loan.debt + lenderInterest + protocolInterest;
​
489 _updatePoolBalance(poolId, pools[poolId].poolBalance - totalDebt);
​
500 pools[oldPoolId].poolBalance + loan.debt + lenderInterest
​
511 loan.debt + lenderInterest + protocolInterest,
​
558 loan.auctionStartTimestamp + loan.auctionLength
​
567 loan.collateral - govFee
​
626 uint256 debtToPay = loan.debt + lenderInterest + protocolInterest;
​
631 pools[oldPoolId].poolBalance + loan.debt + lenderInterest
​
636 _updatePoolBalance(poolId, pools[poolId].poolBalance - debt);
​
645 debtToPay - debt
​
650 uint256 fee = (borrowerFee * (debt - debtToPay)) / 10000;
​

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

File: src/Staking.sol
​
66 uint256 _diff = _balance - balance;
​
70 index = index + _ratio;
​
86 uint256 _delta = index - _supplyIndex;
​

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

[G-08] 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.

File: src/Lender.sol
​
165 emit PoolBalanceUpdated(poolId, p.poolBalance);
​
169 emit PoolCreated(poolId, p);
​
172 emit PoolUpdated(poolId, p);
​
214 emit PoolMaxLoanRatioUpdated(poolId, maxLoanRatio);
​
225 emit PoolInterestRateUpdated(poolId, interestRate);
​
277 emit Borrowed(
​
333 emit Repaid(
​
405 emit Repaid(
​
422 emit Borrowed(
​
449 emit AuctionStart(
​
507 emit Repaid(
​
524 emit Borrowed(
​
533 emit LoanBought(loanId);
​
577 emit LoanSiezed(
​
676 emit Repaid(
​
699 emit Borrowed(
​
708 emit Refinanced(loanId);
​
734 emit PoolBalanceUpdated(poolId, newBalance);
​

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

File: src/utils/Ownable.sol
​
16 emit OwnershipTransferred(address(0), _owner);
​
21 emit OwnershipTransferred(msg.sender, _owner);
​

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

[G-09] Use assembly to validate msg.sender

We can use assembly to efficiently validate msg.sender for the didPay and uniswapV3SwapCallback functions with the least amount of opcodes necessary. Additionally, we can use xor() instead of iszero(eq()), saving 3 gas. We can also potentially save gas on the unhappy path by using scratch space to store the error selector, potentially avoiding memory expansion costs.

File: src/utils/Ownable.sol
​
11 require(msg.sender == owner, "UNAUTHORIZED");
​

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

File: src/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();
​
365 if (msg.sender != loan.lender) revert Unauthorized();
​
443 if (msg.sender != loan.lender) revert Unauthorized();
​

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

[G-10] Multiple address mappings can be combined into a single mapping of an address to a struct, where appropriate

We can combine multiple mappings below into structs. This will result in cheaper storage reads since multiple mappings are accessed in functions and those values are now occupying the same storage slot, meaning the slot will become warm after the first SLOAD. In addition, when writing to and reading from the struct values we will avoid a Gsset (20000 gas) and Gcoldsload (2100 gas) since multiple struct values are now occupying the same slot.

File: src/Staking.sol
​
19 mapping(address => uint256) public supplyIndex;
20 /// @notice mapping of user balances
21 mapping(address => uint256) public balances;
22 /// @notice mapping of user claimable rewards
23 mapping(address => uint256) public claimable;

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

[G-11] Using storage instead of memory for structs/arrays saves gas

File: src/Lender.sol
​
117 Loan memory loan = loans[loanId];
​
238 Pool memory pool = pools[poolId];
​
296 Loan memory loan = loans[loanId];
​
363 Loan memory loan = loans[loanId];
​
367 Pool memory pool = pools[poolId];
​
441 Loan memory loan = loans[loanId];
​
467 Loan memory loan = loans[loanId];
​
552 Loan memory loan = loans[loanId];
​
606 Loan memory loan = loans[loanId];
​
611 Pool memory pool = pools[poolId];

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

[G-12] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

The EVM operates with 32 byte words. Therefore, if you declare state variables less than 32 bytes the EVM will need to perform extra operations to cast your value to the specified size.

File: src/interfaces/ISwapRouter.sol
​
8 uint24 fee;
​

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

[G-13] Internal/Private functions only called once can be inlined to save gas Gas saved: 20 * 20 = 400

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

File: src/Beedle.sol
​
15 function _afterTokenTransfer(address from, address to, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
​
29 function _burn(address account, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
​

https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Beedle.sol#L15-L18

[G-14] Use assembly to write address storage values

File: src/Lender.sol
​
100 function setFeeReceiver(address _feeReceiver) external onlyOwner {
feeReceiver = _feeReceiver;
}
​

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

File: src/utils/Ownable.sol
​
14 constructor(address _owner) {
owner = _owner;
emit OwnershipTransferred(address(0), _owner);
}
​
19 function transferOwnership(address _owner) public virtual onlyOwner {
owner = _owner;
emit OwnershipTransferred(msg.sender, _owner);
}
​

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

Recommendation Code

​
14: constructor(
- 15: owner = _owner;
+ assembly {
+ sstore(vault. owner, _owner)
+ }
​

[G‑15] .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)

File: src/Lender.sol
​
233 for (uint256 i = 0; i < borrows.length; i++) {
​
293 for (uint256 i = 0; i < loanIds.length; i++) {
​
359 for (uint256 i = 0; i < loanIds.length; i++) {
​
438 for (uint256 i = 0; i < loanIds.length; i++) {
​
549 for (uint256 i = 0; i < loanIds.length; i++) {
​
592 for (uint256 i = 0; i < refinances.length; i++) {
​

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

[G‑16] Using > 0 costs more gas than != 0 when used on a uint in a require() statement

File: src/Staking.sol
​
63 if (totalSupply > 0) {
​
67 if (_diff > 0) {
​
69 if (_ratio > 0) {
​
83 if (_supplied > 0) {
​
87 if (_delta > 0) {
​

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

[G‑17] Use custom errors rather than revert()/require() strings to save gas

Custom errors are available from solidity version 0.8.4.

Custom errors save ~50 gas each time they’re hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas.

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

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

File: src/utils/Ownable.sol
​
11 require(msg.sender == owner, "UNAUTHORIZED");
​

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

[G‑18] 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.

File: src/Fees.sol
​
16 ISwapRouter public constant swapRouter =
ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
​

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

File: src/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/blob/main/src/Lender.sol#L59

[G-19] Use assembly to check for address(0)

Saves 6 gas per instance

File: src/Lender.sol
​
167 if (pools[poolId].lender == address(0)) {
​
240 if (pool.lender == address(0)) revert PoolConfig();
​

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

File: src/utils/Ownable.sol
​
16 emit OwnershipTransferred(address(0), _owner);
​

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

[G-20] Don’t initialize variables with default value

File: src/Lender.sol
​
233 for (uint256 i = 0; i < borrows.length; i++) {
​
293 for (uint256 i = 0; i < loanIds.length; i++) {
​
359 for (uint256 i = 0; i < loanIds.length; i++) {
​
438 for (uint256 i = 0; i < loanIds.length; i++) {
​
549 for (uint256 i = 0; i < loanIds.length; i++) {
​
592 for (uint256 i = 0; i < refinances.length; i++) {
​

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

File: src/Staking.sol
​
14 uint256 public balance = 0;
​
16 uint256 public index = 0;
​
56 claimable[msg.sender] = 0;
​

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

[G-21] Amounts should be checked for 0 before calling a transfer

Checking non-zero transfer values can avoid an expensive external call and save gas.
While this is done at some places, it’s not consistently done in the solution.
I suggest adding a non-zero-value check here:

File: src/Beedle.sol
​
19 super._afterTokenTransfer(from, to, amount);
​

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

File: src/Staking.sol
​
39 TKN.transferFrom(msg.sender, address(this), _amount);
​
49 TKN.transfer(msg.sender, _amount);
​

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

[G-22] abi.encode() is less efficient than abi.encodePacked()

Consider changing it if possible.
There are 4 instances of this issue:

File: src/Lender.sol
​
113 poolId = keccak256(abi.encode(lender, loanToken, collateralToken));
​
571 abi.encode(loan.lender, loan.loanToken, loan.collateralToken)
​
596 abi.encode(
​

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

Support

FAQs

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

Give us feedback!