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

Gas Optimizations (Save 33108 gas)

Gas Optimizations

Issue Instances Total Gas Saved
[G-01] [G-01] Using storage instead of memory for structs/arrays saves gas 3 6300
[G-02] Multiple accesses of a mapping/array should use a local variable cache 28 1176
[G-03] Can Make The Variable Outside The Loop To Save Gas 8 800
[G-04] Emitting storage values instead of the memory one 4 400
[G-05] Cache state variables with stack variables 2 200
[G-06] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate 1 21538
[G-07] Structs can be packed into fewer storage slots by truncating timestamp bytes 1 2000
[G-08] ++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 360
[G-09] Constructors can be marked payable 4 84
[G-10] Avoid emitting block.timestamp in events 5

Total: 62 instances over 10 issues with 33108 gas saved

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

When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct.

Gas Save 6300

List of instances ```solidity File: /src/Lender.sol
117: Loan memory loan = loans[loanId];
```

```solidity
File: /src/Lender.sol
363: Loan memory loan = loans[loanId];
```

```solidity
File: /src/Lender.sol
367: Pool memory pool = pools[poolId];
```

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

Gas save 28*42 = 1176

List of instances - https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Lender.sol#L130C5-L130C5
```solidity
File: /src/Lender.sol

130: function setPool(Pool calldata p) public returns (bytes32 poolId) {
131:        // validate the pool
....      
141:        // check if they already have a pool balance
142:        poolId = getPoolId(p.lender, p.loanToken, p.collateralToken);
143:
144:        // you can't change the outstanding loans
145:       if (p.outstandingLoans != pools[poolId].outstandingLoans)  //@audit first call
146:           revert PoolConfig();
147:
148:        uint256 currentBalance = pools[poolId].poolBalance;  //@audit second call
....
166:
167:        if (pools[poolId].lender == address(0)) { //@audit third call
168:            // if the pool doesn't exist then create it
169:            emit PoolCreated(poolId, p);
170:        } else {
171:            // if the pool does exist then update it
172:            emit PoolUpdated(poolId, p);
173:        }
174:
175:        pools[poolId] = p;
176:    }
```

```diff
File: /src/Lender.sol

130: function setPool(Pool calldata p) public returns (bytes32 poolId) {
131:        // validate the pool
....      
141:        // check if they already have a pool balance
+           Pool calldata pool = pools[poolId]; 
142:        poolId = getPoolId(p.lender, p.loanToken, p.collateralToken);
143:
144:        // you can't change the outstanding loans
-145:       if (p.outstandingLoans != pools[poolId].outstandingLoans)
+145:       if (p.outstandingLoans != pool.outstandingLoans)
146:           revert PoolConfig();
147:
-148:        uint256 currentBalance = pools[poolId].poolBalance;
+148:        uint256 currentBalance = pool.poolBalance;
....
166:
-167:        if (pools[poolId].lender == address(0)) {
+167:        if (pool.lender == address(0)) {
168:            // if the pool doesn't exist then create it
169:            emit PoolCreated(poolId, p);
170:        } else {
171:            // if the pool does exist then update it
172:            emit PoolUpdated(poolId, p);
173:        }
174:
175:        pools[poolId] = p;
176:    }
```

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

```solidity
File: /src/Lender.sol

182: function addToPool(bytes32 poolId, uint256 amount) external {
183:        if (pools[poolId].lender != msg.sender) revert Unauthorized();  //@audit first call
184:        if (amount == 0) revert PoolConfig();
185:        _updatePoolBalance(poolId, pools[poolId].poolBalance + amount);  //@audit second call
186:        // transfer the loan tokens from the lender to the contract
187:        IERC20(pools[poolId].loanToken).transferFrom(    //@audit third call
188:            msg.sender,
189:            address(this),
190:            amount
191:        );
192:    }
```

```diff
File: /src/Lender.sol

182: function addToPool(bytes32 poolId, uint256 amount) external {
+            Pool calldata pool = pools[poolId]; 
-183:        if (pools[poolId].lender != msg.sender) revert Unauthorized();
+183:        if (pool.lender != msg.sender) revert Unauthorized();
184:        if (amount == 0) revert PoolConfig();
-185:        _updatePoolBalance(poolId, pools[poolId].poolBalance + amount);
+185:        _updatePoolBalance(poolId, pool.poolBalance + amount);
186:        // transfer the loan tokens from the lender to the contract
-187:        IERC20(pools[poolId].loanToken).transferFrom(
+187:        IERC20(pool.loanToken).transferFrom(
188:            msg.sender,
189:            address(this),
190:            amount
191:        );
192:    }
```

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

```solidity
File: /src/Lender.sol

198: function removeFromPool(bytes32 poolId, uint256 amount) external {
199:        if (pools[poolId].lender != msg.sender) revert Unauthorized();  //@audit first call
200:        if (amount == 0) revert PoolConfig();
201:        _updatePoolBalance(poolId, pools[poolId].poolBalance - amount);   //@audit second call
202:        // transfer the loan tokens from the contract to the lender
203:        IERC20(pools[poolId].loanToken).transfer(msg.sender, amount);  //@audit third call
204:    }
```

```diff
File: /src/Lender.sol

198: function removeFromPool(bytes32 poolId, uint256 amount) external {
+           Pool calldata pool = pools[poolId]; 
-199:        if (pools[poolId].lender != msg.sender) revert Unauthorized();
+199:        if (pool.lender != msg.sender) revert Unauthorized();
200:        if (amount == 0) revert PoolConfig();
-201:        _updatePoolBalance(poolId, pools[poolId].poolBalance - amount);
+201:        _updatePoolBalance(poolId, pool.poolBalance - amount);
202:        // transfer the loan tokens from the contract to the lender
-203:        IERC20(pools[poolId].loanToken).transfer(msg.sender, amount);
+203:        IERC20(pool.loanToken).transfer(msg.sender, amount);
204:    }
```

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

```solidity
File: /src/Lender.sol
210: function updateMaxLoanRatio(bytes32 poolId, uint256 maxLoanRatio) external {
211:        if (pools[poolId].lender != msg.sender) revert Unauthorized();
212:        if (maxLoanRatio == 0) revert PoolConfig();
213:        pools[poolId].maxLoanRatio = maxLoanRatio;
214:        emit PoolMaxLoanRatioUpdated(poolId, maxLoanRatio);
215:    }
```

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

```solidity
File: /src/Lender.sol
221: function updateInterestRate(bytes32 poolId, uint256 interestRate) external {
222:        if (pools[poolId].lender != msg.sender) revert Unauthorized();
223:        if (interestRate > MAX_INTEREST_RATE) revert PoolConfig();
224:        pools[poolId].interestRate = interestRate;
225:        emit PoolInterestRateUpdated(poolId, interestRate);
226:    }
```

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

```solidity
416: loans[loanId].lender = pool.lender;
417:            loans[loanId].interestRate = pool.interestRate;
418:            loans[loanId].startTimestamp = block.timestamp;
419:           loans[loanId].auctionStartTimestamp = type(uint256).max;
420:            loans[loanId].debt = totalDebt;
```

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

```solidity
518: loans[loanId].lender = msg.sender;
519:        loans[loanId].interestRate = pools[poolId].interestRate;
520:        loans[loanId].startTimestamp = block.timestamp;
521:        loans[loanId].auctionStartTimestamp = type(uint256).max;
522:        loans[loanId].debt = totalDebt;
```

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

```solidity
File: /src/Lender.sol
686: loans[loanId].collateral = collateral;
687:            // update loan interest rate
688:            loans[loanId].interestRate = pool.interestRate;
689:            // update loan start timestamp
690:            loans[loanId].startTimestamp = block.timestamp;
691:            // update loan auction start timestamp
692:            loans[loanId].auctionStartTimestamp = type(uint256).max;
693:            // update loan auction length
694:            loans[loanId].auctionLength = pool.auctionLength;
695:            // update loan lender
696:            loans[loanId].lender = pool.lender;
```

[G-03] Can Make The Variable Outside The Loop To Save Gas

When you declare a variable inside a loop, Solidity creates a new instance of the variable for each iteration of the loop. This can lead to unnecessary gas costs, especially if the loop is executed frequently or iterates over a large number of elements.

By declaring the variable outside the loop, you can avoid the creation of multiple instances of the variable and reduce the gas cost of your contract. Here's an example:

gas save 8*100= 800

contract MyContract {
function sum(uint256[] memory values) public pure returns (uint256) {
uint256 total = 0;
for (uint256 i = 0; i < values.length; i++) {
total += values[i];
}
return total;
}
}
List of instances - https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Lender.sol#L234C12-L237C33
```solidity
File: /src/Lender.sol

233: for (uint256 i = 0; i < borrows.length; i++) {
234:            bytes32 poolId = borrows[i].poolId;
235:            uint256 debt = borrows[i].debt;
236:            uint256 collateral = borrows[i].collateral;
```

```solidity
File: /src/Lender.sol
294: uint256 loanId = loanIds[i];
```

```solidity
File: /src/Lender.sol
360: uint256 loanId = loanIds[i];
361: bytes32 poolId = poolIds[i];
```

```solidity
File: /src/Lender.sol
439: uint256 loanId = loanIds[i];
```

```solidity
File: /src/Lender.sol
550: uint256 loanId = loanIds[i];
```

[G-04] Emitting storage values instead of the memory one

Here, the values emitted shouldn’t be read from storage. The existing memory values should be used instead:

Gas save 100 per instances

total gas 400

List of instances - https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Lender.sol#L422C12-L430C15
```solidity
File: /src/Lender.sol

422: emit Borrowed(
423:                loan.borrower,
424:                pool.lender,
425:                loanId,
426:                loans[loanId].debt,
427:                loans[loanId].collateral,
428:                pool.interestRate,
429:                block.timestamp
430:            );
```

```diff
File: /src/Lender.sol

422: emit Borrowed(
423:                loan.borrower,
424:                pool.lender,
425:                loanId,
-426:                loans[loanId].debt,
+426:                totalDebt,
-427:                loans[loanId].collateral,
+427:                loan.collateral,
428:                pool.interestRate,
429:                block.timestamp
430:            );
```

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

```solidity
File: /src/Lender.sol
524: emit Borrowed(
525:            loan.borrower,
526:            msg.sender,
527:            loanId,
528:            loans[loanId].debt,
529:            loans[loanId].collateral,
530:            pools[poolId].interestRate,
531:            block.timestamp
532:        );
```

```diff
File: /src/Lender.sol
524: emit Borrowed(
525:            loan.borrower,
526:            msg.sender,
527:            loanId,
-528:            loans[loanId].debt,
+528:            totalDebt,
-529:            loans[loanId].collateral,
+529:             loan.collateral,
-530:            pools[poolId].interestRate,
+530:            pool.interestRate,
531:            block.timestamp
532:        );
```

[G-05] Cache state variables with stack variables

Caching of a state variable replaces each Gwarmaccess (100 gas) with a cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.

Gas save (200)

List of instances - https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Fees.sol#L26C4-L44C6
```solidity
File: src/Fees.sol

26: function sellProfits(address _profits) public {
27:        require(_profits != WETH, "not allowed");
28:        uint256 amount = IERC20(_profits).balanceOf(address(this));
29:
30:        ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
31:            .ExactInputSingleParams({
32:                tokenIn: _profits,
33:                tokenOut: WETH,
34:                fee: 3000,
35:                recipient: address(this),
36:                deadline: block.timestamp,
37:                amountIn: amount,
38:                amountOutMinimum: 0,
39:                sqrtPriceLimitX96: 0
40:           });
41:
42:        amount = swapRouter.exactInputSingle(params);
43:        IERC20(WETH).transfer(staking, IERC20(WETH).balanceOf(address(this)));
44:    }
```

```diff
File: src/Fees.sol

26: function sellProfits(address _profits) public {
-27:        require(_profits != WETH, "not allowed");
+27:        require(_profits != _WETH, "not allowed");
28:        uint256 amount = IERC20(_profits).balanceOf(address(this));
29:
30:        ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
31:            .ExactInputSingleParams({
32:                tokenIn: _profits,
-33:                tokenOut: WETH,
+33:                tokenOut: _WETH,
34:                fee: 3000,
35:                recipient: address(this),
36:                deadline: block.timestamp,
37:                amountIn: amount,
38:                amountOutMinimum: 0,
39:                sqrtPriceLimitX96: 0
40:           });
41:
42:        amount = swapRouter.exactInputSingle(params);
-43:        IERC20(WETH).transfer(staking, IERC20(WETH).balanceOf(address(this)));
+43:        IERC20(_WETH).transfer(staking, IERC20(_WETH).balanceOf(address(this)));
44:    }
```

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

```solidity
File: /src/Lender.sol
//@auidt feeReceiver see line 651
656: IERC20(loan.loanToken).transfer(feeReceiver, protocolInterest);
```

[G-06] Multiple mappings that share an ID can be combined into a single mapping of ID / struct

This can avoid a Gsset (20000 Gas) per mapping combined. Reads and writes will also be cheaper when a function requires both values as they both can fit in the same storage slot.��Finally, if both fields are accessed in the same function, this can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 Gas) and that calculation's associated stack operations.

Gas save 21538

List of instances - https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Staking.sol#L21
```solidity
File: [/src/Staking.sol](https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Staking.sol#L21)

21: /// @notice mapping of user balances
22:    mapping(address => uint256) public balances;
23:    /// @notice mapping of user claimable rewards
24:    mapping(address => uint256) public claimable;
```

[G-07] Structs can be packed into fewer storage slots by truncating timestamp bytes

By using a uint32 rather than a larger type for variables that track timestamps, one can save gas by using fewer storage slots per struct, at the expense of the protocol breaking after the year 2106 (when uint32 wraps). If this is an acceptable tradeoff, each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct. Subsequent reads as well as writes have smaller gas savings

gas save 2000

List of instances - https://github.com/Cyfrin/2023-07-beedle/blob/main/src/utils/Structs.sol#L49
```solidity
File: src/utils/Structs.sol

34: struct Loan {
    /// @notice address of the lender
36:    address lender;
    /// @notice address of the borrower
38:    address borrower;
    /// @notice address of the loan token
40:    address loanToken;
    /// @notice address of the collateral token
42:    address collateralToken;
    /// @notice the amount borrowed
44:    uint256 debt;
    /// @notice the amount of collateral locked in the loan
46:    uint256 collateral;
    /// @notice the interest rate of the loan per second (in debt tokens)
48:    uint256 interestRate;
    /// @notice the timestamp of the loan start
50:    uint256 startTimestamp;
    /// @notice the timestamp of a refinance auction start
52:    uint256 auctionStartTimestamp;
    /// @notice the refinance auction length
54:    uint256 auctionLength;
55: }
```

```diff
File: src/utils/Structs.sol

34: struct Loan {
    /// @notice address of the lender
36:    address lender;
    /// @notice address of the borrower
38:    address borrower;
    /// @notice address of the loan token
40:    address loanToken;
    /// @notice address of the collateral token
42:    address collateralToken;
    /// @notice the amount borrowed
44:    uint256 debt;
    /// @notice the amount of collateral locked in the loan
46:    uint256 collateral;
    /// @notice the interest rate of the loan per second (in debt tokens)
48:    uint256 interestRate;
    /// @notice the timestamp of the loan start
-50:    uint256 startTimestamp;
-    /// @notice the timestamp of a refinance auction start
-52:    uint256 auctionStartTimestamp;
+50:    uint32 startTimestamp;
+    /// @notice the timestamp of a refinance auction start
+52:    uint32 auctionStartTimestamp;
    /// @notice the refinance auction length
54:    uint256 auctionLength;
55: }
```

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

Gas save 360

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++) {

[G-09] Constructors can be marked payable

Payable functions cost less gas to execute, since the compiler does not have to add extra checks to ensure that a payment wasn't provided. A constructor can safely be marked as payable, since only the deployer would be able to pass funds, and the project itself would not pass any funds.

Gas save 84

List of instances - https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Staking.sol#L31
```solidity
31: constructor(address _token, address _weth) Ownable(msg.sender) {
```

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

```solidity
73: constructor() Ownable(msg.sender) {
```

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

```solidity
19: constructor(address _weth, address _staking) {
```

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

```solidity
11: constructor() ERC20("Beedle", "BDL") ERC20Permit("Beedle") Ownable(msg.sender) {
```

[G-10] Avoid emitting block.timestamp in events

While the event is emitted in blockchain, an event also consist of block.timestamp Therefore a gas can be saved by removing the explicitly block.timestamp from event.

List of instances - https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Lender.sol
```solidity
File: /src/Lender.sol
277: emit Borrowed(
278:                msg.sender,
279:                pool.lender,
280:                loans.length - 1,
281:                debt,
282:                collateral,
283:                pool.interestRate,
284:                block.timestamp
285:            );
....
422: emit Borrowed(
423:                loan.borrower,
424:                pool.lender,
425:                loanId,
426:                loans[loanId].debt,
427:                loans[loanId].collateral,
428:                pool.interestRate,
429:                block.timestamp
430:            );
....
449: emit AuctionStart(
450:                loan.borrower,
451:                loan.lender,
452:                loanId,
453:                loan.debt,
454:                loan.collateral,
455:               block.timestamp,
456:               loan.auctionLength
457:            );
....
524: emit Borrowed(
525:            loan.borrower,
526:            msg.sender,
527:            loanId,
528:            loans[loanId].debt,
529:            loans[loanId].collateral,
530:            pools[poolId].interestRate,
531:            block.timestamp
532:        );
....
699: emit Borrowed(
700:                msg.sender,
701:                pool.lender,
702:                loanId,
703:                debt,
704:                collateral,
705:                pool.interestRate,
706:                block.timestamp
707:            );
```

Support

FAQs

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