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!